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