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