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