setup package in lib subdirectory
[electrum-nvc.git] / lib / gui_qt.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 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 import sys, time, datetime, re
20
21 # todo: see PySide
22
23 from PyQt4.QtGui import *
24 from PyQt4.QtCore import *
25 import PyQt4.QtCore as QtCore
26 import PyQt4.QtGui as QtGui
27 from interface import DEFAULT_SERVERS
28
29 try:
30     import icons_rc
31 except:
32     print "Could not import icons_rp.py"
33     print "Please generate it with: 'pyrcc4 icons.qrc -o icons_rc.py'"
34     sys.exit(1)
35
36 from wallet import format_satoshis
37 from decimal import Decimal
38
39 import platform
40 MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace'
41     
42
43 def numbify(entry, is_int = False):
44     text = unicode(entry.text()).strip()
45     chars = '0123456789'
46     if not is_int: chars +='.'
47     s = ''.join([i for i in text if i in chars])
48     if not is_int:
49         if '.' in s:
50             p = s.find('.')
51             s = s.replace('.','')
52             s = s[:p] + '.' + s[p:p+8]
53         try:
54             amount = int( Decimal(s) * 100000000 )
55         except:
56             amount = None
57     else:
58         try:
59             amount = int( s )
60         except:
61             amount = None
62     entry.setText(s)
63     return amount
64
65
66 class Timer(QtCore.QThread):
67     def run(self):
68         while True:
69             self.emit(QtCore.SIGNAL('timersignal'))
70             time.sleep(0.5)
71
72 class EnterButton(QPushButton):
73     def __init__(self, text, func):
74         QPushButton.__init__(self, text)
75         self.func = func
76         self.clicked.connect(func)
77
78     def keyPressEvent(self, e):
79         if e.key() == QtCore.Qt.Key_Return:
80             apply(self.func,())
81
82 class StatusBarButton(QPushButton):
83     def __init__(self, icon, tooltip, func):
84         QPushButton.__init__(self, icon, '')
85         self.setToolTip(tooltip)
86         self.setFlat(True)
87         self.setMaximumWidth(25)
88         self.clicked.connect(func)
89         self.func = func
90
91     def keyPressEvent(self, e):
92         if e.key() == QtCore.Qt.Key_Return:
93             apply(self.func,())
94
95
96 class QRCodeWidget(QWidget):
97
98     def __init__(self, addr):
99         super(QRCodeWidget, self).__init__()
100         self.setGeometry(300, 300, 350, 350)
101         self.set_addr(addr)
102
103     def set_addr(self, addr):
104         from electrum import pyqrnative
105         self.addr = addr
106         self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
107         self.qr.addData(addr)
108         self.qr.make()
109         
110     def paintEvent(self, e):
111         qp = QtGui.QPainter()
112         qp.begin(self)
113         boxsize = 7
114         size = self.qr.getModuleCount()*boxsize
115         k = self.qr.getModuleCount()
116         black = QColor(0, 0, 0, 255)
117         white = QColor(255, 255, 255, 255)
118         for r in range(k):
119             for c in range(k):
120                 if self.qr.isDark(r, c):
121                     qp.setBrush(black)
122                     qp.setPen(black)
123                 else:
124                     qp.setBrush(white)
125                     qp.setPen(white)
126                 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
127         qp.end()
128         
129
130
131 def ok_cancel_buttons(dialog):
132     hbox = QHBoxLayout()
133     hbox.addStretch(1)
134     b = QPushButton("OK")
135     hbox.addWidget(b)
136     b.clicked.connect(dialog.accept)
137     b = QPushButton("Cancel")
138     hbox.addWidget(b)
139     b.clicked.connect(dialog.reject)
140     return hbox
141
142
143 class ElectrumWindow(QMainWindow):
144
145     def __init__(self, wallet):
146         QMainWindow.__init__(self)
147         self.wallet = wallet
148         self.wallet.gui_callback = self.update_callback
149
150         self.funds_error = False
151
152         self.tabs = tabs = QTabWidget(self)
153         tabs.addTab(self.create_history_tab(), 'History')
154         tabs.addTab(self.create_send_tab(),    'Send')
155         tabs.addTab(self.create_receive_tab(), 'Receive')
156         tabs.addTab(self.create_contacts_tab(),'Contacts')
157         tabs.addTab(self.create_wall_tab(),    'Wall')
158         tabs.setMinimumSize(600, 400)
159         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
160         self.setCentralWidget(tabs)
161         self.create_status_bar()
162         self.setGeometry(100,100,840,400)
163         self.setWindowTitle( 'Electrum ' + self.wallet.electrum_version )
164         self.show()
165
166         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
167         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
168         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
169         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
170         
171         self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
172
173
174     def connect_slots(self, sender):
175         self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
176         self.previous_payto_e=''
177
178     def check_recipient(self):
179         if self.payto_e.hasFocus():
180             return
181         r = unicode( self.payto_e.text() )
182         if r != self.previous_payto_e:
183             self.previous_payto_e = r
184             r = r.strip()
185             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
186                 try:
187                     to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
188                 except:
189                     return
190                 if to_address:
191                     s = r + ' <' + to_address + '>'
192                     self.payto_e.setText(s)
193
194
195     def update_callback(self):
196         self.emit(QtCore.SIGNAL('updatesignal'))
197
198     def update_wallet(self):
199         if self.wallet.interface.is_connected:
200             if self.wallet.blocks == -1:
201                 text = "Connecting..."
202                 icon = QIcon(":icons/status_disconnected.png")
203             elif self.wallet.blocks == 0:
204                 text = "Server not ready"
205                 icon = QIcon(":icons/status_disconnected.png")
206             elif not self.wallet.up_to_date:
207                 text = "Synchronizing..."
208                 icon = QIcon(":icons/status_waiting.png")
209             else:
210                 c, u = self.wallet.get_balance()
211                 text =  "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
212                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
213                 icon = QIcon(":icons/status_connected.png")
214         else:
215             text = "Not connected"
216             icon = QIcon(":icons/status_disconnected.png")
217
218         if self.funds_error:
219             text = "Not enough funds"
220
221         self.statusBar().showMessage(text)
222         self.status_button.setIcon( icon )
223
224         if self.wallet.up_to_date:
225             self.textbox.setText( self.wallet.banner )
226             self.update_history_tab()
227             self.update_receive_tab()
228             self.update_contacts_tab()
229
230
231     def create_history_tab(self):
232         self.history_list = w = QTreeWidget(self)
233         #print w.getContentsMargins()
234         w.setColumnCount(5)
235         w.setColumnWidth(0, 40) 
236         w.setColumnWidth(1, 140) 
237         w.setColumnWidth(2, 350) 
238         w.setColumnWidth(3, 140) 
239         w.setColumnWidth(4, 140) 
240         w.setHeaderLabels( [ '', 'Date', 'Description', 'Amount', 'Balance'] )
241         self.connect(w, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.tx_details)
242         self.connect(w, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
243         self.connect(w, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
244         return w
245
246     def tx_details(self, item, column):
247         tx_hash = str(item.toolTip(0))
248         tx = self.wallet.tx_history.get(tx_hash)
249
250         if tx['height']:
251             conf = self.wallet.blocks - tx['height'] + 1
252             time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
253         else:
254             conf = 0
255             time_str = 'pending'
256
257         tx_details = "Transaction Details:\n\n" \
258             + "Transaction ID:\n" + tx_hash + "\n\n" \
259             + "Status: %d confirmations\n\n"%conf  \
260             + "Date: %s\n\n"%time_str \
261             + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
262             + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
263
264         r = self.wallet.receipts.get(tx_hash)
265         if r:
266             tx_details += "\n_______________________________________" \
267                 + '\n\nSigned URI: ' + r[2] \
268                 + "\n\nSigned by: " + r[0] \
269                 + '\n\nSignature: ' + r[1]
270
271         QMessageBox.information(self, 'Details', tx_details, 'OK')
272
273
274     def tx_label_clicked(self, item, column):
275         if column==2 and item.isSelected():
276             tx_hash = str(item.toolTip(0))
277             self.is_edit=True
278             #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
279             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
280             self.history_list.editItem( item, column )
281             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
282             self.is_edit=False
283
284     def tx_label_changed(self, item, column):
285         if self.is_edit: 
286             return
287         self.is_edit=True
288         tx_hash = str(item.toolTip(0))
289         tx = self.wallet.tx_history.get(tx_hash)
290         s = self.wallet.labels.get(tx_hash)
291         text = unicode( item.text(2) )
292         if text: 
293             self.wallet.labels[tx_hash] = text
294             item.setForeground(2, QBrush(QColor('black')))
295         else:
296             if s: self.wallet.labels.pop(tx_hash)
297             text = tx['default_label']
298             item.setText(2, text)
299             item.setForeground(2, QBrush(QColor('gray')))
300         self.is_edit=False
301
302     def address_label_clicked(self, item, column, l):
303         if column==1 and item.isSelected():
304             addr = unicode( item.text(0) )
305             if addr in map(lambda x:x[1], self.wallet.aliases.values()):
306                 return
307             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
308             l.editItem( item, column )
309             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
310
311     def address_label_changed(self, item, column, l):
312         addr = unicode( item.text(0) )
313         text = unicode( item.text(1) )
314         if text:
315             self.wallet.labels[addr] = text
316         else:
317             s = self.wallet.labels.get(addr)
318             if s: self.wallet.labels.pop(addr)
319         self.update_history_tab()
320
321     def update_history_tab(self):
322         self.history_list.clear()
323         balance = 0
324         for tx in self.wallet.get_tx_history():
325             tx_hash = tx['tx_hash']
326             if tx['height']:
327                 conf = self.wallet.blocks - tx['height'] + 1
328                 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
329                 icon = QIcon(":icons/confirmed.png")
330             else:
331                 conf = 0
332                 time_str = 'pending'
333                 icon = QIcon(":icons/unconfirmed.png")
334             v = tx['value']
335             balance += v 
336             label = self.wallet.labels.get(tx_hash)
337             is_default_label = (label == '') or (label is None)
338             if is_default_label: label = tx['default_label']
339
340             item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] )
341             item.setFont(2, QFont(MONOSPACE_FONT))
342             item.setFont(3, QFont(MONOSPACE_FONT))
343             item.setFont(4, QFont(MONOSPACE_FONT))
344             item.setToolTip(0, tx_hash)
345             if is_default_label:
346                 item.setForeground(2, QBrush(QColor('grey')))
347
348             item.setIcon(0, icon)
349             self.history_list.insertTopLevelItem(0,item)
350
351
352     def create_send_tab(self):
353         w = QWidget()
354
355         grid = QGridLayout()
356         grid.setSpacing(8)
357         grid.setColumnMinimumWidth(3,300)
358         grid.setColumnStretch(4,1)
359
360         self.payto_e = QLineEdit()
361         grid.addWidget(QLabel('Pay to'), 1, 0)
362         grid.addWidget(self.payto_e, 1, 1, 1, 3)
363
364         self.message_e = QLineEdit()
365         grid.addWidget(QLabel('Description'), 2, 0)
366         grid.addWidget(self.message_e, 2, 1, 1, 3)
367
368         self.amount_e = QLineEdit()
369         grid.addWidget(QLabel('Amount'), 3, 0)
370         grid.addWidget(self.amount_e, 3, 1, 1, 2)
371         
372         self.fee_e = QLineEdit()
373         grid.addWidget(QLabel('Fee'), 4, 0)
374         grid.addWidget(self.fee_e, 4, 1, 1, 2)
375         
376         b = EnterButton("Send", self.do_send)
377         grid.addWidget(b, 5, 1)
378
379         b = EnterButton("Clear",self.do_clear)
380         grid.addWidget(b, 5, 2)
381
382         self.payto_sig = QLabel('')
383         grid.addWidget(self.payto_sig, 6, 0, 1, 4)
384
385         w.setLayout(grid) 
386         w.show()
387
388         w2 = QWidget()
389         vbox = QVBoxLayout()
390         vbox.addWidget(w)
391         vbox.addStretch(1)
392         w2.setLayout(vbox)
393
394         def entry_changed( is_fee ):
395             self.funds_error = False
396             amount = numbify(self.amount_e)
397             fee = numbify(self.fee_e)
398             if not is_fee: fee = None
399             if amount is None:
400                 return
401             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
402             if not is_fee:
403                 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
404             if inputs:
405                 palette = QPalette()
406                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
407             else:
408                 palette = QPalette()
409                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
410                 self.funds_error = True
411             self.amount_e.setPalette(palette)
412             self.fee_e.setPalette(palette)
413
414         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
415         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
416
417         return w2
418
419     def do_send(self):
420
421         label = unicode( self.message_e.text() )
422         r = unicode( self.payto_e.text() )
423         r = r.strip()
424
425         m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
426         m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
427         
428         if m1:
429             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
430             if not to_address:
431                 return
432         elif m2:
433             to_address = m2.group(5)
434         else:
435             to_address = r
436
437         if not self.wallet.is_valid(to_address):
438             QMessageBox.warning(self, 'Error', 'Invalid Bitcoin Address:\n'+to_address, 'OK')
439             return
440
441         try:
442             amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
443         except:
444             QMessageBox.warning(self, 'Error', 'Invalid Amount', 'OK')
445             return
446         try:
447             fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
448         except:
449             QMessageBox.warning(self, 'Error', 'Invalid Fee', 'OK')
450             return
451
452         if self.wallet.use_encryption:
453             password = self.password_dialog()
454             if not password:
455                 return
456         else:
457             password = None
458
459         try:
460             tx = self.wallet.mktx( to_address, amount, label, password, fee )
461         except BaseException, e:
462             self.show_message(e.message)
463             return
464             
465         status, msg = self.wallet.sendtx( tx )
466         if status:
467             QMessageBox.information(self, '', 'Payment sent.\n'+msg, 'OK')
468             self.do_clear()
469             self.update_contacts_tab()
470         else:
471             QMessageBox.warning(self, 'Error', msg, 'OK')
472
473
474     def set_url(self, url):
475         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
476         self.tabs.setCurrentIndex(1)
477         self.payto_e.setText(payto)
478         self.message_e.setText(message)
479         self.amount_e.setText(amount)
480         if identity:
481             self.set_frozen(self.payto_e,True)
482             self.set_frozen(self.amount_e,True)
483             self.set_frozen(self.message_e,True)
484             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
485         else:
486             self.payto_sig.setVisible(False)
487
488     def do_clear(self):
489         self.payto_sig.setVisible(False)
490         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
491             e.setText('')
492             self.set_frozen(e,False)
493
494     def set_frozen(self,entry,frozen):
495         if frozen:
496             entry.setReadOnly(True)
497             entry.setFrame(False)
498             palette = QPalette()
499             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
500             entry.setPalette(palette)
501         else:
502             entry.setReadOnly(False)
503             entry.setFrame(True)
504             palette = QPalette()
505             palette.setColor(entry.backgroundRole(), QColor('white'))
506             entry.setPalette(palette)
507
508
509
510
511     def clear_buttons(self, hbox):
512         while hbox.count(): hbox.removeItem(hbox.itemAt(0))
513
514     def add_buttons(self, l, hbox, is_recv):
515         self.clear_buttons(hbox)
516
517         i = l.currentItem()
518         if not i: return
519         addr = unicode( i.text(0) )
520
521         hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(addr)))
522         hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(addr)))
523         if is_recv:
524             def toggle_freeze(addr):
525                 if addr in self.wallet.frozen_addresses:
526                     self.wallet.frozen_addresses.remove(addr)
527                 else:
528                     self.wallet.frozen_addresses.append(addr)
529                 self.wallet.save()
530                 self.update_receive_tab()
531
532             t = "Unfreeze" if addr in self.wallet.frozen_addresses else "Freeze"
533             hbox.addWidget(EnterButton(t, lambda: toggle_freeze(addr)))
534
535         else:
536             def payto(addr):
537                 if not addr:return
538                 self.tabs.setCurrentIndex(1)
539                 self.payto_e.setText(addr)
540                 self.amount_e.setFocus()
541             hbox.addWidget(EnterButton('Pay to', lambda: payto(addr)))
542             hbox.addWidget(EnterButton("New", self.newaddress_dialog))
543         hbox.addStretch(1)
544
545
546     def create_receive_tab(self):
547         l = QTreeWidget(self)
548         l.setColumnCount(4)
549         l.setColumnWidth(0, 350) 
550         l.setColumnWidth(1, 330)
551         l.setColumnWidth(2, 100) 
552         l.setColumnWidth(3, 10) 
553         l.setHeaderLabels( ['Address', 'Label','Balance','Tx'])
554
555         w = QWidget()
556         vbox = QVBoxLayout()
557         w.setLayout(vbox)
558
559         vbox.setMargin(0)
560         vbox.setSpacing(0)
561         vbox.addWidget(l)
562         buttons = QWidget()
563         vbox.addWidget(buttons)
564
565         hbox = QHBoxLayout()
566         hbox.setMargin(0)
567         hbox.setSpacing(0)
568         buttons.setLayout(hbox)
569
570         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l))
571         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l))
572         self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, True))
573         self.receive_list = l
574         self.receive_buttons_hbox = hbox
575         return w
576
577     def create_contacts_tab(self):
578         l = QTreeWidget(self)
579         l.setColumnCount(3)
580         l.setColumnWidth(0, 350) 
581         l.setColumnWidth(1, 330)
582         l.setColumnWidth(2, 20) 
583         l.setHeaderLabels( ['Address', 'Label','Tx'])
584
585         w = QWidget()
586         vbox = QVBoxLayout()
587         w.setLayout(vbox)
588
589         vbox.setMargin(0)
590         vbox.setSpacing(0)
591         vbox.addWidget(l)
592         buttons = QWidget()
593         vbox.addWidget(buttons)
594
595         hbox = QHBoxLayout()
596         hbox.setMargin(0)
597         hbox.setSpacing(0)
598         buttons.setLayout(hbox)
599
600         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l))
601         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l))
602         self.connect(l, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.show_contact_details)
603         self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, False))
604
605         self.contacts_list = l
606         self.contacts_buttons_hbox = hbox
607         return w
608
609     def update_receive_tab(self):
610         self.receive_list.clear()
611         self.clear_buttons(self.receive_buttons_hbox)
612
613         for address in self.wallet.all_addresses():
614             if self.wallet.is_change(address):continue
615             label = self.wallet.labels.get(address,'')
616             n = 0 
617             h = self.wallet.history.get(address,[])
618             for item in h:
619                 if not item['is_input'] : n=n+1
620             tx = "None" if n==0 else "%d"%n
621
622             c, u = self.wallet.get_addr_balance(address)
623             balance = format_satoshis( c + u, False, self.wallet.num_zeros )
624             if address in self.wallet.frozen_addresses: 
625                 balance += '[F]'
626
627             item = QTreeWidgetItem( [ address, label, balance, tx] )
628             item.setFont(0, QFont(MONOSPACE_FONT))
629             self.receive_list.addTopLevelItem(item)
630
631     def show_contact_details(self, item, column):
632         m = unicode(item.text(0))
633         a = self.wallet.aliases.get(m)
634         if a:
635             if a[0] in self.wallet.authorities.keys():
636                 s = self.wallet.authorities.get(a[0])
637             else:
638                 s = "self-signed"
639             msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
640             QMessageBox.information(self, 'Alias', msg, 'OK')
641
642     def update_contacts_tab(self):
643         self.contacts_list.clear()
644         self.clear_buttons(self.contacts_buttons_hbox)
645
646         for alias, v in self.wallet.aliases.items():
647             s, target = v
648             item = QTreeWidgetItem( [ target, alias, '-'] )
649             self.contacts_list.addTopLevelItem(item)
650             
651         for address in self.wallet.addressbook:
652             label = self.wallet.labels.get(address,'')
653             n = 0 
654             for item in self.wallet.tx_history.values():
655                 if address in item['outputs'] : n=n+1
656             tx = "None" if n==0 else "%d"%n
657             item = QTreeWidgetItem( [ address, label, tx] )
658             item.setFont(0, QFont(MONOSPACE_FONT))
659             self.contacts_list.addTopLevelItem(item)
660
661
662     def create_wall_tab(self):
663         self.textbox = textbox = QTextEdit(self)
664         textbox.setFont(QFont(MONOSPACE_FONT))
665         textbox.setReadOnly(True)
666         return textbox
667
668     def create_status_bar(self):
669         sb = QStatusBar()
670         sb.setFixedHeight(35)
671         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
672         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
673         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
674         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) 
675         sb.addPermanentWidget( self.status_button )
676         self.setStatusBar(sb)
677
678     def newaddress_dialog(self):
679         text, ok = QInputDialog.getText(self, 'New Contact', 'Address:')
680         address = unicode(text)
681         if ok:
682             if self.wallet.is_valid(address):
683                 self.wallet.addressbook.append(address)
684                 self.wallet.save()
685                 self.update_contacts_tab()
686             else:
687                 QMessageBox.warning(self, 'Error', 'Invalid Address', 'OK')
688
689     @staticmethod
690     def show_seed_dialog(wallet, parent=None):
691         from electrum import mnemonic
692         if wallet.use_encryption:
693             password = parent.password_dialog()
694             if not password: return
695         else:
696             password = None
697             
698         try:
699             seed = wallet.pw_decode( wallet.seed, password)
700         except:
701             QMessageBox.warning(parent, 'Error', 'Invalid Password', 'OK')
702             return
703
704         msg = "Your wallet generation seed is:\n\n" + seed \
705               + "\n\nPlease keep it in a safe place; if you lose it,\nyou will not be able to restore your wallet.\n\n" \
706               + "Equivalently, your wallet seed can be stored and\nrecovered with the following mnemonic code:\n\n\"" \
707               + ' '.join(mnemonic.mn_encode(seed)) + "\"\n\n\n"
708
709         d = QDialog(None)
710         d.setModal(1)
711         d.setWindowTitle("Seed")
712         d.setMinimumSize(400, 270)
713
714         vbox = QVBoxLayout()
715         hbox = QHBoxLayout()
716         vbox2 = QVBoxLayout()
717         l = QLabel()
718         l.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
719         vbox2.addWidget(l)
720         vbox2.addStretch(1)
721         hbox.addLayout(vbox2)
722         hbox.addWidget(QLabel(msg))
723         vbox.addLayout(hbox)
724
725         hbox = QHBoxLayout()
726         hbox.addStretch(1)
727
728
729         if parent:
730             app = parent.app
731         else:
732             app = QApplication
733
734         b = QPushButton("Copy to Clipboard")
735         b.clicked.connect(lambda: app.clipboard().setText(' '.join(mnemonic.mn_encode(seed))))
736         hbox.addWidget(b)
737         b = QPushButton("View as QR Code")
738         b.clicked.connect(lambda: ElectrumWindow.show_seed_qrcode(seed))
739         hbox.addWidget(b)
740
741         b = QPushButton("OK")
742         b.clicked.connect(d.accept)
743         hbox.addWidget(b)
744         vbox.addLayout(hbox)
745         d.setLayout(vbox)
746         d.exec_()
747
748     @staticmethod
749     def show_seed_qrcode(seed):
750         if not seed: return
751         d = QDialog(None)
752         d.setModal(1)
753         d.setWindowTitle("Seed")
754         d.setMinimumSize(270, 300)
755         vbox = QVBoxLayout()
756         vbox.addWidget(QRCodeWidget(seed))
757         hbox = QHBoxLayout()
758         hbox.addStretch(1)
759         b = QPushButton("OK")
760         hbox.addWidget(b)
761         b.clicked.connect(d.accept)
762
763         vbox.addLayout(hbox)
764         d.setLayout(vbox)
765         d.exec_()
766
767     def show_address_qrcode(self,address):
768         if not address: return
769         d = QDialog(None)
770         d.setModal(1)
771         d.setWindowTitle(address)
772         d.setMinimumSize(270, 350)
773         vbox = QVBoxLayout()
774         qrw = QRCodeWidget(address)
775         vbox.addWidget(qrw)
776
777         hbox = QHBoxLayout()
778         amount_e = QLineEdit()
779         hbox.addWidget(QLabel('Amount'))
780         hbox.addWidget(amount_e)
781         vbox.addLayout(hbox)
782
783         #hbox = QHBoxLayout()
784         #label_e = QLineEdit()
785         #hbox.addWidget(QLabel('Label'))
786         #hbox.addWidget(label_e)
787         #vbox.addLayout(hbox)
788
789         def amount_changed():
790             amount = numbify(amount_e)
791             #label = str( label_e.getText() )
792             if amount is not None:
793                 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
794             else:
795                 qrw.set_addr( address )
796             qrw.repaint()
797
798         def do_save():
799             from electrum import bmp
800             bmp.save_qrcode(qrw.qr, "qrcode.bmp")
801             self.show_message("QR code saved to file 'qrcode.bmp'")
802             
803         amount_e.textChanged.connect( amount_changed )
804
805         hbox = QHBoxLayout()
806         hbox.addStretch(1)
807         b = QPushButton("Save")
808         b.clicked.connect(do_save)
809         hbox.addWidget(b)
810         b = QPushButton("Close")
811         hbox.addWidget(b)
812         b.clicked.connect(d.accept)
813
814         vbox.addLayout(hbox)
815         d.setLayout(vbox)
816         d.exec_()
817
818     def question(self, msg):
819         return QMessageBox.question(self, 'Message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
820
821     def show_message(self, msg):
822         QMessageBox.information(self, 'Message', msg, 'OK')
823
824     def password_dialog(self ):
825         d = QDialog(self)
826         d.setModal(1)
827
828         pw = QLineEdit()
829         pw.setEchoMode(2)
830
831         vbox = QVBoxLayout()
832         msg = 'Please enter your password'
833         vbox.addWidget(QLabel(msg))
834
835         grid = QGridLayout()
836         grid.setSpacing(8)
837         grid.addWidget(QLabel('Password'), 1, 0)
838         grid.addWidget(pw, 1, 1)
839         vbox.addLayout(grid)
840
841         vbox.addLayout(ok_cancel_buttons(d))
842         d.setLayout(vbox) 
843
844         if not d.exec_(): return
845         return unicode(pw.text())
846
847     @staticmethod
848     def change_password_dialog( wallet, parent=None ):
849         d = QDialog(parent)
850         d.setModal(1)
851
852         pw = QLineEdit()
853         pw.setEchoMode(2)
854         new_pw = QLineEdit()
855         new_pw.setEchoMode(2)
856         conf_pw = QLineEdit()
857         conf_pw.setEchoMode(2)
858
859         vbox = QVBoxLayout()
860         if parent:
861             msg = 'Your wallet is encrypted. Use this dialog to change your password.\nTo disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted'
862         else:
863             msg = "Please choose a password to encrypt your wallet keys.\nLeave these fields empty if you want to disable encryption."
864         vbox.addWidget(QLabel(msg))
865
866         grid = QGridLayout()
867         grid.setSpacing(8)
868
869         if wallet.use_encryption:
870             grid.addWidget(QLabel('Password'), 1, 0)
871             grid.addWidget(pw, 1, 1)
872
873         grid.addWidget(QLabel('New Password'), 2, 0)
874         grid.addWidget(new_pw, 2, 1)
875
876         grid.addWidget(QLabel('Confirm Password'), 3, 0)
877         grid.addWidget(conf_pw, 3, 1)
878         vbox.addLayout(grid)
879
880         vbox.addLayout(ok_cancel_buttons(d))
881         d.setLayout(vbox) 
882
883         if not d.exec_(): return
884
885         password = unicode(pw.text()) if wallet.use_encryption else None
886         new_password = unicode(new_pw.text())
887         new_password2 = unicode(conf_pw.text())
888
889         try:
890             seed = wallet.pw_decode( wallet.seed, password)
891         except:
892             QMessageBox.warning(parent, 'Error', 'Incorrect Password', 'OK')
893             return
894
895         if new_password != new_password2:
896             QMessageBox.warning(parent, 'Error', 'Passwords do not match', 'OK')
897             return
898
899         wallet.update_password(seed, new_password)
900
901     @staticmethod
902     def seed_dialog(wallet, parent=None):
903         d = QDialog(parent)
904         d.setModal(1)
905
906         vbox = QVBoxLayout()
907         msg = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
908         vbox.addWidget(QLabel(msg))
909
910         grid = QGridLayout()
911         grid.setSpacing(8)
912
913         seed_e = QLineEdit()
914         grid.addWidget(QLabel('Seed or mnemonic'), 1, 0)
915         grid.addWidget(seed_e, 1, 1)
916
917         gap_e = QLineEdit()
918         gap_e.setText("5")
919         grid.addWidget(QLabel('Gap limit'), 2, 0)
920         grid.addWidget(gap_e, 2, 1)
921         gap_e.textChanged.connect(lambda: numbify(gap_e,True))
922         vbox.addLayout(grid)
923
924         vbox.addLayout(ok_cancel_buttons(d))
925         d.setLayout(vbox) 
926
927         if not d.exec_(): return
928
929         try:
930             gap = int(unicode(gap_e.text()))
931         except:
932             QMessageBox.warning(None, 'Error', 'error', 'OK')
933             sys.exit(0)
934
935         try:
936             seed = unicode(seed_e.text())
937             seed.decode('hex')
938         except:
939             from electrum import mnemonic
940             print "not hex, trying decode"
941             try:
942                 seed = mnemonic.mn_decode( seed.split(' ') )
943             except:
944                 QMessageBox.warning(None, 'Error', 'I cannot decode this', 'OK')
945                 sys.exit(0)
946         if not seed:
947             QMessageBox.warning(None, 'Error', 'no seed', 'OK')
948             sys.exit(0)
949         
950         wallet.seed = str(seed)
951         #print repr(wallet.seed)
952         wallet.gap_limit = gap
953         return True
954
955
956     def settings_dialog(self):
957         d = QDialog(self)
958         d.setModal(1)
959
960         vbox = QVBoxLayout()
961
962         msg = 'Here are the settings of your wallet.'
963         vbox.addWidget(QLabel(msg))
964
965         grid = QGridLayout()
966         grid.setSpacing(8)
967         vbox.addLayout(grid)
968
969         fee_e = QLineEdit()
970         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
971         grid.addWidget(QLabel('Fee per tx. input'), 2, 0)
972         grid.addWidget(fee_e, 2, 1)
973         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
974
975         nz_e = QLineEdit()
976         nz_e.setText("%d"% self.wallet.num_zeros)
977         grid.addWidget(QLabel('Zeros displayed after decimal point'), 3, 0)
978         grid.addWidget(nz_e, 3, 1)
979         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
980
981         vbox.addLayout(ok_cancel_buttons(d))
982         d.setLayout(vbox) 
983
984         if not d.exec_(): return
985
986         fee = unicode(fee_e.text())
987         try:
988             fee = int( 100000000 * Decimal(fee) )
989         except:
990             QMessageBox.warning(self, 'Error', 'Invalid value:%s'%fee, 'OK')
991             return
992
993         if self.wallet.fee != fee:
994             self.wallet.fee = fee
995             self.wallet.save()
996         
997         nz = unicode(nz_e.text())
998         try:
999             nz = int( nz )
1000             if nz>8: nz=8
1001         except:
1002             QMessageBox.warning(self, 'Error', 'Invalid value:%s'%nz, 'OK')
1003             return
1004
1005         if self.wallet.num_zeros != nz:
1006             self.wallet.num_zeros = nz
1007             self.update_history_tab()
1008             self.update_receive_tab()
1009             self.wallet.save()
1010
1011     @staticmethod 
1012     def network_dialog(wallet, parent=None):
1013         interface = wallet.interface
1014         if parent:
1015             if interface.is_connected:
1016                 status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks)
1017             else:
1018                 status = "Not connected"
1019             server = wallet.server
1020         else:
1021             import random
1022             status = "Please choose a server."
1023             server = random.choice( DEFAULT_SERVERS )
1024
1025         if not wallet.interface.servers:
1026             servers_list = []
1027             for x in DEFAULT_SERVERS:
1028                 h,port,protocol = x.split(':')
1029                 servers_list.append( (h,[(protocol,port)] ) )
1030         else:
1031             servers_list = wallet.interface.servers
1032             
1033         plist = {}
1034         for item in servers_list:
1035             host, pp = item
1036             z = {}
1037             for item2 in pp:
1038                 protocol, port = item2
1039                 z[protocol] = port
1040             plist[host] = z
1041
1042         d = QDialog(parent)
1043         d.setModal(1)
1044         d.setWindowTitle('Server')
1045         d.setMinimumSize(375, 20)
1046
1047         vbox = QVBoxLayout()
1048         vbox.setSpacing(20)
1049
1050         hbox = QHBoxLayout()
1051         l = QLabel()
1052         l.setPixmap(QPixmap(":icons/network.png"))
1053         hbox.addWidget(l)        
1054         hbox.addWidget(QLabel(status))
1055
1056         vbox.addLayout(hbox)
1057
1058         hbox = QHBoxLayout()
1059         host_line = QLineEdit()
1060         host_line.setText(server)
1061         hbox.addWidget(QLabel('Connect to:'))
1062         hbox.addWidget(host_line)
1063         vbox.addLayout(hbox)
1064
1065         hbox = QHBoxLayout()
1066
1067         buttonGroup = QGroupBox("protocol")
1068         radio1 = QRadioButton("tcp", buttonGroup)
1069         radio2 = QRadioButton("http", buttonGroup)
1070
1071         def current_line():
1072             return unicode(host_line.text()).split(':')
1073             
1074         def set_button(protocol):
1075             if protocol == 't':
1076                 radio1.setChecked(1)
1077             elif protocol == 'h':
1078                 radio2.setChecked(1)
1079
1080         def set_protocol(protocol):
1081             host = current_line()[0]
1082             pp = plist[host]
1083             if protocol not in pp.keys():
1084                 protocol = pp.keys()[0]
1085                 set_button(protocol)
1086             port = pp[protocol]
1087             host_line.setText( host + ':' + port + ':' + protocol)
1088
1089         radio1.clicked.connect(lambda x: set_protocol('t') )
1090         radio2.clicked.connect(lambda x: set_protocol('h') )
1091
1092         set_button(current_line()[2])
1093
1094         hbox.addWidget(QLabel('Protocol:'))
1095         hbox.addWidget(radio1)
1096         hbox.addWidget(radio2)
1097
1098         vbox.addLayout(hbox)
1099
1100         if wallet.interface.servers:
1101             label = 'Active Servers'
1102         else:
1103             label = 'Default Servers'
1104         
1105         servers_list_widget = QTreeWidget(parent)
1106         servers_list_widget.setHeaderLabels( [ label ] )
1107         servers_list_widget.setMaximumHeight(150)
1108         for host in plist.keys():
1109             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ host ] ))
1110
1111         def do_set_line(x):
1112             host = unicode(x.text(0))
1113             pp = plist[host]
1114             if 't' in pp.keys():
1115                 protocol = 't'
1116             else:
1117                 protocol = pp.keys()[0]
1118             port = pp[protocol]
1119             host_line.setText( host + ':' + port + ':' + protocol)
1120             set_button(protocol)
1121
1122         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line)
1123         vbox.addWidget(servers_list_widget)
1124
1125         vbox.addLayout(ok_cancel_buttons(d))
1126         d.setLayout(vbox) 
1127
1128         if not d.exec_(): return
1129         server = unicode( host_line.text() )
1130
1131         try:
1132             wallet.set_server(server)
1133         except:
1134             QMessageBox.information(None, 'Error', 'error', 'OK')
1135             if parent == None:
1136                 sys.exit(1)
1137             else:
1138                 return
1139
1140         return True
1141
1142
1143
1144 class ElectrumGui():
1145
1146     def __init__(self, wallet):
1147         self.wallet = wallet
1148         self.app = QApplication(sys.argv)
1149
1150     def waiting_dialog(self):
1151
1152         s = Timer()
1153         s.start()
1154         w = QDialog()
1155         w.resize(200, 70)
1156         w.setWindowTitle('Electrum')
1157         l = QLabel('')
1158         vbox = QVBoxLayout()
1159         vbox.addWidget(l)
1160         w.setLayout(vbox)
1161         w.show()
1162         def f():
1163             if self.wallet.up_to_date: w.close()
1164             else:
1165                 l.setText("Please wait...\nGenerating addresses: %d"%len(self.wallet.all_addresses()))
1166                 pass
1167         w.connect(s, QtCore.SIGNAL('timersignal'), f)
1168         self.wallet.interface.poke()
1169         w.exec_()
1170         w.destroy()
1171
1172
1173     def restore_or_create(self):
1174
1175         msg = "Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?"
1176         r = QMessageBox.question(None, 'Message', msg, 'create', 'restore', 'cancel', 0, 2)
1177         if r==2: return False
1178         
1179         is_recovery = (r==1)
1180         wallet = self.wallet
1181         # ask for the server.
1182         if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1183
1184         if not is_recovery:
1185             wallet.new_seed(None)
1186             wallet.init_mpk( wallet.seed )
1187             wallet.up_to_date_event.clear()
1188             wallet.up_to_date = False
1189             self.waiting_dialog()
1190             # run a dialog indicating the seed, ask the user to remember it
1191             ElectrumWindow.show_seed_dialog(wallet)
1192             #ask for password
1193             ElectrumWindow.change_password_dialog(wallet)
1194         else:
1195             # ask for seed and gap.
1196             if not ElectrumWindow.seed_dialog( wallet ): return False
1197             wallet.init_mpk( wallet.seed )
1198             wallet.up_to_date_event.clear()
1199             wallet.up_to_date = False
1200             self.waiting_dialog()
1201             if wallet.is_found():
1202                 # history and addressbook
1203                 wallet.update_tx_history()
1204                 wallet.fill_addressbook()
1205                 print "recovery successful"
1206                 wallet.save()
1207             else:
1208                 QMessageBox.information(None, 'Message', "No transactions found for this seed", 'OK')
1209
1210         wallet.save()
1211         return True
1212
1213     def main(self,url):
1214         s = Timer()
1215         s.start()
1216         w = ElectrumWindow(self.wallet)
1217         if url: w.set_url(url)
1218         w.app = self.app
1219         w.connect_slots(s)
1220         w.update_wallet()
1221
1222         self.app.exec_()