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