669307353d0c4af88e73b89abdeb8e6056544898
[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     chars = '0123456789'
58     if not is_int: chars +='.'
59     s = ''.join([i for i in text if i in chars])
60     if not is_int:
61         if '.' in s:
62             p = s.find('.')
63             s = s.replace('.','')
64             s = s[:p] + '.' + s[p:p+8]
65         try:
66             amount = int( Decimal(s) * 100000000 )
67         except:
68             amount = None
69     else:
70         try:
71             amount = int( s )
72         except:
73             amount = None
74     entry.setText(s)
75     return amount
76
77
78 class Timer(QtCore.QThread):
79     def run(self):
80         while True:
81             self.emit(QtCore.SIGNAL('timersignal'))
82             time.sleep(0.5)
83
84 class HelpButton(QPushButton):
85     def __init__(self, text):
86         QPushButton.__init__(self, '?')
87         self.setFocusPolicy(Qt.NoFocus)
88         self.setFixedWidth(20)
89         self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
90
91
92 class EnterButton(QPushButton):
93     def __init__(self, text, func):
94         QPushButton.__init__(self, text)
95         self.func = func
96         self.clicked.connect(func)
97
98     def keyPressEvent(self, e):
99         if e.key() == QtCore.Qt.Key_Return:
100             apply(self.func,())
101
102 class MyTreeWidget(QTreeWidget):
103     def __init__(self, parent):
104         QTreeWidget.__init__(self, parent)
105         def ddfr(item):
106             if not item: return
107             for i in range(0,self.viewport().height()/5):
108                 if self.itemAt(QPoint(0,i*5)) == item:
109                     break
110             else:
111                 return
112             for j in range(0,30):
113                 if self.itemAt(QPoint(0,i*5 + j)) != item:
114                     break
115             self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
116
117         self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
118         
119
120
121
122 class StatusBarButton(QPushButton):
123     def __init__(self, icon, tooltip, func):
124         QPushButton.__init__(self, icon, '')
125         self.setToolTip(tooltip)
126         self.setFlat(True)
127         self.setMaximumWidth(25)
128         self.clicked.connect(func)
129         self.func = func
130
131     def keyPressEvent(self, e):
132         if e.key() == QtCore.Qt.Key_Return:
133             apply(self.func,())
134
135
136 class QRCodeWidget(QWidget):
137
138     def __init__(self, addr):
139         super(QRCodeWidget, self).__init__()
140         self.setGeometry(300, 300, 350, 350)
141         self.set_addr(addr)
142
143     def set_addr(self, addr):
144         self.addr = addr
145         self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
146         self.qr.addData(addr)
147         self.qr.make()
148         
149     def paintEvent(self, e):
150         qp = QtGui.QPainter()
151         qp.begin(self)
152         boxsize = 6
153         size = self.qr.getModuleCount()*boxsize
154         k = self.qr.getModuleCount()
155         black = QColor(0, 0, 0, 255)
156         white = QColor(255, 255, 255, 255)
157         for r in range(k):
158             for c in range(k):
159                 if self.qr.isDark(r, c):
160                     qp.setBrush(black)
161                     qp.setPen(black)
162                 else:
163                     qp.setBrush(white)
164                     qp.setPen(white)
165                 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
166         qp.end()
167         
168
169
170 def ok_cancel_buttons(dialog):
171     hbox = QHBoxLayout()
172     hbox.addStretch(1)
173     b = QPushButton("OK")
174     hbox.addWidget(b)
175     b.clicked.connect(dialog.accept)
176     b = QPushButton("Cancel")
177     hbox.addWidget(b)
178     b.clicked.connect(dialog.reject)
179     return hbox
180
181
182 class ElectrumWindow(QMainWindow):
183
184     def __init__(self, wallet):
185         QMainWindow.__init__(self)
186         self.wallet = wallet
187         self.wallet.register_callback(self.update_callback)
188
189         self.funds_error = False
190         self.completions = QStringListModel()
191
192         self.tabs = tabs = QTabWidget(self)
193         tabs.addTab(self.create_history_tab(), _('History') )
194         if self.wallet.seed:
195             tabs.addTab(self.create_send_tab(), _('Send') )
196         tabs.addTab(self.create_receive_tab(), _('Receive') )
197         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
198         tabs.addTab(self.create_wall_tab(), _('Wall') )
199         tabs.setMinimumSize(600, 400)
200         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
201         self.setCentralWidget(tabs)
202         self.create_status_bar()
203         self.setGeometry(100,100,840,400)
204         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.path
205         if not self.wallet.seed: title += ' [seedless]'
206         self.setWindowTitle( title )
207
208         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
209         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
210         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
211         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
212         
213         self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
214         self.history_list.setFocus(True)
215
216         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
217         if platform.system() == 'Windows':
218             n = 3 if self.wallet.seed else 2
219             tabs.setCurrentIndex (n)
220             tabs.setCurrentIndex (0)
221
222
223     def connect_slots(self, sender):
224         if self.wallet.seed:
225             self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
226             self.previous_payto_e=''
227
228     def check_recipient(self):
229         if self.payto_e.hasFocus():
230             return
231         r = unicode( self.payto_e.text() )
232         if r != self.previous_payto_e:
233             self.previous_payto_e = r
234             r = r.strip()
235             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
236                 try:
237                     to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
238                 except:
239                     return
240                 if to_address:
241                     s = r + '  <' + to_address + '>'
242                     self.payto_e.setText(s)
243
244
245     def update_callback(self):
246         self.emit(QtCore.SIGNAL('updatesignal'))
247
248     def update_wallet(self):
249         if self.wallet.interface and self.wallet.interface.is_connected:
250             if self.wallet.blocks == -1:
251                 text = _( "Connecting..." )
252                 icon = QIcon(":icons/status_disconnected.png")
253             elif self.wallet.blocks == 0:
254                 text = _( "Server not ready" )
255                 icon = QIcon(":icons/status_disconnected.png")
256             elif not self.wallet.up_to_date:
257                 text = _( "Synchronizing..." )
258                 icon = QIcon(":icons/status_waiting.png")
259             else:
260                 c, u = self.wallet.get_balance()
261                 text =  _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
262                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
263                 icon = QIcon(":icons/status_connected.png")
264         else:
265             text = _( "Not connected" )
266             icon = QIcon(":icons/status_disconnected.png")
267
268         if self.funds_error:
269             text = _( "Not enough funds" )
270
271         self.statusBar().showMessage(text)
272         self.status_button.setIcon( icon )
273
274         if self.wallet.up_to_date:
275             self.textbox.setText( self.wallet.banner )
276             self.update_history_tab()
277             self.update_receive_tab()
278             self.update_contacts_tab()
279             self.update_completions()
280
281
282     def create_history_tab(self):
283         self.history_list = l = MyTreeWidget(self)
284         l.setColumnCount(5)
285         l.setColumnWidth(0, 40) 
286         l.setColumnWidth(1, 140) 
287         l.setColumnWidth(2, 350) 
288         l.setColumnWidth(3, 140) 
289         l.setColumnWidth(4, 140) 
290         l.setHeaderLabels( [ '', _( 'Date' ), _( 'To / From' ) , _('Amount'), _('Balance')] )
291         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
292         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
293         l.setContextMenuPolicy(Qt.CustomContextMenu)
294         l.customContextMenuRequested.connect(self.create_history_menu)
295         return l
296
297     def create_history_menu(self, position):
298         self.history_list.selectedIndexes() 
299         item = self.history_list.currentItem()
300         if not item: return
301         tx_hash = str(item.toolTip(0))
302         menu = QMenu()
303         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
304         menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
305         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
306         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
307
308     def tx_details(self, tx_hash):
309         tx = self.wallet.tx_history.get(tx_hash)
310
311         if tx['height']:
312             conf = self.wallet.blocks - tx['height'] + 1
313             time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
314         else:
315             conf = 0
316             time_str = 'pending'
317
318         tx_details = _("Transaction Details") +"\n\n" \
319             + "Transaction ID:\n" + tx_hash + "\n\n" \
320             + "Status: %d confirmations\n\n"%conf  \
321             + "Date: %s\n\n"%time_str \
322             + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
323             + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
324
325         r = self.wallet.receipts.get(tx_hash)
326         if r:
327             tx_details += "\n_______________________________________" \
328                 + '\n\nSigned URI: ' + r[2] \
329                 + "\n\nSigned by: " + r[0] \
330                 + '\n\nSignature: ' + r[1]
331
332         QMessageBox.information(self, 'Details', tx_details, 'OK')
333
334
335     def tx_label_clicked(self, item, column):
336         if column==2 and item.isSelected():
337             tx_hash = str(item.toolTip(0))
338             self.is_edit=True
339             #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
340             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
341             self.history_list.editItem( item, column )
342             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
343             self.is_edit=False
344
345     def tx_label_changed(self, item, column):
346         if self.is_edit: 
347             return
348         self.is_edit=True
349         tx_hash = str(item.toolTip(0))
350         tx = self.wallet.tx_history.get(tx_hash)
351         s = self.wallet.labels.get(tx_hash)
352         text = unicode( item.text(2) )
353         if text: 
354             self.wallet.labels[tx_hash] = text
355             item.setForeground(2, QBrush(QColor('black')))
356         else:
357             if s: self.wallet.labels.pop(tx_hash)
358             text = tx['default_label']
359             item.setText(2, text)
360             item.setForeground(2, QBrush(QColor('gray')))
361         self.is_edit=False
362
363     def edit_label(self, is_recv):
364         l = self.receive_list if is_recv else self.contacts_list
365         c = 2 if is_recv else 1
366         item = l.currentItem()
367         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
368         l.editItem( item, c )
369         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
370
371     def address_label_clicked(self, item, column, l, column_addr, column_label):
372         if column==column_label and item.isSelected():
373             addr = unicode( item.text(column_addr) )
374             label = unicode( item.text(column_label) )
375             if label in self.wallet.aliases.keys():
376                 return
377             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
378             l.editItem( item, column )
379             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
380
381     def address_label_changed(self, item, column, l, column_addr, column_label):
382         addr = unicode( item.text(column_addr) )
383         text = unicode( item.text(column_label) )
384         if text:
385             if text not in self.wallet.aliases.keys():
386                 self.wallet.labels[addr] = text
387             else:
388                 print_error("Error: This is one of your aliases")
389                 label = self.wallet.labels.get(addr,'')
390                 item.setText(column_label, QString(label))
391         else:
392             s = self.wallet.labels.get(addr)
393             if s: self.wallet.labels.pop(addr)
394
395         self.update_history_tab()
396         self.update_completions()
397
398     def update_history_tab(self):
399         self.history_list.clear()
400         balance = 0
401         for tx in self.wallet.get_tx_history():
402             tx_hash = tx['tx_hash']
403             if tx['height']:
404                 conf = self.wallet.blocks - tx['height'] + 1
405                 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
406                 icon = QIcon(":icons/confirmed.png")
407             else:
408                 conf = 0
409                 time_str = 'pending'
410                 icon = QIcon(":icons/unconfirmed.png")
411             v = tx['value']
412             balance += v 
413             label = self.wallet.labels.get(tx_hash)
414             is_default_label = (label == '') or (label is None)
415             if is_default_label: label = tx['default_label']
416
417             item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] )
418             item.setFont(2, QFont(MONOSPACE_FONT))
419             item.setFont(3, QFont(MONOSPACE_FONT))
420             item.setFont(4, QFont(MONOSPACE_FONT))
421             item.setToolTip(0, tx_hash)
422             if is_default_label:
423                 item.setForeground(2, QBrush(QColor('grey')))
424
425             item.setIcon(0, icon)
426             self.history_list.insertTopLevelItem(0,item)
427
428         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
429
430
431     def create_send_tab(self):
432         w = QWidget()
433
434         grid = QGridLayout()
435         grid.setSpacing(8)
436         grid.setColumnMinimumWidth(3,300)
437         grid.setColumnStretch(5,1)
438
439         self.payto_e = QLineEdit()
440         grid.addWidget(QLabel(_('Pay to')), 1, 0)
441         grid.addWidget(self.payto_e, 1, 1, 1, 3)
442         
443         def fill_from_qr():
444             qrcode = qrscanner.scan_qr()
445             if 'address' in qrcode:
446                 self.payto_e.setText(qrcode['address'])
447             if 'amount' in qrcode:
448                 self.amount_e.setText(str(qrcode['amount']))
449             if 'label' in qrcode:
450                 self.message_e.setText(qrcode['label'])
451             if 'message' in qrcode:
452                 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
453                 
454
455         if qrscanner.is_available():
456             b = QPushButton(_("Scan QR code"))
457             b.clicked.connect(fill_from_qr)
458             grid.addWidget(b, 1, 5)
459     
460         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)
461
462         completer = QCompleter()
463         completer.setCaseSensitivity(False)
464         self.payto_e.setCompleter(completer)
465         completer.setModel(self.completions)
466
467         self.message_e = QLineEdit()
468         grid.addWidget(QLabel(_('Description')), 2, 0)
469         grid.addWidget(self.message_e, 2, 1, 1, 3)
470         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)
471
472         self.amount_e = QLineEdit()
473         grid.addWidget(QLabel(_('Amount')), 3, 0)
474         grid.addWidget(self.amount_e, 3, 1, 1, 2)
475         grid.addWidget(HelpButton(
476                 _('Amount to be sent.') + '\n\n' \
477                     + _('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)
478         
479         self.fee_e = QLineEdit()
480         grid.addWidget(QLabel(_('Fee')), 4, 0)
481         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
482         grid.addWidget(HelpButton(
483                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
484                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
485                     + _('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)
486         
487         b = EnterButton(_("Send"), self.do_send)
488         grid.addWidget(b, 6, 1)
489
490         b = EnterButton(_("Clear"),self.do_clear)
491         grid.addWidget(b, 6, 2)
492
493         self.payto_sig = QLabel('')
494         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
495
496         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
497         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
498         w.setLayout(grid) 
499
500         w2 = QWidget()
501         vbox = QVBoxLayout()
502         vbox.addWidget(w)
503         vbox.addStretch(1)
504         w2.setLayout(vbox)
505
506         def entry_changed( is_fee ):
507             self.funds_error = False
508             amount = numbify(self.amount_e)
509             fee = numbify(self.fee_e)
510             if not is_fee: fee = None
511             if amount is None:
512                 return
513             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
514             if not is_fee:
515                 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
516             if inputs:
517                 palette = QPalette()
518                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
519             else:
520                 palette = QPalette()
521                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
522                 self.funds_error = True
523             self.amount_e.setPalette(palette)
524             self.fee_e.setPalette(palette)
525
526         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
527         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
528
529         return w2
530
531
532     def update_completions(self):
533         l = []
534         for addr,label in self.wallet.labels.items():
535             if addr in self.wallet.addressbook:
536                 l.append( label + '  <' + addr + '>')
537         l = l + self.wallet.aliases.keys()
538
539         self.completions.setStringList(l)
540
541
542
543     def do_send(self):
544
545         label = unicode( self.message_e.text() )
546         r = unicode( self.payto_e.text() )
547         r = r.strip()
548
549         # alias
550         m1 = re.match(ALIAS_REGEXP, r)
551         # label or alias, with address in brackets
552         m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
553         
554         if m1:
555             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
556             if not to_address:
557                 return
558         elif m2:
559             to_address = m2.group(2)
560         else:
561             to_address = r
562
563         if not self.wallet.is_valid(to_address):
564             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
565             return
566
567         try:
568             amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
569         except:
570             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
571             return
572         try:
573             fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
574         except:
575             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
576             return
577
578         if self.wallet.use_encryption:
579             password = self.password_dialog()
580             if not password:
581                 return
582         else:
583             password = None
584
585         try:
586             tx = self.wallet.mktx( to_address, amount, label, password, fee)
587         except BaseException, e:
588             self.show_message(str(e))
589             return
590             
591         status, msg = self.wallet.sendtx( tx )
592         if status:
593             QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
594             self.do_clear()
595             self.update_contacts_tab()
596         else:
597             QMessageBox.warning(self, _('Error'), msg, _('OK'))
598
599
600     def set_url(self, url):
601         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
602         self.tabs.setCurrentIndex(1)
603         label = self.wallet.labels.get(payto)
604         m_addr = label + '  <'+ payto+'>' if label else payto
605         self.payto_e.setText(m_addr)
606
607         self.message_e.setText(message)
608         self.amount_e.setText(amount)
609         if identity:
610             self.set_frozen(self.payto_e,True)
611             self.set_frozen(self.amount_e,True)
612             self.set_frozen(self.message_e,True)
613             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
614         else:
615             self.payto_sig.setVisible(False)
616
617     def do_clear(self):
618         self.payto_sig.setVisible(False)
619         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
620             e.setText('')
621             self.set_frozen(e,False)
622
623     def set_frozen(self,entry,frozen):
624         if frozen:
625             entry.setReadOnly(True)
626             entry.setFrame(False)
627             palette = QPalette()
628             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
629             entry.setPalette(palette)
630         else:
631             entry.setReadOnly(False)
632             entry.setFrame(True)
633             palette = QPalette()
634             palette.setColor(entry.backgroundRole(), QColor('white'))
635             entry.setPalette(palette)
636
637
638     def toggle_freeze(self,addr):
639         if not addr: return
640         if addr in self.wallet.frozen_addresses:
641             self.wallet.unfreeze(addr)
642         else:
643             self.wallet.freeze(addr)
644         self.update_receive_tab()
645
646     def toggle_priority(self,addr):
647         if not addr: return
648         if addr in self.wallet.prioritized_addresses:
649             self.wallet.unprioritize(addr)
650         else:
651             self.wallet.prioritize(addr)
652         self.update_receive_tab()
653
654
655     def create_list_tab(self, headers):
656         "generic tab creation method"
657         l = MyTreeWidget(self)
658         l.setColumnCount( len(headers) )
659         l.setHeaderLabels( headers )
660
661         w = QWidget()
662         vbox = QVBoxLayout()
663         w.setLayout(vbox)
664
665         vbox.setMargin(0)
666         vbox.setSpacing(0)
667         vbox.addWidget(l)
668         buttons = QWidget()
669         vbox.addWidget(buttons)
670
671         hbox = QHBoxLayout()
672         hbox.setMargin(0)
673         hbox.setSpacing(0)
674         buttons.setLayout(hbox)
675
676         return l,w,hbox
677
678
679     def create_receive_tab(self):
680         l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Balance'), _('Tx')])
681         l.setContextMenuPolicy(Qt.CustomContextMenu)
682         l.customContextMenuRequested.connect(self.create_receive_menu)
683         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
684         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
685         self.receive_list = l
686         self.receive_buttons_hbox = hbox
687         return w
688
689
690     def create_contacts_tab(self):
691         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
692         l.setContextMenuPolicy(Qt.CustomContextMenu)
693         l.customContextMenuRequested.connect(self.create_contact_menu)
694         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
695         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
696         self.contacts_list = l
697         self.contacts_buttons_hbox = hbox
698         hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
699         hbox.addStretch(1)
700         return w
701
702
703     def create_receive_menu(self, position):
704         # fixme: this function apparently has a side effect.
705         # if it is not called the menu pops up several times
706         #self.receive_list.selectedIndexes() 
707
708         item = self.receive_list.itemAt(position)
709         if not item: return
710         addr = unicode(item.text(1))
711         menu = QMenu()
712         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
713         menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
714         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
715         if self.wallet.expert_mode:
716             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
717             menu.addAction(t, lambda: self.toggle_freeze(addr))
718             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
719             menu.addAction(t, lambda: self.toggle_priority(addr))
720         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
721
722
723     def payto(self, x, is_alias):
724         if not x: return
725         if is_alias:
726             label = x
727             m_addr = label
728         else:
729             addr = x
730             label = self.wallet.labels.get(addr)
731             m_addr = label + '  <' + addr + '>' if label else addr
732         self.tabs.setCurrentIndex(1)
733         self.payto_e.setText(m_addr)
734         self.amount_e.setFocus()
735
736     def delete_contact(self, x, is_alias):
737         if self.question("Do you want to remove %s from your list of contacts?"%x):
738             if not is_alias and x in self.wallet.addressbook:
739                 self.wallet.addressbook.remove(x)
740                 if x in self.wallet.labels.keys():
741                     self.wallet.labels.pop(x)
742             elif is_alias and x in self.wallet.aliases:
743                 self.wallet.aliases.pop(x)
744             self.update_history_tab()
745             self.update_contacts_tab()
746             self.update_completions()
747
748     def create_contact_menu(self, position):
749         # fixme: this function apparently has a side effect.
750         # if it is not called the menu pops up several times
751         #self.contacts_list.selectedIndexes() 
752
753         item = self.contacts_list.itemAt(position)
754         if not item: return
755         addr = unicode(item.text(0))
756         label = unicode(item.text(1))
757         is_alias = label in self.wallet.aliases.keys()
758         x = label if is_alias else addr
759         menu = QMenu()
760         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
761         menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
762         menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
763         if not is_alias:
764             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
765         else:
766             menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
767         menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
768         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
769
770
771     def update_receive_tab(self):
772         l = self.receive_list
773         l.clear()
774         l.setColumnHidden(0,not self.wallet.expert_mode)
775         l.setColumnHidden(3,not self.wallet.expert_mode)
776         l.setColumnHidden(4,not self.wallet.expert_mode)
777         l.setColumnWidth(0, 50) 
778         l.setColumnWidth(1, 310) 
779         l.setColumnWidth(2, 250)
780         l.setColumnWidth(3, 130) 
781         l.setColumnWidth(4, 10)
782
783         gap = 0
784         is_red = False
785         for address in self.wallet.all_addresses():
786
787             if self.wallet.is_change(address) and not self.wallet.expert_mode:
788                 continue
789
790             label = self.wallet.labels.get(address,'')
791             n = 0 
792             h = self.wallet.history.get(address,[])
793             for item in h:
794                 if not item['is_input'] : n=n+1
795
796             tx = "%d "%n
797             if n==0:
798                 if address in self.wallet.addresses:
799                     gap += 1
800                     if gap > self.wallet.gap_limit:
801                         is_red = True
802             else:
803                 if address in self.wallet.addresses:
804                     gap = 0
805
806             c, u = self.wallet.get_addr_balance(address)
807             balance = format_satoshis( c + u, False, self.wallet.num_zeros )
808             flags = self.wallet.get_address_flags(address)
809             item = QTreeWidgetItem( [ flags, address, label, balance, tx] )
810
811             item.setFont(0, QFont(MONOSPACE_FONT))
812             item.setFont(1, QFont(MONOSPACE_FONT))
813             item.setFont(3, QFont(MONOSPACE_FONT))
814             if address in self.wallet.frozen_addresses: 
815                 item.setBackgroundColor(1, QColor('lightblue'))
816             elif address in self.wallet.prioritized_addresses: 
817                 item.setBackgroundColor(1, QColor('lightgreen'))
818             if is_red and address in self.wallet.addresses:
819                 item.setBackgroundColor(1, QColor('red'))
820             l.addTopLevelItem(item)
821
822         # we use column 1 because column 0 may be hidden
823         l.setCurrentItem(l.topLevelItem(0),1)
824
825     def show_contact_details(self, m):
826         a = self.wallet.aliases.get(m)
827         if a:
828             if a[0] in self.wallet.authorities.keys():
829                 s = self.wallet.authorities.get(a[0])
830             else:
831                 s = "self-signed"
832             msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
833             QMessageBox.information(self, 'Alias', msg, 'OK')
834
835     def update_contacts_tab(self):
836
837         l = self.contacts_list
838         l.clear()
839         l.setColumnHidden(2, not self.wallet.expert_mode)
840         l.setColumnWidth(0, 350) 
841         l.setColumnWidth(1, 330)
842         l.setColumnWidth(2, 100) 
843
844         alias_targets = []
845         for alias, v in self.wallet.aliases.items():
846             s, target = v
847             alias_targets.append(target)
848             item = QTreeWidgetItem( [ target, alias, '-'] )
849             item.setBackgroundColor(0, QColor('lightgray'))
850             l.addTopLevelItem(item)
851             
852         for address in self.wallet.addressbook:
853             if address in alias_targets: continue
854             label = self.wallet.labels.get(address,'')
855             n = 0 
856             for item in self.wallet.tx_history.values():
857                 if address in item['outputs'] : n=n+1
858             tx = "%d"%n
859             item = QTreeWidgetItem( [ address, label, tx] )
860             item.setFont(0, QFont(MONOSPACE_FONT))
861             l.addTopLevelItem(item)
862
863         l.setCurrentItem(l.topLevelItem(0))
864
865     def create_wall_tab(self):
866         self.textbox = textbox = QTextEdit(self)
867         textbox.setFont(QFont(MONOSPACE_FONT))
868         textbox.setReadOnly(True)
869         return textbox
870
871     def create_status_bar(self):
872         sb = QStatusBar()
873         sb.setFixedHeight(35)
874         if self.wallet.seed:
875             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
876         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
877         if self.wallet.seed:
878             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
879         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) 
880         sb.addPermanentWidget( self.status_button )
881         self.setStatusBar(sb)
882
883     def new_contact_dialog(self):
884         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
885         address = unicode(text)
886         if ok:
887             if self.wallet.is_valid(address):
888                 self.wallet.addressbook.append(address)
889                 self.wallet.save()
890                 self.update_contacts_tab()
891                 self.update_history_tab()
892                 self.update_completions()
893             else:
894                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
895
896     @staticmethod
897     def show_seed_dialog(wallet, parent=None):
898         if not wallet.seed:
899             QMessageBox.information(parent, _('Message'),
900                                     _('No seed'), _('OK'))
901             return
902
903         if wallet.use_encryption:
904             password = parent.password_dialog()
905             if not password:
906                 return
907         else:
908             password = None
909             
910         try:
911             seed = wallet.pw_decode(wallet.seed, password)
912         except:
913             QMessageBox.warning(parent, _('Error'),
914                                 _('Incorrect Password'), _('OK'))
915             return
916
917         dialog = QDialog(None)
918         dialog.setModal(1)
919         dialog.setWindowTitle(_("Seed"))
920
921         brainwallet = ' '.join(mnemonic.mn_encode(seed))
922
923         msg = _('<p>"%s"</p>'
924                 "<p>If you memorise or write down these 12 words, you will always be able to recover your wallet.</p>"
925                 "<p>This is called a 'BrainWallet'. The order of words is important. Case does not matter (capitals or lowercase).</p>") % brainwallet
926         main_text = QLabel(msg)
927         main_text.setWordWrap(True)
928
929         logo = QLabel()
930         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
931
932         if parent:
933             app = parent.app
934         else:
935             app = QApplication
936
937         copy_function = lambda: app.clipboard().setText(brainwallet)
938         copy_button = QPushButton(_("Copy to Clipboard"))
939         copy_button.clicked.connect(copy_function)
940
941         show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
942         qr_button = QPushButton(_("View as QR Code"))
943         qr_button.clicked.connect(show_qr_function)
944
945         ok_button = QPushButton(_("OK"))
946         ok_button.clicked.connect(dialog.accept)
947
948         main_layout = QGridLayout()
949         main_layout.addWidget(logo, 0, 0)
950         main_layout.addWidget(main_text, 0, 1, 1, -1)
951         main_layout.addWidget(copy_button, 1, 1)
952         main_layout.addWidget(qr_button, 1, 2)
953         main_layout.addWidget(ok_button, 1, 3)
954         dialog.setLayout(main_layout)
955
956         dialog.exec_()
957
958     @staticmethod
959     def show_seed_qrcode(seed):
960         if not seed: return
961         d = QDialog(None)
962         d.setModal(1)
963         d.setWindowTitle(_("Seed"))
964         d.setMinimumSize(270, 300)
965         vbox = QVBoxLayout()
966         vbox.addWidget(QRCodeWidget(seed))
967         hbox = QHBoxLayout()
968         hbox.addStretch(1)
969         b = QPushButton(_("OK"))
970         hbox.addWidget(b)
971         b.clicked.connect(d.accept)
972
973         vbox.addLayout(hbox)
974         d.setLayout(vbox)
975         d.exec_()
976
977
978     def show_address_qrcode(self,address):
979         if not address: return
980         d = QDialog(self)
981         d.setModal(1)
982         d.setWindowTitle(address)
983         d.setMinimumSize(270, 350)
984         vbox = QVBoxLayout()
985         qrw = QRCodeWidget(address)
986         vbox.addWidget(qrw)
987
988         hbox = QHBoxLayout()
989         amount_e = QLineEdit()
990         hbox.addWidget(QLabel(_('Amount')))
991         hbox.addWidget(amount_e)
992         vbox.addLayout(hbox)
993
994         #hbox = QHBoxLayout()
995         #label_e = QLineEdit()
996         #hbox.addWidget(QLabel('Label'))
997         #hbox.addWidget(label_e)
998         #vbox.addLayout(hbox)
999
1000         def amount_changed():
1001             amount = numbify(amount_e)
1002             #label = str( label_e.getText() )
1003             if amount is not None:
1004                 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
1005             else:
1006                 qrw.set_addr( address )
1007             qrw.repaint()
1008
1009         def do_save():
1010             bmp.save_qrcode(qrw.qr, "qrcode.bmp")
1011             self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
1012             
1013         amount_e.textChanged.connect( amount_changed )
1014
1015         hbox = QHBoxLayout()
1016         hbox.addStretch(1)
1017         b = QPushButton(_("Save"))
1018         b.clicked.connect(do_save)
1019         hbox.addWidget(b)
1020         b = QPushButton(_("Close"))
1021         hbox.addWidget(b)
1022         b.clicked.connect(d.accept)
1023
1024         vbox.addLayout(hbox)
1025         d.setLayout(vbox)
1026         d.exec_()
1027
1028     def question(self, msg):
1029         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1030
1031     def show_message(self, msg):
1032         QMessageBox.information(self, _('Message'), msg, _('OK'))
1033
1034     def password_dialog(self ):
1035         d = QDialog(self)
1036         d.setModal(1)
1037
1038         pw = QLineEdit()
1039         pw.setEchoMode(2)
1040
1041         vbox = QVBoxLayout()
1042         msg = _('Please enter your password')
1043         vbox.addWidget(QLabel(msg))
1044
1045         grid = QGridLayout()
1046         grid.setSpacing(8)
1047         grid.addWidget(QLabel(_('Password')), 1, 0)
1048         grid.addWidget(pw, 1, 1)
1049         vbox.addLayout(grid)
1050
1051         vbox.addLayout(ok_cancel_buttons(d))
1052         d.setLayout(vbox) 
1053
1054         if not d.exec_(): return
1055         return unicode(pw.text())
1056
1057
1058
1059
1060
1061     @staticmethod
1062     def change_password_dialog( wallet, parent=None ):
1063
1064         if not wallet.seed:
1065             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1066             return
1067
1068         d = QDialog(parent)
1069         d.setModal(1)
1070
1071         pw = QLineEdit()
1072         pw.setEchoMode(2)
1073         new_pw = QLineEdit()
1074         new_pw.setEchoMode(2)
1075         conf_pw = QLineEdit()
1076         conf_pw.setEchoMode(2)
1077
1078         vbox = QVBoxLayout()
1079         if parent:
1080             msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'+_('To disable wallet encryption, enter an empty new password.')) if wallet.use_encryption else _('Your wallet keys are not encrypted')
1081         else:
1082             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1083         vbox.addWidget(QLabel(msg))
1084
1085         grid = QGridLayout()
1086         grid.setSpacing(8)
1087
1088         if wallet.use_encryption:
1089             grid.addWidget(QLabel(_('Password')), 1, 0)
1090             grid.addWidget(pw, 1, 1)
1091
1092         grid.addWidget(QLabel(_('New Password')), 2, 0)
1093         grid.addWidget(new_pw, 2, 1)
1094
1095         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1096         grid.addWidget(conf_pw, 3, 1)
1097         vbox.addLayout(grid)
1098
1099         vbox.addLayout(ok_cancel_buttons(d))
1100         d.setLayout(vbox) 
1101
1102         if not d.exec_(): return
1103
1104         password = unicode(pw.text()) if wallet.use_encryption else None
1105         new_password = unicode(new_pw.text())
1106         new_password2 = unicode(conf_pw.text())
1107
1108         try:
1109             seed = wallet.pw_decode( wallet.seed, password)
1110         except:
1111             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1112             return
1113
1114         if new_password != new_password2:
1115             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1116             return
1117
1118         wallet.update_password(seed, password, new_password)
1119
1120     @staticmethod
1121     def seed_dialog(wallet, parent=None):
1122         d = QDialog(parent)
1123         d.setModal(1)
1124
1125         vbox = QVBoxLayout()
1126         msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1127         vbox.addWidget(QLabel(msg))
1128
1129         grid = QGridLayout()
1130         grid.setSpacing(8)
1131
1132         seed_e = QLineEdit()
1133         grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1134         grid.addWidget(seed_e, 1, 1)
1135
1136         gap_e = QLineEdit()
1137         gap_e.setText("5")
1138         grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1139         grid.addWidget(gap_e, 2, 1)
1140         gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1141         vbox.addLayout(grid)
1142
1143         vbox.addLayout(ok_cancel_buttons(d))
1144         d.setLayout(vbox) 
1145
1146         if not d.exec_(): return
1147
1148         try:
1149             gap = int(unicode(gap_e.text()))
1150         except:
1151             QMessageBox.warning(None, _('Error'), 'error', 'OK')
1152             sys.exit(0)
1153
1154         try:
1155             seed = unicode(seed_e.text())
1156             seed.decode('hex')
1157         except:
1158             print_error("Warning: Not hex, trying decode")
1159             try:
1160                 seed = mnemonic.mn_decode( seed.split(' ') )
1161             except:
1162                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1163                 sys.exit(0)
1164         if not seed:
1165             QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1166             sys.exit(0)
1167         
1168         wallet.seed = str(seed)
1169         #print repr(wallet.seed)
1170         wallet.gap_limit = gap
1171         return True
1172
1173
1174     def set_expert_mode(self, b):
1175         self.wallet.expert_mode = b
1176         self.wallet.save()
1177         self.update_receive_tab()
1178         self.update_contacts_tab()
1179         # if self.wallet.seed:
1180         # self.nochange_cb.setHidden(not self.wallet.expert_mode)
1181         
1182
1183     def settings_dialog(self):
1184         d = QDialog(self)
1185         d.setModal(1)
1186         vbox = QVBoxLayout()
1187         msg = _('Here are the settings of your wallet.') + '\n'\
1188               + _('For more explanations, click on the help buttons next to each field.')
1189
1190         label = QLabel(msg)
1191         label.setFixedWidth(250)
1192         label.setWordWrap(True)
1193         label.setAlignment(Qt.AlignJustify)
1194         vbox.addWidget(label)
1195
1196         grid = QGridLayout()
1197         grid.setSpacing(8)
1198         vbox.addLayout(grid)
1199
1200         fee_e = QLineEdit()
1201         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1202         grid.addWidget(QLabel(_('Transaction fee')), 2, 0)
1203         grid.addWidget(fee_e, 2, 1)
1204         msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1205             + _('Recommended value') + ': 0.001'
1206         grid.addWidget(HelpButton(msg), 2, 2)
1207         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1208
1209         nz_e = QLineEdit()
1210         nz_e.setText("%d"% self.wallet.num_zeros)
1211         grid.addWidget(QLabel(_('Display zeros')), 3, 0)
1212         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1213         grid.addWidget(HelpButton(msg), 3, 2)
1214         grid.addWidget(nz_e, 3, 1)
1215         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1216
1217         cb = QCheckBox(_('Expert mode'))
1218         grid.addWidget(cb, 4, 0)
1219         cb.setChecked(self.wallet.expert_mode)
1220
1221         if self.wallet.expert_mode:
1222
1223             usechange_cb = QCheckBox(_('Use change addresses'))
1224             grid.addWidget(usechange_cb, 5, 0)
1225             usechange_cb.setChecked(self.wallet.use_change)
1226             grid.addWidget(HelpButton(_('Using a change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1227
1228             msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1229                   + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1230                   + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1231                   + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1232                   + _('Warning') + ': ' \
1233                   + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1234                   + _('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' 
1235             gap_e = QLineEdit()
1236             gap_e.setText("%d"% self.wallet.gap_limit)
1237             grid.addWidget(QLabel(_('Gap limit')), 6, 0)
1238             grid.addWidget(gap_e, 6, 1)
1239             grid.addWidget(HelpButton(msg), 6, 2)
1240             gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1241
1242         
1243         vbox.addLayout(ok_cancel_buttons(d))
1244         d.setLayout(vbox) 
1245
1246         # run the dialog
1247         if not d.exec_(): return
1248
1249         fee = unicode(fee_e.text())
1250         try:
1251             fee = int( 100000000 * Decimal(fee) )
1252         except:
1253             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1254             return
1255
1256         if self.wallet.fee != fee:
1257             self.wallet.fee = fee
1258             self.wallet.save()
1259         
1260         nz = unicode(nz_e.text())
1261         try:
1262             nz = int( nz )
1263             if nz>8: nz=8
1264         except:
1265             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1266             return
1267
1268         if self.wallet.num_zeros != nz:
1269             self.wallet.num_zeros = nz
1270             self.update_history_tab()
1271             self.update_receive_tab()
1272             self.wallet.save()
1273
1274         if self.wallet.expert_mode:
1275
1276             self.wallet.use_change = usechange_cb.isChecked()
1277
1278             try:
1279                 n = int(gap_e.text())
1280             except:
1281                 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1282                 return
1283             if self.wallet.gap_limit != n:
1284                 r = self.wallet.change_gap_limit(n)
1285                 if r:
1286                     self.update_receive_tab()
1287                 else:
1288                     QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1289
1290         self.set_expert_mode(cb.isChecked())
1291
1292
1293     @staticmethod 
1294     def network_dialog(wallet, parent=None):
1295         interface = wallet.interface
1296         if parent:
1297             if interface.is_connected:
1298                 status = _("Connected to")+" %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks)
1299             else:
1300                 status = _("Not connected")
1301             server = wallet.server
1302         else:
1303             import random
1304             status = _("Please choose a server.")
1305             server = random.choice( DEFAULT_SERVERS )
1306
1307         if not wallet.interface.servers:
1308             servers_list = []
1309             for x in DEFAULT_SERVERS:
1310                 h,port,protocol = x.split(':')
1311                 servers_list.append( (h,[(protocol,port)] ) )
1312         else:
1313             servers_list = wallet.interface.servers
1314             
1315         plist = {}
1316         for item in servers_list:
1317             host, pp = item
1318             z = {}
1319             for item2 in pp:
1320                 protocol, port = item2
1321                 z[protocol] = port
1322             plist[host] = z
1323
1324         d = QDialog(parent)
1325         d.setModal(1)
1326         d.setWindowTitle(_('Server'))
1327         d.setMinimumSize(375, 20)
1328
1329         vbox = QVBoxLayout()
1330         vbox.setSpacing(20)
1331
1332         hbox = QHBoxLayout()
1333         l = QLabel()
1334         l.setPixmap(QPixmap(":icons/network.png"))
1335         hbox.addWidget(l)        
1336         hbox.addWidget(QLabel(status))
1337
1338         vbox.addLayout(hbox)
1339
1340         hbox = QHBoxLayout()
1341         host_line = QLineEdit()
1342         host_line.setText(server)
1343         hbox.addWidget(QLabel(_('Connect to') + ':'))
1344         hbox.addWidget(host_line)
1345         vbox.addLayout(hbox)
1346
1347         hbox = QHBoxLayout()
1348
1349         buttonGroup = QGroupBox(_("Protocol"))
1350         radio1 = QRadioButton("tcp", buttonGroup)
1351         radio2 = QRadioButton("http", buttonGroup)
1352
1353         def current_line():
1354             return unicode(host_line.text()).split(':')
1355             
1356         def set_button(protocol):
1357             if protocol == 't':
1358                 radio1.setChecked(1)
1359             elif protocol == 'h':
1360                 radio2.setChecked(1)
1361
1362         def set_protocol(protocol):
1363             host = current_line()[0]
1364             pp = plist[host]
1365             if protocol not in pp.keys():
1366                 protocol = pp.keys()[0]
1367                 set_button(protocol)
1368             port = pp[protocol]
1369             host_line.setText( host + ':' + port + ':' + protocol)
1370
1371         radio1.clicked.connect(lambda x: set_protocol('t') )
1372         radio2.clicked.connect(lambda x: set_protocol('h') )
1373
1374         set_button(current_line()[2])
1375
1376         hbox.addWidget(QLabel(_('Protocol')+':'))
1377         hbox.addWidget(radio1)
1378         hbox.addWidget(radio2)
1379
1380         vbox.addLayout(hbox)
1381
1382         if wallet.interface.servers:
1383             label = _('Active Servers')
1384         else:
1385             label = _('Default Servers')
1386         
1387         servers_list_widget = QTreeWidget(parent)
1388         servers_list_widget.setHeaderLabels( [ label ] )
1389         servers_list_widget.setMaximumHeight(150)
1390         for host in plist.keys():
1391             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ host ] ))
1392
1393         def do_set_line(x):
1394             host = unicode(x.text(0))
1395             pp = plist[host]
1396             if 't' in pp.keys():
1397                 protocol = 't'
1398             else:
1399                 protocol = pp.keys()[0]
1400             port = pp[protocol]
1401             host_line.setText( host + ':' + port + ':' + protocol)
1402             set_button(protocol)
1403
1404         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line)
1405         vbox.addWidget(servers_list_widget)
1406
1407         vbox.addLayout(ok_cancel_buttons(d))
1408         d.setLayout(vbox) 
1409
1410         if not d.exec_(): return
1411         server = unicode( host_line.text() )
1412
1413         try:
1414             wallet.set_server(server)
1415         except:
1416             QMessageBox.information(None, _('Error'), 'error', _('OK'))
1417             if parent == None:
1418                 sys.exit(1)
1419             else:
1420                 return
1421
1422         return True
1423
1424
1425
1426 class ElectrumGui:
1427
1428     def __init__(self, wallet, app=None):
1429         self.wallet = wallet
1430         if app is None:
1431             self.app = QApplication(sys.argv)
1432
1433     def server_list_changed(self):
1434         pass
1435
1436     def waiting_dialog(self):
1437
1438         s = Timer()
1439         s.start()
1440         w = QDialog()
1441         w.resize(200, 70)
1442         w.setWindowTitle('Electrum')
1443         l = QLabel('')
1444         vbox = QVBoxLayout()
1445         vbox.addWidget(l)
1446         w.setLayout(vbox)
1447         w.show()
1448         def f():
1449             if self.wallet.up_to_date: 
1450                 w.close()
1451             else:
1452                 l.setText("Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1453                               %(len(self.wallet.all_addresses()), self.wallet.interface.bytes_received/1024.))
1454
1455         w.connect(s, QtCore.SIGNAL('timersignal'), f)
1456         self.wallet.interface.poke()
1457         w.exec_()
1458         w.destroy()
1459
1460
1461     def restore_or_create(self):
1462
1463         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1464         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1465         if r==2: return False
1466         
1467         is_recovery = (r==1)
1468         wallet = self.wallet
1469         # ask for the server.
1470         if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1471
1472         if not is_recovery:
1473             wallet.new_seed(None)
1474             wallet.init_mpk( wallet.seed )
1475             wallet.up_to_date_event.clear()
1476             wallet.up_to_date = False
1477             self.waiting_dialog()
1478             # run a dialog indicating the seed, ask the user to remember it
1479             ElectrumWindow.show_seed_dialog(wallet)
1480             #ask for password
1481             ElectrumWindow.change_password_dialog(wallet)
1482         else:
1483             # ask for seed and gap.
1484             if not ElectrumWindow.seed_dialog( wallet ): return False
1485             wallet.init_mpk( wallet.seed )
1486             wallet.up_to_date_event.clear()
1487             wallet.up_to_date = False
1488             self.waiting_dialog()
1489             if wallet.is_found():
1490                 # history and addressbook
1491                 wallet.update_tx_history()
1492                 wallet.fill_addressbook()
1493                 print "Recovery successful"
1494                 wallet.save()
1495             else:
1496                 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1497
1498         wallet.save()
1499         return True
1500
1501     def main(self,url):
1502         s = Timer()
1503         s.start()
1504         w = ElectrumWindow(self.wallet)
1505         if url: w.set_url(url)
1506         w.app = self.app
1507         w.connect_slots(s)
1508         w.update_wallet()
1509         w.show()
1510
1511         self.app.exec_()