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