use timer signal to render qr code
[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 from i18n import _
21 from util import print_error
22
23 try:
24     import PyQt4
25 except:
26     sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
27
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31 import PyQt4.QtGui as QtGui
32 from interface import DEFAULT_SERVERS
33
34 try:
35     import icons_rc
36 except:
37     sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
38
39 from wallet import format_satoshis
40 import bmp, mnemonic, pyqrnative, qrscanner
41
42 from decimal import Decimal
43
44 import platform
45
46 if platform.system() == 'Windows':
47     MONOSPACE_FONT = 'Lucida Console'
48 elif platform.system() == 'Darwin':
49     MONOSPACE_FONT = 'Monaco'
50 else:
51     MONOSPACE_FONT = 'monospace'
52
53 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
54
55 def numbify(entry, is_int = False):
56     text = unicode(entry.text()).strip()
57     pos = entry.cursorPosition()
58     chars = '0123456789'
59     if not is_int: chars +='.'
60     s = ''.join([i for i in text if i in chars])
61     if not is_int:
62         if '.' in s:
63             p = s.find('.')
64             s = s.replace('.','')
65             s = s[:p] + '.' + s[p:p+8]
66         try:
67             amount = int( Decimal(s) * 100000000 )
68         except:
69             amount = None
70     else:
71         try:
72             amount = int( s )
73         except:
74             amount = None
75     entry.setText(s)
76     entry.setCursorPosition(pos)
77     return amount
78
79
80 class Timer(QtCore.QThread):
81     def run(self):
82         while True:
83             self.emit(QtCore.SIGNAL('timersignal'))
84             time.sleep(0.5)
85
86 class HelpButton(QPushButton):
87     def __init__(self, text):
88         QPushButton.__init__(self, '?')
89         self.setFocusPolicy(Qt.NoFocus)
90         self.setFixedWidth(20)
91         self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
92
93
94 class EnterButton(QPushButton):
95     def __init__(self, text, func):
96         QPushButton.__init__(self, text)
97         self.func = func
98         self.clicked.connect(func)
99
100     def keyPressEvent(self, e):
101         if e.key() == QtCore.Qt.Key_Return:
102             apply(self.func,())
103
104 class MyTreeWidget(QTreeWidget):
105     def __init__(self, parent):
106         QTreeWidget.__init__(self, parent)
107         def ddfr(item):
108             if not item: return
109             for i in range(0,self.viewport().height()/5):
110                 if self.itemAt(QPoint(0,i*5)) == item:
111                     break
112             else:
113                 return
114             for j in range(0,30):
115                 if self.itemAt(QPoint(0,i*5 + j)) != item:
116                     break
117             self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
118
119         self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
120         
121
122
123
124 class StatusBarButton(QPushButton):
125     def __init__(self, icon, tooltip, func):
126         QPushButton.__init__(self, icon, '')
127         self.setToolTip(tooltip)
128         self.setFlat(True)
129         self.setMaximumWidth(25)
130         self.clicked.connect(func)
131         self.func = func
132
133     def keyPressEvent(self, e):
134         if e.key() == QtCore.Qt.Key_Return:
135             apply(self.func,())
136
137
138 class QRCodeWidget(QWidget):
139
140     def __init__(self):
141         QWidget.__init__(self)
142         self.setMinimumSize(210, 210)
143         self.addr = None
144         self.qr = None
145
146     def set_addr(self, addr):
147         if self.addr != addr:
148             self.addr = addr
149             self.qr = None
150
151     def paintEvent(self, e):
152         if not self.addr: return
153         if not self.qr:
154             self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
155             self.qr.addData(self.addr)
156             self.qr.make()
157         
158         qp = QtGui.QPainter()
159         qp.begin(self)
160         boxsize = 6
161         size = self.qr.getModuleCount()*boxsize
162         k = self.qr.getModuleCount()
163         black = QColor(0, 0, 0, 255)
164         white = QColor(255, 255, 255, 255)
165         for r in range(k):
166             for c in range(k):
167                 if self.qr.isDark(r, c):
168                     qp.setBrush(black)
169                     qp.setPen(black)
170                 else:
171                     qp.setBrush(white)
172                     qp.setPen(white)
173                 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
174         qp.end()
175         
176
177
178 class QR_Window(QWidget):
179
180     def __init__(self):
181         QWidget.__init__(self)
182         self.setWindowTitle('Electrum - Invoice')
183         self.setMinimumSize(800, 250)
184         self.address = ''
185         self.labe = ''
186         self.amount = 0
187         self.setFocusPolicy(QtCore.Qt.NoFocus)
188
189         main_box = QHBoxLayout()
190         
191         self.qrw = QRCodeWidget()
192         main_box.addWidget(self.qrw)
193
194         vbox = QVBoxLayout()
195         main_box.addLayout(vbox)
196
197         main_box.addStretch(1)
198
199         self.address_label = QLabel("")
200         self.address_label.setFont(QFont(MONOSPACE_FONT))
201         vbox.addWidget(self.address_label)
202
203         self.label_label = QLabel("")
204         vbox.addWidget(self.label_label)
205
206         self.amount_label = QLabel("")
207         vbox.addWidget(self.amount_label)
208
209         vbox.addStretch(1)
210
211         self.setLayout(main_box)
212
213     def do_save(self):
214         self.filename = "qrcode.bmp"
215         bmp.save_qrcode(self.qrw.qr, self.filename)
216
217     def set_content(self, addr, label, amount):
218         self.address = addr
219         address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
220         self.address_label.setText(address_text)
221
222         self.amount = amount
223         amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
224         self.amount_label.setText(amount_text)
225
226         self.label = label
227         label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
228         self.label_label.setText(label_text)
229
230         msg = 'bitcoin:'+self.address
231         if self.amount is not None:
232             msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
233             if self.label is not None:
234                 msg += '&label=%s'%(self.label)
235         elif self.label is not None:
236             msg += '?label=%s'%(self.label)
237             
238         self.qrw.set_addr( msg )
239
240             
241
242
243 def waiting_dialog(f):
244
245     s = Timer()
246     s.start()
247     w = QDialog()
248     w.resize(200, 70)
249     w.setWindowTitle('Electrum')
250     l = QLabel('')
251     vbox = QVBoxLayout()
252     vbox.addWidget(l)
253     w.setLayout(vbox)
254     w.show()
255     def ff():
256         s = f()
257         if s: l.setText(s)
258         else: w.close()
259     w.connect(s, QtCore.SIGNAL('timersignal'), ff)
260     w.exec_()
261     w.destroy()
262
263
264 def ok_cancel_buttons(dialog):
265     hbox = QHBoxLayout()
266     hbox.addStretch(1)
267     b = QPushButton("OK")
268     hbox.addWidget(b)
269     b.clicked.connect(dialog.accept)
270     b = QPushButton("Cancel")
271     hbox.addWidget(b)
272     b.clicked.connect(dialog.reject)
273     return hbox
274
275
276 class ElectrumWindow(QMainWindow):
277
278     def __init__(self, wallet, config):
279         QMainWindow.__init__(self)
280         self.wallet = wallet
281         self.config = config
282         self.wallet.interface.register_callback('updated', self.update_callback)
283         self.wallet.interface.register_callback('connected', self.update_callback)
284         self.wallet.interface.register_callback('disconnected', self.update_callback)
285         self.wallet.interface.register_callback('disconnecting', self.update_callback)
286
287         self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
288
289         self.qr_window = None
290         self.funds_error = False
291         self.completions = QStringListModel()
292
293         self.tabs = tabs = QTabWidget(self)
294         tabs.addTab(self.create_history_tab(), _('History') )
295         if self.wallet.seed:
296             tabs.addTab(self.create_send_tab(), _('Send') )
297         tabs.addTab(self.create_receive_tab(), _('Receive') )
298         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
299         tabs.addTab(self.create_wall_tab(), _('Wall') )
300         tabs.setMinimumSize(600, 400)
301         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
302         self.setCentralWidget(tabs)
303         self.create_status_bar()
304         self.toggle_QR_window(self.receive_tab_mode == 2)
305
306         g = self.config.get("winpos-qt",[100, 100, 840, 400])
307         self.setGeometry(g[0], g[1], g[2], g[3])
308         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.config.path
309         if not self.wallet.seed: title += ' [seedless]'
310         self.setWindowTitle( title )
311
312         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
313         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
314         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
315         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
316         
317         self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
318         #self.connect(self, SIGNAL('editamount'), self.edit_amount)
319         self.history_list.setFocus(True)
320
321         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
322         if platform.system() == 'Windows':
323             n = 3 if self.wallet.seed else 2
324             tabs.setCurrentIndex (n)
325             tabs.setCurrentIndex (0)
326
327     def close(self):
328         QMainWindow.close(self)
329         if self.qr_window: 
330             self.qr_window.close()
331             self.qr_window = None
332
333     def connect_slots(self, sender):
334         if self.wallet.seed:
335             self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
336             self.previous_payto_e=''
337
338     def timer_actions(self):
339         if self.qr_window:
340             self.qr_window.qrw.update()
341             
342         if self.payto_e.hasFocus():
343             return
344         r = unicode( self.payto_e.text() )
345         if r != self.previous_payto_e:
346             self.previous_payto_e = r
347             r = r.strip()
348             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
349                 try:
350                     to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
351                 except:
352                     return
353                 if to_address:
354                     s = r + '  <' + to_address + '>'
355                     self.payto_e.setText(s)
356
357
358     def update_callback(self):
359         self.emit(QtCore.SIGNAL('updatesignal'))
360
361     def update_wallet(self):
362         if self.wallet.interface and self.wallet.interface.is_connected:
363             if not self.wallet.up_to_date:
364                 text = _( "Synchronizing..." )
365                 icon = QIcon(":icons/status_waiting.png")
366             else:
367                 c, u = self.wallet.get_balance()
368                 text =  _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
369                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
370                 icon = QIcon(":icons/status_connected.png")
371         else:
372             text = _( "Not connected" )
373             icon = QIcon(":icons/status_disconnected.png")
374
375         if self.funds_error:
376             text = _( "Not enough funds" )
377
378         self.statusBar().showMessage(text)
379         self.status_button.setIcon( icon )
380
381         if self.wallet.up_to_date or not self.wallet.interface.is_connected:
382             self.textbox.setText( self.wallet.banner )
383             self.update_history_tab()
384             self.update_receive_tab()
385             self.update_contacts_tab()
386             self.update_completions()
387
388
389     def create_history_tab(self):
390         self.history_list = l = MyTreeWidget(self)
391         l.setColumnCount(5)
392         l.setColumnWidth(0, 40) 
393         l.setColumnWidth(1, 140) 
394         l.setColumnWidth(2, 350) 
395         l.setColumnWidth(3, 140) 
396         l.setColumnWidth(4, 140) 
397         l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
398         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
399         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
400
401         l.setContextMenuPolicy(Qt.CustomContextMenu)
402         l.customContextMenuRequested.connect(self.create_history_menu)
403         return l
404
405
406     def create_history_menu(self, position):
407         self.history_list.selectedIndexes() 
408         item = self.history_list.currentItem()
409         if not item: return
410         tx_hash = str(item.toolTip(0))
411         if not tx_hash: return
412         menu = QMenu()
413         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
414         menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
415         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
416         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
417
418
419     def tx_details(self, tx_hash):
420         tx_details = self.wallet.get_tx_details(tx_hash)
421         QMessageBox.information(self, 'Details', tx_details, 'OK')
422
423
424     def tx_label_clicked(self, item, column):
425         if column==2 and item.isSelected():
426             tx_hash = str(item.toolTip(0))
427             self.is_edit=True
428             #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
429             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
430             self.history_list.editItem( item, column )
431             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
432             self.is_edit=False
433
434     def tx_label_changed(self, item, column):
435         if self.is_edit: 
436             return
437         self.is_edit=True
438         tx_hash = str(item.toolTip(0))
439         tx = self.wallet.transactions.get(tx_hash)
440         s = self.wallet.labels.get(tx_hash)
441         text = unicode( item.text(2) )
442         if text: 
443             self.wallet.labels[tx_hash] = text
444             item.setForeground(2, QBrush(QColor('black')))
445         else:
446             if s: self.wallet.labels.pop(tx_hash)
447             text = self.wallet.get_default_label(tx_hash)
448             item.setText(2, text)
449             item.setForeground(2, QBrush(QColor('gray')))
450         self.is_edit=False
451
452
453     def edit_label(self, is_recv):
454         l = self.receive_list if is_recv else self.contacts_list
455         c = 2 if is_recv else 1
456         item = l.currentItem()
457         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
458         l.editItem( item, c )
459         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
460
461     def edit_amount(self):
462         l = self.receive_list
463         item = l.currentItem()
464         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
465         l.editItem( item, 3 )
466         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
467
468
469     def address_label_clicked(self, item, column, l, column_addr, column_label):
470         if column == column_label and item.isSelected():
471             addr = unicode( item.text(column_addr) )
472             label = unicode( item.text(column_label) )
473             if label in self.wallet.aliases.keys():
474                 return
475             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
476             l.editItem( item, column )
477             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
478
479
480     def address_label_changed(self, item, column, l, column_addr, column_label):
481
482         if column == column_label:
483             addr = unicode( item.text(column_addr) )
484             text = unicode( item.text(column_label) )
485             changed = False
486
487             if text:
488                 if text not in self.wallet.aliases.keys():
489                     old_addr = self.wallet.labels.get(text)
490                     if old_addr != addr:
491                         self.wallet.labels[addr] = text
492                         changed = True
493                 else:
494                     print_error("Error: This is one of your aliases")
495                     label = self.wallet.labels.get(addr,'')
496                     item.setText(column_label, QString(label))
497             else:
498                 s = self.wallet.labels.get(addr)
499                 if s: 
500                     self.wallet.labels.pop(addr)
501                     changed = True
502
503             if changed:
504                 self.update_history_tab()
505                 self.update_completions()
506                 
507             self.recv_changed(item)
508
509         if column == 3:
510             address = unicode( item.text(column_addr) )
511             text = unicode( item.text(3) )
512             try:
513                 index = self.wallet.addresses.index(address)
514             except:
515                 return
516
517             try:
518                 amount = int( Decimal( unicode(text)) * 100000000 )
519                 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
520             except:
521                 amount = self.wallet.requested_amounts.get(address)
522                 if amount: 
523                     item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
524                 else:
525                     item.setText(3,"")
526                 return
527
528             self.wallet.requested_amounts[address] = amount
529
530             label = self.wallet.labels.get(address)
531             if not label: 
532                 label = 'invoice %04d'%(index+1)
533
534             self.update_receive_item(self.receive_list.currentItem())
535             if self.qr_window:
536                 self.qr_window.set_content( address, label, amount )
537
538
539     def recv_changed(self, a):
540         "current item changed"
541         if a is not None and self.qr_window and self.qr_window.isVisible():
542             address = str(a.text(1))
543             label = self.wallet.labels.get(address)
544             amount = self.wallet.requested_amounts.get(address)
545             self.qr_window.set_content( address, label, amount )
546
547
548     def update_history_tab(self):
549
550         self.history_list.clear()
551         for item in self.wallet.get_tx_history():
552             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
553             if conf:
554                 try:
555                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
556                 except:
557                     time_str = "unknown"
558                 if conf == -1:
559                     icon = None
560                 if conf == 0:
561                     icon = QIcon(":icons/unconfirmed.png")
562                 elif conf < 6:
563                     icon = QIcon(":icons/clock%d.png"%conf)
564                 else:
565                     icon = QIcon(":icons/confirmed.png")
566             else:
567                 time_str = 'pending'
568                 icon = QIcon(":icons/unconfirmed.png")
569
570             if value is not None:
571                 v_str = format_satoshis(value, True, self.wallet.num_zeros)
572             else:
573                 v_str = '--'
574
575             balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
576             
577             if tx_hash:
578                 label, is_default_label = self.wallet.get_label(tx_hash)
579             else:
580                 label = _('Pruned transaction outputs')
581                 is_default_label = False
582
583             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
584             item.setFont(2, QFont(MONOSPACE_FONT))
585             item.setFont(3, QFont(MONOSPACE_FONT))
586             item.setFont(4, QFont(MONOSPACE_FONT))
587             if tx_hash:
588                 item.setToolTip(0, tx_hash)
589             if is_default_label:
590                 item.setForeground(2, QBrush(QColor('grey')))
591
592             item.setIcon(0, icon)
593             self.history_list.insertTopLevelItem(0,item)
594             
595
596         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
597
598
599     def create_send_tab(self):
600         w = QWidget()
601
602         grid = QGridLayout()
603         grid.setSpacing(8)
604         grid.setColumnMinimumWidth(3,300)
605         grid.setColumnStretch(5,1)
606
607         self.payto_e = QLineEdit()
608         grid.addWidget(QLabel(_('Pay to')), 1, 0)
609         grid.addWidget(self.payto_e, 1, 1, 1, 3)
610         
611         def fill_from_qr():
612             qrcode = qrscanner.scan_qr()
613             if 'address' in qrcode:
614                 self.payto_e.setText(qrcode['address'])
615             if 'amount' in qrcode:
616                 self.amount_e.setText(str(qrcode['amount']))
617             if 'label' in qrcode:
618                 self.message_e.setText(qrcode['label'])
619             if 'message' in qrcode:
620                 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
621                 
622
623         if qrscanner.is_available():
624             b = QPushButton(_("Scan QR code"))
625             b.clicked.connect(fill_from_qr)
626             grid.addWidget(b, 1, 5)
627     
628         grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
629
630         completer = QCompleter()
631         completer.setCaseSensitivity(False)
632         self.payto_e.setCompleter(completer)
633         completer.setModel(self.completions)
634
635         self.message_e = QLineEdit()
636         grid.addWidget(QLabel(_('Description')), 2, 0)
637         grid.addWidget(self.message_e, 2, 1, 1, 3)
638         grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
639
640         self.amount_e = QLineEdit()
641         grid.addWidget(QLabel(_('Amount')), 3, 0)
642         grid.addWidget(self.amount_e, 3, 1, 1, 2)
643         grid.addWidget(HelpButton(
644                 _('Amount to be sent.') + '\n\n' \
645                     + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.')), 3, 3)
646         
647         self.fee_e = QLineEdit()
648         grid.addWidget(QLabel(_('Fee')), 4, 0)
649         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
650         grid.addWidget(HelpButton(
651                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
652                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
653                     + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
654         
655         b = EnterButton(_("Send"), self.do_send)
656         grid.addWidget(b, 6, 1)
657
658         b = EnterButton(_("Clear"),self.do_clear)
659         grid.addWidget(b, 6, 2)
660
661         self.payto_sig = QLabel('')
662         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
663
664         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
665         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
666         w.setLayout(grid) 
667
668         w2 = QWidget()
669         vbox = QVBoxLayout()
670         vbox.addWidget(w)
671         vbox.addStretch(1)
672         w2.setLayout(vbox)
673
674         def entry_changed( is_fee ):
675             self.funds_error = False
676             amount = numbify(self.amount_e)
677             fee = numbify(self.fee_e)
678             if not is_fee: fee = None
679             if amount is None:
680                 return
681             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
682             if not is_fee:
683                 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
684             if inputs:
685                 palette = QPalette()
686                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
687             else:
688                 palette = QPalette()
689                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
690                 self.funds_error = True
691             self.amount_e.setPalette(palette)
692             self.fee_e.setPalette(palette)
693
694         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
695         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
696
697         return w2
698
699
700     def update_completions(self):
701         l = []
702         for addr,label in self.wallet.labels.items():
703             if addr in self.wallet.addressbook:
704                 l.append( label + '  <' + addr + '>')
705         l = l + self.wallet.aliases.keys()
706
707         self.completions.setStringList(l)
708
709
710
711     def do_send(self):
712
713         label = unicode( self.message_e.text() )
714         r = unicode( self.payto_e.text() )
715         r = r.strip()
716
717         # alias
718         m1 = re.match(ALIAS_REGEXP, r)
719         # label or alias, with address in brackets
720         m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
721         
722         if m1:
723             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
724             if not to_address:
725                 return
726         elif m2:
727             to_address = m2.group(2)
728         else:
729             to_address = r
730
731         if not self.wallet.is_valid(to_address):
732             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
733             return
734
735         try:
736             amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
737         except:
738             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
739             return
740         try:
741             fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
742         except:
743             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
744             return
745
746         if self.wallet.use_encryption:
747             password = self.password_dialog()
748             if not password:
749                 return
750         else:
751             password = None
752
753         try:
754             tx = self.wallet.mktx( to_address, amount, label, password, fee)
755         except BaseException, e:
756             self.show_message(str(e))
757             return
758             
759         h = self.wallet.send_tx(tx)
760         waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
761         status, msg = self.wallet.receive_tx( h )
762
763         if status:
764             QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
765             self.do_clear()
766             self.update_contacts_tab()
767         else:
768             QMessageBox.warning(self, _('Error'), msg, _('OK'))
769
770
771     def set_url(self, url):
772         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
773         self.tabs.setCurrentIndex(1)
774         label = self.wallet.labels.get(payto)
775         m_addr = label + '  <'+ payto+'>' if label else payto
776         self.payto_e.setText(m_addr)
777
778         self.message_e.setText(message)
779         self.amount_e.setText(amount)
780         if identity:
781             self.set_frozen(self.payto_e,True)
782             self.set_frozen(self.amount_e,True)
783             self.set_frozen(self.message_e,True)
784             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
785         else:
786             self.payto_sig.setVisible(False)
787
788     def do_clear(self):
789         self.payto_sig.setVisible(False)
790         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
791             e.setText('')
792             self.set_frozen(e,False)
793
794     def set_frozen(self,entry,frozen):
795         if frozen:
796             entry.setReadOnly(True)
797             entry.setFrame(False)
798             palette = QPalette()
799             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
800             entry.setPalette(palette)
801         else:
802             entry.setReadOnly(False)
803             entry.setFrame(True)
804             palette = QPalette()
805             palette.setColor(entry.backgroundRole(), QColor('white'))
806             entry.setPalette(palette)
807
808
809     def toggle_freeze(self,addr):
810         if not addr: return
811         if addr in self.wallet.frozen_addresses:
812             self.wallet.unfreeze(addr)
813         else:
814             self.wallet.freeze(addr)
815         self.update_receive_tab()
816
817     def toggle_priority(self,addr):
818         if not addr: return
819         if addr in self.wallet.prioritized_addresses:
820             self.wallet.unprioritize(addr)
821         else:
822             self.wallet.prioritize(addr)
823         self.update_receive_tab()
824
825
826     def create_list_tab(self, headers):
827         "generic tab creation method"
828         l = MyTreeWidget(self)
829         l.setColumnCount( len(headers) )
830         l.setHeaderLabels( headers )
831
832         w = QWidget()
833         vbox = QVBoxLayout()
834         w.setLayout(vbox)
835
836         vbox.setMargin(0)
837         vbox.setSpacing(0)
838         vbox.addWidget(l)
839         buttons = QWidget()
840         vbox.addWidget(buttons)
841
842         hbox = QHBoxLayout()
843         hbox.setMargin(0)
844         hbox.setSpacing(0)
845         buttons.setLayout(hbox)
846
847         return l,w,hbox
848
849
850     def create_receive_tab(self):
851         l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
852         l.setContextMenuPolicy(Qt.CustomContextMenu)
853         l.customContextMenuRequested.connect(self.create_receive_menu)
854         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
855         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
856         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
857         self.receive_list = l
858         self.receive_buttons_hbox = hbox
859         view_combo = QComboBox()
860         view_combo.addItems(['Simple View', 'Detailed View', 'Point of Sale'])
861         view_combo.setCurrentIndex(self.receive_tab_mode)
862         hbox.addWidget(view_combo)
863         view_combo.currentIndexChanged.connect(self.receive_tab_set_mode)
864         hbox.addStretch(1)
865         return w
866
867
868     def print_qr(self):
869         if self.qr_window:
870             self.qr_window.do_save()
871             self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
872
873
874     def receive_tab_set_mode(self, i):
875         self.receive_tab_mode = i
876         self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
877         self.wallet.save()
878         self.update_receive_tab()
879         self.toggle_QR_window(self.receive_tab_mode == 2)
880
881
882     def create_contacts_tab(self):
883         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
884         l.setContextMenuPolicy(Qt.CustomContextMenu)
885         l.customContextMenuRequested.connect(self.create_contact_menu)
886         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
887         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
888         self.contacts_list = l
889         self.contacts_buttons_hbox = hbox
890         hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
891         hbox.addStretch(1)
892         return w
893
894
895     def create_receive_menu(self, position):
896         # fixme: this function apparently has a side effect.
897         # if it is not called the menu pops up several times
898         #self.receive_list.selectedIndexes() 
899
900         item = self.receive_list.itemAt(position)
901         if not item: return
902         addr = unicode(item.text(1))
903         menu = QMenu()
904         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
905         if self.receive_tab_mode == 2:
906             menu.addAction(_("Request amount"), lambda: self.edit_amount())
907             menu.addAction(_("Print QR"), self.print_qr)
908         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
909         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
910
911         t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
912         menu.addAction(t, lambda: self.toggle_freeze(addr))
913         t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
914         menu.addAction(t, lambda: self.toggle_priority(addr))
915         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
916
917
918     def payto(self, x, is_alias):
919         if not x: return
920         if is_alias:
921             label = x
922             m_addr = label
923         else:
924             addr = x
925             label = self.wallet.labels.get(addr)
926             m_addr = label + '  <' + addr + '>' if label else addr
927         self.tabs.setCurrentIndex(1)
928         self.payto_e.setText(m_addr)
929         self.amount_e.setFocus()
930
931     def delete_contact(self, x, is_alias):
932         if self.question("Do you want to remove %s from your list of contacts?"%x):
933             if not is_alias and x in self.wallet.addressbook:
934                 self.wallet.addressbook.remove(x)
935                 if x in self.wallet.labels.keys():
936                     self.wallet.labels.pop(x)
937             elif is_alias and x in self.wallet.aliases:
938                 self.wallet.aliases.pop(x)
939             self.update_history_tab()
940             self.update_contacts_tab()
941             self.update_completions()
942
943     def create_contact_menu(self, position):
944         # fixme: this function apparently has a side effect.
945         # if it is not called the menu pops up several times
946         #self.contacts_list.selectedIndexes() 
947
948         item = self.contacts_list.itemAt(position)
949         if not item: return
950         addr = unicode(item.text(0))
951         label = unicode(item.text(1))
952         is_alias = label in self.wallet.aliases.keys()
953         x = label if is_alias else addr
954         menu = QMenu()
955         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
956         menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
957         menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
958         if not is_alias:
959             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
960         else:
961             menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
962         menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
963         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
964
965
966     def update_receive_item(self, item):
967         address = str( item.data(1,0).toString() )
968
969         flags = self.wallet.get_address_flags(address)
970         item.setData(0,0,flags)
971
972         label = self.wallet.labels.get(address,'')
973         item.setData(2,0,label)
974
975         amount = self.wallet.requested_amounts.get(address,None)
976         amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None  else ""
977         item.setData(3,0,amount_str)
978         
979         c, u = self.wallet.get_addr_balance(address)
980         balance = format_satoshis( c + u, False, self.wallet.num_zeros )
981         item.setData(4,0,balance)
982
983         if address in self.wallet.frozen_addresses: 
984             item.setBackgroundColor(1, QColor('lightblue'))
985         elif address in self.wallet.prioritized_addresses: 
986             item.setBackgroundColor(1, QColor('lightgreen'))
987         
988
989     def update_receive_tab(self):
990         l = self.receive_list
991         
992         l.clear()
993         l.setColumnHidden(0, not self.receive_tab_mode == 1)
994         l.setColumnHidden(3, not self.receive_tab_mode == 2)
995         l.setColumnHidden(4, self.receive_tab_mode == 0)
996         l.setColumnHidden(5, not self.receive_tab_mode == 1)
997         l.setColumnWidth(0, 50)
998         l.setColumnWidth(1, 310) 
999         l.setColumnWidth(2, 200)
1000         l.setColumnWidth(3, 130)
1001         l.setColumnWidth(4, 130)
1002         l.setColumnWidth(5, 10)
1003
1004         gap = 0
1005         is_red = False
1006         for address in self.wallet.all_addresses():
1007
1008             if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1009                 continue
1010
1011             n = 0 
1012             h = self.wallet.history.get(address,[])
1013
1014             if h != ['*']: 
1015                 for tx_hash, tx_height in h:
1016                     tx = self.wallet.transactions.get(tx_hash)
1017                     if tx: n += 1
1018                 num_tx = "%d "%n
1019             else:
1020                 n = -1
1021                 num_tx = "*"
1022
1023             if n==0:
1024                 if address in self.wallet.addresses:
1025                     gap += 1
1026                     if gap > self.wallet.gap_limit:
1027                         is_red = True
1028             else:
1029                 if address in self.wallet.addresses:
1030                     gap = 0
1031
1032             item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1033             item.setFont(0, QFont(MONOSPACE_FONT))
1034             item.setFont(1, QFont(MONOSPACE_FONT))
1035             item.setFont(3, QFont(MONOSPACE_FONT))
1036             self.update_receive_item(item)
1037             if is_red and address in self.wallet.addresses:
1038                 item.setBackgroundColor(1, QColor('red'))
1039             l.addTopLevelItem(item)
1040
1041         # we use column 1 because column 0 may be hidden
1042         l.setCurrentItem(l.topLevelItem(0),1)
1043
1044     def show_contact_details(self, m):
1045         a = self.wallet.aliases.get(m)
1046         if a:
1047             if a[0] in self.wallet.authorities.keys():
1048                 s = self.wallet.authorities.get(a[0])
1049             else:
1050                 s = "self-signed"
1051             msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1052             QMessageBox.information(self, 'Alias', msg, 'OK')
1053
1054     def update_contacts_tab(self):
1055
1056         l = self.contacts_list
1057         l.clear()
1058         l.setColumnWidth(0, 350) 
1059         l.setColumnWidth(1, 330)
1060         l.setColumnWidth(2, 100) 
1061
1062         alias_targets = []
1063         for alias, v in self.wallet.aliases.items():
1064             s, target = v
1065             alias_targets.append(target)
1066             item = QTreeWidgetItem( [ target, alias, '-'] )
1067             item.setBackgroundColor(0, QColor('lightgray'))
1068             l.addTopLevelItem(item)
1069             
1070         for address in self.wallet.addressbook:
1071             if address in alias_targets: continue
1072             label = self.wallet.labels.get(address,'')
1073             n = 0 
1074             for item in self.wallet.transactions.values():
1075                 if address in item['outputs'] : n=n+1
1076             tx = "%d"%n
1077             item = QTreeWidgetItem( [ address, label, tx] )
1078             item.setFont(0, QFont(MONOSPACE_FONT))
1079             l.addTopLevelItem(item)
1080
1081         l.setCurrentItem(l.topLevelItem(0))
1082
1083     def create_wall_tab(self):
1084         self.textbox = textbox = QTextEdit(self)
1085         textbox.setFont(QFont(MONOSPACE_FONT))
1086         textbox.setReadOnly(True)
1087         return textbox
1088
1089     def create_status_bar(self):
1090         sb = QStatusBar()
1091         sb.setFixedHeight(35)
1092         if self.wallet.seed:
1093             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1094         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1095         if self.wallet.seed:
1096             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1097         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) 
1098         sb.addPermanentWidget( self.status_button )
1099         self.setStatusBar(sb)
1100
1101     def new_contact_dialog(self):
1102         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1103         address = unicode(text)
1104         if ok:
1105             if self.wallet.is_valid(address):
1106                 self.wallet.addressbook.append(address)
1107                 self.wallet.save()
1108                 self.update_contacts_tab()
1109                 self.update_history_tab()
1110                 self.update_completions()
1111             else:
1112                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1113
1114     @staticmethod
1115     def show_seed_dialog(wallet, parent=None):
1116         if not wallet.seed:
1117             QMessageBox.information(parent, _('Message'),
1118                                     _('No seed'), _('OK'))
1119             return
1120
1121         if wallet.use_encryption:
1122             password = parent.password_dialog()
1123             if not password:
1124                 return
1125         else:
1126             password = None
1127             
1128         try:
1129             seed = wallet.pw_decode(wallet.seed, password)
1130         except:
1131             QMessageBox.warning(parent, _('Error'),
1132                                 _('Incorrect Password'), _('OK'))
1133             return
1134
1135         dialog = QDialog(None)
1136         dialog.setModal(1)
1137         dialog.setWindowTitle("Electrum")
1138
1139         brainwallet = ' '.join(mnemonic.mn_encode(seed))
1140
1141         msg =   _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1142               + _("Please write down or memorize these 12 words (order is important).") + " " \
1143               + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1144               + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1145
1146         main_text = QLabel(msg)
1147         main_text.setWordWrap(True)
1148
1149         logo = QLabel()
1150         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1151
1152         if parent:
1153             app = parent.app
1154         else:
1155             app = QApplication
1156
1157         copy_function = lambda: app.clipboard().setText(brainwallet)
1158         copy_button = QPushButton(_("Copy to Clipboard"))
1159         copy_button.clicked.connect(copy_function)
1160
1161         show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1162         qr_button = QPushButton(_("View as QR Code"))
1163         qr_button.clicked.connect(show_qr_function)
1164
1165         ok_button = QPushButton(_("OK"))
1166         ok_button.setDefault(True)
1167         ok_button.clicked.connect(dialog.accept)
1168
1169         main_layout = QGridLayout()
1170         main_layout.addWidget(logo, 0, 0)
1171         main_layout.addWidget(main_text, 0, 1, 1, -1)
1172         main_layout.addWidget(copy_button, 1, 1)
1173         main_layout.addWidget(qr_button, 1, 2)
1174         main_layout.addWidget(ok_button, 1, 3)
1175         dialog.setLayout(main_layout)
1176
1177         dialog.exec_()
1178
1179     @staticmethod
1180     def show_seed_qrcode(seed):
1181         if not seed: return
1182         d = QDialog(None)
1183         d.setModal(1)
1184         d.setWindowTitle(_("Seed"))
1185         d.setMinimumSize(270, 300)
1186         vbox = QVBoxLayout()
1187         vbox.addWidget(QRCodeWidget(seed))
1188         hbox = QHBoxLayout()
1189         hbox.addStretch(1)
1190         b = QPushButton(_("OK"))
1191         hbox.addWidget(b)
1192         b.clicked.connect(d.accept)
1193
1194         vbox.addLayout(hbox)
1195         d.setLayout(vbox)
1196         d.exec_()
1197
1198     def sign_message(self,address):
1199         if not address: return
1200         d = QDialog(self)
1201         d.setModal(1)
1202         d.setWindowTitle('Sign Message')
1203         d.setMinimumSize(270, 350)
1204
1205         tab_widget = QTabWidget()
1206         tab = QWidget()
1207         layout = QGridLayout(tab)
1208
1209         sign_address = QLineEdit()
1210         sign_address.setText(address)
1211         layout.addWidget(QLabel(_('Address')), 1, 0)
1212         layout.addWidget(sign_address, 1, 1)
1213
1214         sign_message = QTextEdit()
1215         layout.addWidget(QLabel(_('Message')), 2, 0)
1216         layout.addWidget(sign_message, 2, 1, 2, 1)
1217
1218         sign_signature = QLineEdit()
1219         layout.addWidget(QLabel(_('Signature')), 3, 0)
1220         layout.addWidget(sign_signature, 3, 1)
1221
1222         def do_sign():
1223             if self.wallet.use_encryption:
1224                 password = self.password_dialog()
1225                 if not password:
1226                     return
1227             else:
1228                 password = None
1229
1230             try:
1231                 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1232                 sign_signature.setText(signature)
1233             except BaseException, e:
1234                 self.show_message(str(e))
1235                 return
1236
1237         hbox = QHBoxLayout()
1238         b = QPushButton(_("Sign"))
1239         hbox.addWidget(b)
1240         b.clicked.connect(do_sign)
1241         b = QPushButton(_("Close"))
1242         b.clicked.connect(d.accept)
1243         hbox.addWidget(b)
1244         layout.addLayout(hbox, 4, 1)
1245         tab_widget.addTab(tab, "Sign")
1246
1247
1248         tab = QWidget()
1249         layout = QGridLayout(tab)
1250
1251         verify_address = QLineEdit()
1252         layout.addWidget(QLabel(_('Address')), 1, 0)
1253         layout.addWidget(verify_address, 1, 1)
1254
1255         verify_message = QTextEdit()
1256         layout.addWidget(QLabel(_('Message')), 2, 0)
1257         layout.addWidget(verify_message, 2, 1, 2, 1)
1258
1259         verify_signature = QLineEdit()
1260         layout.addWidget(QLabel(_('Signature')), 3, 0)
1261         layout.addWidget(verify_signature, 3, 1)
1262
1263         def do_verify():
1264             try:
1265                 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1266                 self.show_message("Signature verified")
1267             except BaseException, e:
1268                 self.show_message(str(e))
1269                 return
1270
1271         hbox = QHBoxLayout()
1272         b = QPushButton(_("Verify"))
1273         b.clicked.connect(do_verify)
1274         hbox.addWidget(b)
1275         b = QPushButton(_("Close"))
1276         b.clicked.connect(d.accept)
1277         hbox.addWidget(b)
1278         layout.addLayout(hbox, 4, 1)
1279         tab_widget.addTab(tab, "Verify")
1280
1281         vbox = QVBoxLayout()
1282         vbox.addWidget(tab_widget)
1283         d.setLayout(vbox)
1284         d.exec_()
1285
1286         
1287     def toggle_QR_window(self, show):
1288         if show and not self.qr_window:
1289             self.qr_window = QR_Window()
1290             self.qr_window.setVisible(True)
1291             self.qr_window_geometry = self.qr_window.geometry()
1292             item = self.receive_list.currentItem()
1293             if item:
1294                 address = str(item.text(1))
1295                 label = self.wallet.labels.get(address)
1296                 amount = self.wallet.requested_amounts.get(address)
1297                 self.qr_window.set_content( address, label, amount )
1298
1299         elif show and self.qr_window and not self.qr_window.isVisible():
1300             self.qr_window.setVisible(True)
1301             self.qr_window.setGeometry(self.qr_window_geometry)
1302
1303         elif not show and self.qr_window and self.qr_window.isVisible():
1304             self.qr_window_geometry = self.qr_window.geometry()
1305             self.qr_window.setVisible(False)
1306
1307         #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1308         self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1309         self.receive_list.setColumnWidth(2, 200)
1310
1311
1312     def question(self, msg):
1313         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1314
1315     def show_message(self, msg):
1316         QMessageBox.information(self, _('Message'), msg, _('OK'))
1317
1318     def password_dialog(self ):
1319         d = QDialog(self)
1320         d.setModal(1)
1321
1322         pw = QLineEdit()
1323         pw.setEchoMode(2)
1324
1325         vbox = QVBoxLayout()
1326         msg = _('Please enter your password')
1327         vbox.addWidget(QLabel(msg))
1328
1329         grid = QGridLayout()
1330         grid.setSpacing(8)
1331         grid.addWidget(QLabel(_('Password')), 1, 0)
1332         grid.addWidget(pw, 1, 1)
1333         vbox.addLayout(grid)
1334
1335         vbox.addLayout(ok_cancel_buttons(d))
1336         d.setLayout(vbox) 
1337
1338         if not d.exec_(): return
1339         return unicode(pw.text())
1340
1341
1342
1343
1344
1345     @staticmethod
1346     def change_password_dialog( wallet, parent=None ):
1347
1348         if not wallet.seed:
1349             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1350             return
1351
1352         d = QDialog(parent)
1353         d.setModal(1)
1354
1355         pw = QLineEdit()
1356         pw.setEchoMode(2)
1357         new_pw = QLineEdit()
1358         new_pw.setEchoMode(2)
1359         conf_pw = QLineEdit()
1360         conf_pw.setEchoMode(2)
1361
1362         vbox = QVBoxLayout()
1363         if parent:
1364             msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1365                    +_('To disable wallet encryption, enter an empty new password.')) \
1366                    if wallet.use_encryption else _('Your wallet keys are not encrypted')
1367         else:
1368             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1369                   +_("Leave these fields empty if you want to disable encryption.")
1370         vbox.addWidget(QLabel(msg))
1371
1372         grid = QGridLayout()
1373         grid.setSpacing(8)
1374
1375         if wallet.use_encryption:
1376             grid.addWidget(QLabel(_('Password')), 1, 0)
1377             grid.addWidget(pw, 1, 1)
1378
1379         grid.addWidget(QLabel(_('New Password')), 2, 0)
1380         grid.addWidget(new_pw, 2, 1)
1381
1382         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1383         grid.addWidget(conf_pw, 3, 1)
1384         vbox.addLayout(grid)
1385
1386         vbox.addLayout(ok_cancel_buttons(d))
1387         d.setLayout(vbox) 
1388
1389         if not d.exec_(): return
1390
1391         password = unicode(pw.text()) if wallet.use_encryption else None
1392         new_password = unicode(new_pw.text())
1393         new_password2 = unicode(conf_pw.text())
1394
1395         try:
1396             seed = wallet.pw_decode( wallet.seed, password)
1397         except:
1398             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1399             return
1400
1401         if new_password != new_password2:
1402             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1403             return
1404
1405         wallet.update_password(seed, password, new_password)
1406
1407     @staticmethod
1408     def seed_dialog(wallet, parent=None):
1409         d = QDialog(parent)
1410         d.setModal(1)
1411
1412         vbox = QVBoxLayout()
1413         msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1414         vbox.addWidget(QLabel(msg))
1415
1416         grid = QGridLayout()
1417         grid.setSpacing(8)
1418
1419         seed_e = QLineEdit()
1420         grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1421         grid.addWidget(seed_e, 1, 1)
1422
1423         gap_e = QLineEdit()
1424         gap_e.setText("5")
1425         grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1426         grid.addWidget(gap_e, 2, 1)
1427         gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1428         vbox.addLayout(grid)
1429
1430         vbox.addLayout(ok_cancel_buttons(d))
1431         d.setLayout(vbox) 
1432
1433         if not d.exec_(): return
1434
1435         try:
1436             gap = int(unicode(gap_e.text()))
1437         except:
1438             QMessageBox.warning(None, _('Error'), 'error', 'OK')
1439             sys.exit(0)
1440
1441         try:
1442             seed = unicode(seed_e.text())
1443             seed.decode('hex')
1444         except:
1445             print_error("Warning: Not hex, trying decode")
1446             try:
1447                 seed = mnemonic.mn_decode( seed.split(' ') )
1448             except:
1449                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1450                 sys.exit(0)
1451         if not seed:
1452             QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1453             sys.exit(0)
1454         
1455         wallet.seed = str(seed)
1456         #print repr(wallet.seed)
1457         wallet.gap_limit = gap
1458         return True
1459
1460
1461
1462     def settings_dialog(self):
1463         d = QDialog(self)
1464         d.setModal(1)
1465         vbox = QVBoxLayout()
1466         msg = _('Here are the settings of your wallet.') + '\n'\
1467               + _('For more explanations, click on the help buttons next to each field.')
1468
1469         label = QLabel(msg)
1470         label.setFixedWidth(250)
1471         label.setWordWrap(True)
1472         label.setAlignment(Qt.AlignJustify)
1473         vbox.addWidget(label)
1474
1475         grid = QGridLayout()
1476         grid.setSpacing(8)
1477         vbox.addLayout(grid)
1478
1479         fee_label = QLabel(_('Transaction fee'))
1480         grid.addWidget(fee_label, 2, 0)
1481         fee_e = QLineEdit()
1482         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1483         grid.addWidget(fee_e, 2, 1)
1484         msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1485             + _('Recommended value') + ': 0.001'
1486         grid.addWidget(HelpButton(msg), 2, 2)
1487         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1488         if not self.config.is_modifiable('fee'):
1489             for w in [fee_e, fee_label]: w.setEnabled(False)
1490
1491         nz_label = QLabel(_('Display zeros'))
1492         grid.addWidget(nz_label, 3, 0)
1493         nz_e = QLineEdit()
1494         nz_e.setText("%d"% self.wallet.num_zeros)
1495         grid.addWidget(nz_e, 3, 1)
1496         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1497         grid.addWidget(HelpButton(msg), 3, 2)
1498         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1499         if not self.config.is_modifiable('num_zeros'):
1500             for w in [nz_e, nz_label]: w.setEnabled(False)
1501
1502         usechange_cb = QCheckBox(_('Use change addresses'))
1503         grid.addWidget(usechange_cb, 5, 0)
1504         usechange_cb.setChecked(self.wallet.use_change)
1505         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1506         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1507
1508         gap_label = QLabel(_('Gap limit'))
1509         grid.addWidget(gap_label, 6, 0)
1510         gap_e = QLineEdit()
1511         gap_e.setText("%d"% self.wallet.gap_limit)
1512         grid.addWidget(gap_e, 6, 1)
1513         msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1514               + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1515               + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1516               + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1517               + _('Warning') + ': ' \
1518               + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1519               + _('Do not modify it if you do not understand what you are doing, or if you expect to recover your wallet without knowing it!') + '\n\n' 
1520         grid.addWidget(HelpButton(msg), 6, 2)
1521         gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1522         if not self.config.is_modifiable('gap_limit'):
1523             for w in [gap_e, gap_label]: w.setEnabled(False)
1524         
1525         gui_label=QLabel(_('Default GUI') + ':')
1526         grid.addWidget(gui_label , 7, 0)
1527         gui_combo = QComboBox()
1528         gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1529         index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1530         if index==-1: index = 1
1531         gui_combo.setCurrentIndex(index)
1532         grid.addWidget(gui_combo, 7, 1)
1533         grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1534         if not self.config.is_modifiable('gui'):
1535             for w in [gui_combo, gui_label]: w.setEnabled(False)
1536
1537         vbox.addLayout(ok_cancel_buttons(d))
1538         d.setLayout(vbox) 
1539
1540         # run the dialog
1541         if not d.exec_(): return
1542
1543         fee = unicode(fee_e.text())
1544         try:
1545             fee = int( 100000000 * Decimal(fee) )
1546         except:
1547             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1548             return
1549
1550         if self.wallet.fee != fee:
1551             self.wallet.fee = fee
1552             self.wallet.save()
1553         
1554         nz = unicode(nz_e.text())
1555         try:
1556             nz = int( nz )
1557             if nz>8: nz=8
1558         except:
1559             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1560             return
1561
1562         if self.wallet.num_zeros != nz:
1563             self.wallet.num_zeros = nz
1564             self.config.set_key('num_zeros', nz, True)
1565             self.update_history_tab()
1566             self.update_receive_tab()
1567
1568         if self.wallet.use_change != usechange_cb.isChecked():
1569             self.wallet.use_change = usechange_cb.isChecked()
1570             self.config.set_key('use_change', self.wallet.use_change, True)
1571         
1572         try:
1573             n = int(gap_e.text())
1574         except:
1575             QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1576             return
1577
1578         if self.wallet.gap_limit != n:
1579             r = self.wallet.change_gap_limit(n)
1580             if r:
1581                 self.update_receive_tab()
1582                 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1583             else:
1584                 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1585                     
1586         self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1587
1588
1589
1590     @staticmethod 
1591     def network_dialog(wallet, parent=None):
1592         interface = wallet.interface
1593         if parent:
1594             if interface.is_connected:
1595                 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1596             else:
1597                 status = _("Not connected")
1598             server = interface.server
1599         else:
1600             import random
1601             status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1602             server = interface.server
1603
1604         plist, servers_list = interface.get_servers_list()
1605
1606         d = QDialog(parent)
1607         d.setModal(1)
1608         d.setWindowTitle(_('Server'))
1609         d.setMinimumSize(375, 20)
1610
1611         vbox = QVBoxLayout()
1612         vbox.setSpacing(30)
1613
1614         hbox = QHBoxLayout()
1615         l = QLabel()
1616         l.setPixmap(QPixmap(":icons/network.png"))
1617         hbox.addStretch(10)
1618         hbox.addWidget(l)
1619         hbox.addWidget(QLabel(status))
1620         hbox.addStretch(50)
1621         vbox.addLayout(hbox)
1622
1623
1624         # grid layout
1625         grid = QGridLayout()
1626         grid.setSpacing(8)
1627         vbox.addLayout(grid)
1628
1629         # server
1630         server_protocol = QComboBox()
1631         server_host = QLineEdit()
1632         server_host.setFixedWidth(200)
1633         server_port = QLineEdit()
1634         server_port.setFixedWidth(60)
1635
1636         protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1637         protocol_letters = 'thsg'
1638         DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1639         server_protocol.addItems(protocol_names)
1640
1641         grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1642         grid.addWidget(server_protocol, 0, 1)
1643         grid.addWidget(server_host, 0, 2)
1644         grid.addWidget(server_port, 0, 3)
1645
1646         def change_protocol(p):
1647             protocol = protocol_letters[p]
1648             host = unicode(server_host.text())
1649             pp = plist.get(host,DEFAULT_PORTS)
1650             if protocol not in pp.keys():
1651                 protocol = pp.keys()[0]
1652             port = pp[protocol]
1653             server_host.setText( host )
1654             server_port.setText( port )
1655
1656         server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1657         
1658         label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1659         servers_list_widget = QTreeWidget(parent)
1660         servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1661         servers_list_widget.setMaximumHeight(150)
1662         servers_list_widget.setColumnWidth(0, 240)
1663         for _host in servers_list.keys():
1664             _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1665             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1666
1667         def change_server(host, protocol=None):
1668             pp = plist.get(host,DEFAULT_PORTS)
1669             if protocol:
1670                 port = pp.get(protocol)
1671                 if not port: protocol = None
1672                     
1673             if not protocol:
1674                 if 't' in pp.keys():
1675                     protocol = 't'
1676                     port = pp.get(protocol)
1677                 else:
1678                     protocol = pp.keys()[0]
1679                     port = pp.get(protocol)
1680             
1681             server_host.setText( host )
1682             server_port.setText( port )
1683             server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1684
1685             if not plist: return
1686             for p in protocol_letters:
1687                 i = protocol_letters.index(p)
1688                 j = server_protocol.model().index(i,0)
1689                 if p not in pp.keys():
1690                     server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1691                 else:
1692                     server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1693
1694
1695         if server:
1696             host, port, protocol = server.split(':')
1697             change_server(host,protocol)
1698
1699         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1700         grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1701
1702         if not wallet.config.is_modifiable('server'):
1703             for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1704
1705         # proxy setting
1706         proxy_mode = QComboBox()
1707         proxy_host = QLineEdit()
1708         proxy_host.setFixedWidth(200)
1709         proxy_port = QLineEdit()
1710         proxy_port.setFixedWidth(60)
1711         proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1712
1713         def check_for_disable(index = False):
1714             if proxy_mode.currentText() != 'NONE':
1715                 proxy_host.setEnabled(True)
1716                 proxy_port.setEnabled(True)
1717             else:
1718                 proxy_host.setEnabled(False)
1719                 proxy_port.setEnabled(False)
1720
1721         check_for_disable()
1722         proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1723
1724         if not wallet.config.is_modifiable('proxy'):
1725             for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1726
1727         proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1728         proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1729         proxy_host.setText(proxy_config.get("host"))
1730         proxy_port.setText(proxy_config.get("port"))
1731
1732         grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1733         grid.addWidget(proxy_mode, 2, 1)
1734         grid.addWidget(proxy_host, 2, 2)
1735         grid.addWidget(proxy_port, 2, 3)
1736
1737         # buttons
1738         vbox.addLayout(ok_cancel_buttons(d))
1739         d.setLayout(vbox) 
1740
1741         if not d.exec_(): return
1742
1743         server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1744         if proxy_mode.currentText() != 'NONE':
1745             proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1746         else:
1747             proxy = None
1748
1749         wallet.config.set_key("proxy", proxy, True)
1750         wallet.config.set_key("server", server, True)
1751         interface.set_server(server, proxy)
1752                 
1753         return True
1754
1755     def closeEvent(self, event):
1756         g = self.geometry()
1757         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1758         event.accept()
1759
1760
1761 class ElectrumGui:
1762
1763     def __init__(self, wallet, config, app=None):
1764         self.wallet = wallet
1765         self.config = config
1766         if app is None:
1767             self.app = QApplication(sys.argv)
1768
1769
1770     def restore_or_create(self):
1771         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1772         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1773         if r==2: return None
1774         return 'restore' if r==1 else 'create'
1775
1776     def seed_dialog(self):
1777         return ElectrumWindow.seed_dialog( self.wallet )
1778
1779     def network_dialog(self):
1780         return ElectrumWindow.network_dialog( self.wallet, parent=None )
1781         
1782
1783     def show_seed(self):
1784         ElectrumWindow.show_seed_dialog(self.wallet)
1785
1786
1787     def password_dialog(self):
1788         ElectrumWindow.change_password_dialog(self.wallet)
1789
1790
1791     def restore_wallet(self):
1792         wallet = self.wallet
1793         # wait until we are connected, because the user might have selected another server
1794         if not wallet.interface.is_connected:
1795             waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1796             waiting_dialog(waiting)
1797
1798         waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1799             %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1800
1801         wallet.set_up_to_date(False)
1802         wallet.interface.poke('synchronizer')
1803         waiting_dialog(waiting)
1804         if wallet.is_found():
1805             print_error( "Recovery successful" )
1806         else:
1807             QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1808
1809         return True
1810
1811     def main(self,url):
1812         s = Timer()
1813         s.start()
1814         w = ElectrumWindow(self.wallet, self.config)
1815         if url: w.set_url(url)
1816         w.app = self.app
1817         w.connect_slots(s)
1818         w.update_wallet()
1819         w.show()
1820
1821         self.app.exec_()