3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
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.
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.
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/>.
19 import sys, time, datetime, re
23 from PyQt4.QtGui import *
24 from PyQt4.QtCore import *
25 import PyQt4.QtCore as QtCore
26 import PyQt4.QtGui as QtGui
27 from interface import DEFAULT_SERVERS
32 print "Could not import icons_rp.py"
33 print "Please generate it with: 'pyrcc4 icons.qrc -o icons_rc.py'"
36 from wallet import format_satoshis
37 from decimal import Decimal
40 MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace'
43 def numbify(entry, is_int = False):
44 text = unicode(entry.text()).strip()
46 if not is_int: chars +='.'
47 s = ''.join([i for i in text if i in chars])
52 s = s[:p] + '.' + s[p:p+8]
54 amount = int( Decimal(s) * 100000000 )
66 class Timer(QtCore.QThread):
69 self.emit(QtCore.SIGNAL('timersignal'))
72 class EnterButton(QPushButton):
73 def __init__(self, text, func):
74 QPushButton.__init__(self, text)
76 self.clicked.connect(func)
78 def keyPressEvent(self, e):
79 if e.key() == QtCore.Qt.Key_Return:
82 class StatusBarButton(QPushButton):
83 def __init__(self, icon, tooltip, func):
84 QPushButton.__init__(self, icon, '')
85 self.setToolTip(tooltip)
87 self.setMaximumWidth(25)
88 self.clicked.connect(func)
91 def keyPressEvent(self, e):
92 if e.key() == QtCore.Qt.Key_Return:
96 class QRCodeWidget(QWidget):
98 def __init__(self, addr):
99 super(QRCodeWidget, self).__init__()
100 self.setGeometry(300, 300, 350, 350)
103 def set_addr(self, addr):
106 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
107 self.qr.addData(addr)
110 def paintEvent(self, e):
111 qp = QtGui.QPainter()
114 size = self.qr.getModuleCount()*boxsize
115 k = self.qr.getModuleCount()
116 black = QColor(0, 0, 0, 255)
117 white = QColor(255, 255, 255, 255)
120 if self.qr.isDark(r, c):
126 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
131 def ok_cancel_buttons(dialog):
134 b = QPushButton("OK")
136 b.clicked.connect(dialog.accept)
137 b = QPushButton("Cancel")
139 b.clicked.connect(dialog.reject)
143 class ElectrumWindow(QMainWindow):
145 def __init__(self, wallet):
146 QMainWindow.__init__(self)
148 self.wallet.gui_callback = self.update_callback
150 self.funds_error = False
152 self.tabs = tabs = QTabWidget(self)
153 tabs.addTab(self.create_history_tab(), 'History')
154 tabs.addTab(self.create_send_tab(), 'Send')
155 tabs.addTab(self.create_receive_tab(), 'Receive')
156 tabs.addTab(self.create_contacts_tab(),'Contacts')
157 tabs.addTab(self.create_wall_tab(), 'Wall')
158 tabs.setMinimumSize(600, 400)
159 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
160 self.setCentralWidget(tabs)
161 self.create_status_bar()
162 self.setGeometry(100,100,840,400)
163 self.setWindowTitle( 'Electrum ' + self.wallet.electrum_version )
166 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
167 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
168 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
169 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
171 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
174 def connect_slots(self, sender):
175 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
176 self.previous_payto_e=''
178 def check_recipient(self):
179 if self.payto_e.hasFocus():
181 r = unicode( self.payto_e.text() )
182 if r != self.previous_payto_e:
183 self.previous_payto_e = r
185 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
187 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
191 s = r + ' <' + to_address + '>'
192 self.payto_e.setText(s)
195 def update_callback(self):
196 self.emit(QtCore.SIGNAL('updatesignal'))
198 def update_wallet(self):
199 if self.wallet.interface.is_connected:
200 if self.wallet.blocks == -1:
201 text = "Connecting..."
202 icon = QIcon(":icons/status_disconnected.png")
203 elif self.wallet.blocks == 0:
204 text = "Server not ready"
205 icon = QIcon(":icons/status_disconnected.png")
206 elif not self.wallet.up_to_date:
207 text = "Synchronizing..."
208 icon = QIcon(":icons/status_waiting.png")
210 c, u = self.wallet.get_balance()
211 text = "Balance: %s "%( format_satoshis(c) )
212 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True).strip() )
213 icon = QIcon(":icons/status_connected.png")
215 text = "Not connected"
216 icon = QIcon(":icons/status_disconnected.png")
219 text = "Not enough funds"
221 self.statusBar().showMessage(text)
222 self.status_button.setIcon( icon )
224 if self.wallet.up_to_date:
225 self.textbox.setText( self.wallet.banner )
226 self.update_history_tab()
227 self.update_receive_tab()
228 self.update_contacts_tab()
231 def create_history_tab(self):
232 self.history_list = w = QTreeWidget(self)
233 #print w.getContentsMargins()
235 w.setColumnWidth(0, 40)
236 w.setColumnWidth(1, 140)
237 w.setColumnWidth(2, 350)
238 w.setColumnWidth(3, 140)
239 w.setColumnWidth(4, 140)
240 w.setHeaderLabels( [ '', 'Date', 'Description', 'Amount', 'Balance'] )
241 self.connect(w, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.tx_details)
242 self.connect(w, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
243 self.connect(w, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
246 def tx_details(self, item, column):
247 tx_hash = str(item.toolTip(0))
248 tx = self.wallet.tx_history.get(tx_hash)
251 conf = self.wallet.blocks - tx['height'] + 1
252 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
257 tx_details = "Transaction Details:\n\n" \
258 + "Transaction ID:\n" + tx_hash + "\n\n" \
259 + "Status: %d confirmations\n\n"%conf \
260 + "Date: %s\n\n"%time_str \
261 + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
262 + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
264 r = self.wallet.receipts.get(tx_hash)
266 tx_details += "\n_______________________________________" \
267 + '\n\nSigned URI: ' + r[2] \
268 + "\n\nSigned by: " + r[0] \
269 + '\n\nSignature: ' + r[1]
271 QMessageBox.information(self, 'Details', tx_details, 'OK')
274 def tx_label_clicked(self, item, column):
275 if column==2 and item.isSelected():
276 tx_hash = str(item.toolTip(0))
278 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
279 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
280 self.history_list.editItem( item, column )
281 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
284 def tx_label_changed(self, item, column):
288 tx_hash = str(item.toolTip(0))
289 tx = self.wallet.tx_history.get(tx_hash)
290 s = self.wallet.labels.get(tx_hash)
291 text = unicode( item.text(2) )
293 self.wallet.labels[tx_hash] = text
294 item.setForeground(2, QBrush(QColor('black')))
296 if s: self.wallet.labels.pop(tx_hash)
297 text = tx['default_label']
298 item.setText(2, text)
299 item.setForeground(2, QBrush(QColor('gray')))
302 def address_label_clicked(self, item, column, l):
303 if column==1 and item.isSelected():
304 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
305 l.editItem( item, column )
306 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
308 def address_label_changed(self, item, column, l):
309 addr = unicode( item.text(0) )
310 text = unicode( item.text(1) )
312 self.wallet.labels[addr] = text
314 s = self.wallet.labels.get(addr)
315 if s: self.wallet.labels.pop(addr)
316 self.update_history_tab()
318 def update_history_tab(self):
319 self.history_list.clear()
321 for tx in self.wallet.get_tx_history():
322 tx_hash = tx['tx_hash']
324 conf = self.wallet.blocks - tx['height'] + 1
325 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
326 icon = QIcon(":icons/confirmed.png")
330 icon = QIcon(":icons/unconfirmed.png")
333 label = self.wallet.labels.get(tx_hash)
334 is_default_label = (label == '') or (label is None)
335 if is_default_label: label = tx['default_label']
337 item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True), format_satoshis(balance)] )
338 item.setFont(2, QFont(MONOSPACE_FONT))
339 item.setFont(3, QFont(MONOSPACE_FONT))
340 item.setFont(4, QFont(MONOSPACE_FONT))
341 item.setToolTip(0, tx_hash)
343 item.setForeground(2, QBrush(QColor('grey')))
345 item.setIcon(0, icon)
346 self.history_list.insertTopLevelItem(0,item)
349 def create_send_tab(self):
354 grid.setColumnMinimumWidth(3,300)
355 grid.setColumnStretch(4,1)
357 self.payto_e = QLineEdit()
358 grid.addWidget(QLabel('Pay to'), 1, 0)
359 grid.addWidget(self.payto_e, 1, 1, 1, 3)
361 self.message_e = QLineEdit()
362 grid.addWidget(QLabel('Description'), 2, 0)
363 grid.addWidget(self.message_e, 2, 1, 1, 3)
365 self.amount_e = QLineEdit()
366 grid.addWidget(QLabel('Amount'), 3, 0)
367 grid.addWidget(self.amount_e, 3, 1, 1, 2)
369 self.fee_e = QLineEdit()
370 grid.addWidget(QLabel('Fee'), 4, 0)
371 grid.addWidget(self.fee_e, 4, 1, 1, 2)
373 b = EnterButton("Send", self.do_send)
374 grid.addWidget(b, 5, 1)
376 b = EnterButton("Clear",self.do_clear)
377 grid.addWidget(b, 5, 2)
379 self.payto_sig = QLabel('')
380 grid.addWidget(self.payto_sig, 6, 0, 1, 4)
391 def entry_changed( is_fee ):
392 self.funds_error = False
393 amount = numbify(self.amount_e)
394 fee = numbify(self.fee_e)
395 if not is_fee: fee = None
398 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
400 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
403 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
406 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
407 self.funds_error = True
408 self.amount_e.setPalette(palette)
409 self.fee_e.setPalette(palette)
411 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
412 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
418 label = unicode( self.message_e.text() )
419 r = unicode( self.payto_e.text() )
422 m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
423 m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
426 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
430 to_address = m2.group(5)
434 if not self.wallet.is_valid(to_address):
435 QMessageBox.warning(self, 'Error', 'Invalid Bitcoin Address:\n'+to_address, 'OK')
439 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
441 QMessageBox.warning(self, 'Error', 'Invalid Amount', 'OK')
444 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
446 QMessageBox.warning(self, 'Error', 'Invalid Fee', 'OK')
449 if self.wallet.use_encryption:
450 password = self.password_dialog()
457 tx = self.wallet.mktx( to_address, amount, label, password, fee )
458 except BaseException, e:
459 self.show_message(e.message)
462 status, msg = self.wallet.sendtx( tx )
464 QMessageBox.information(self, '', 'Payment sent.\n'+msg, 'OK')
466 self.update_contacts_tab()
468 QMessageBox.warning(self, 'Error', msg, 'OK')
471 def set_url(self, url):
472 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
473 self.tabs.setCurrentIndex(1)
474 self.payto_e.setText(payto)
475 self.message_e.setText(message)
476 self.amount_e.setText(amount)
478 self.set_frozen(self.payto_e,True)
479 self.set_frozen(self.amount_e,True)
480 self.set_frozen(self.message_e,True)
481 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
483 self.payto_sig.setVisible(False)
486 self.payto_sig.setVisible(False)
487 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
489 self.set_frozen(e,False)
491 def set_frozen(self,entry,frozen):
493 entry.setReadOnly(True)
494 entry.setFrame(False)
496 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
497 entry.setPalette(palette)
499 entry.setReadOnly(False)
502 palette.setColor(entry.backgroundRole(), QColor('white'))
503 entry.setPalette(palette)
506 def make_address_list(self, is_recv):
508 l = QTreeWidget(self)
510 l.setColumnWidth(0, 350)
511 l.setColumnWidth(1, 330)
512 l.setColumnWidth(2, 20)
513 l.setHeaderLabels( ['Address', 'Label','Tx'])
527 addr = unicode( i.text(0) )
530 qrButton = EnterButton("QR",lambda: self.show_address_qrcode(get_addr(l)))
532 def copy2clipboard(addr):
533 self.app.clipboard().setText(addr)
534 copyButton = EnterButton("Copy to Clipboard", lambda: copy2clipboard(get_addr(l)))
535 hbox.addWidget(qrButton)
536 hbox.addWidget(copyButton)
538 addButton = EnterButton("New", self.newaddress_dialog)
539 hbox.addWidget(addButton)
542 self.tabs.setCurrentIndex(1)
543 self.payto_e.setText(addr)
544 self.amount_e.setFocus()
545 paytoButton = EnterButton('Pay to', lambda: payto(get_addr(l)))
546 hbox.addWidget(paytoButton)
549 buttons.setLayout(hbox)
550 vbox.addWidget(buttons)
556 def create_receive_tab(self):
557 w, l = self.make_address_list(True)
558 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l))
559 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l))
560 self.receive_list = l
563 def create_contacts_tab(self):
564 w, l = self.make_address_list(False)
565 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l))
566 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l))
567 self.connect(l, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.show_contact_details)
568 self.contacts_list = l
571 def update_receive_tab(self):
572 self.receive_list.clear()
573 for address in self.wallet.all_addresses():
574 if self.wallet.is_change(address):continue
575 label = self.wallet.labels.get(address,'')
577 h = self.wallet.history.get(address,[])
579 if not item['is_input'] : n=n+1
580 tx = "None" if n==0 else "%d"%n
581 item = QTreeWidgetItem( [ address, label, tx] )
582 item.setFont(0, QFont(MONOSPACE_FONT))
583 self.receive_list.addTopLevelItem(item)
585 def show_contact_details(self, item, column):
586 m = unicode(item.text(0))
587 a = self.wallet.aliases.get(m)
589 if a[0] in self.wallet.authorities.keys():
590 s = self.wallet.authorities.get(a[0])
593 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
594 QMessageBox.information(self, 'Alias', msg, 'OK')
596 def update_contacts_tab(self):
597 self.contacts_list.clear()
598 for alias, v in self.wallet.aliases.items():
600 label = self.wallet.labels.get(alias,'')
601 item = QTreeWidgetItem( [ alias, label, '-'] )
602 self.contacts_list.addTopLevelItem(item)
604 for address in self.wallet.addressbook:
605 label = self.wallet.labels.get(address,'')
607 for item in self.wallet.tx_history.values():
608 if address in item['outputs'] : n=n+1
609 tx = "None" if n==0 else "%d"%n
610 item = QTreeWidgetItem( [ address, label, tx] )
611 item.setFont(0, QFont(MONOSPACE_FONT))
612 self.contacts_list.addTopLevelItem(item)
615 def create_wall_tab(self):
616 self.textbox = textbox = QTextEdit(self)
617 textbox.setFont(QFont(MONOSPACE_FONT))
618 textbox.setReadOnly(True)
621 def create_status_bar(self):
623 sb.setFixedHeight(35)
624 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
625 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
626 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
627 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
628 sb.addPermanentWidget( self.status_button )
629 self.setStatusBar(sb)
631 def newaddress_dialog(self):
632 text, ok = QInputDialog.getText(self, 'New Contact', 'Address:')
633 address = unicode(text)
635 if self.wallet.is_valid(address):
636 self.wallet.addressbook.append(address)
638 self.update_contacts_tab()
640 QMessageBox.warning(self, 'Error', 'Invalid Address', 'OK')
643 def show_seed_dialog(wallet, parent=None):
645 if wallet.use_encryption:
646 password = parent.password_dialog()
647 if not password: return
652 seed = wallet.pw_decode( wallet.seed, password)
654 QMessageBox.warning(parent, 'Error', 'Invalid Password', 'OK')
657 msg = "Your wallet generation seed is:\n\n" + seed \
658 + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \
659 + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" \
660 + ' '.join(mnemonic.mn_encode(seed)) + "\""
662 QMessageBox.information(parent, 'Seed', msg, 'OK')
663 if parent: ElectrumWindow.show_seed_qrcode(seed)
666 def show_seed_qrcode(seed):
670 d.setWindowTitle(seed)
671 d.setMinimumSize(270, 300)
673 vbox.addWidget(QRCodeWidget(seed))
676 b = QPushButton("OK")
678 b.clicked.connect(d.accept)
684 def show_address_qrcode(self,address):
685 if not address: return
688 d.setWindowTitle(address)
689 d.setMinimumSize(270, 350)
691 qrw = QRCodeWidget(address)
695 amount_e = QLineEdit()
696 hbox.addWidget(QLabel('Amount'))
697 hbox.addWidget(amount_e)
700 #hbox = QHBoxLayout()
701 #label_e = QLineEdit()
702 #hbox.addWidget(QLabel('Label'))
703 #hbox.addWidget(label_e)
704 #vbox.addLayout(hbox)
706 def amount_changed():
707 amount = numbify(amount_e)
708 #label = str( label_e.getText() )
709 if amount is not None:
710 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
712 qrw.set_addr( address )
717 bmp.save_qrcode(qrw.qr, "qrcode.bmp")
718 self.show_message("QR code saved to file 'qrcode.bmp'")
720 amount_e.textChanged.connect( amount_changed )
724 b = QPushButton("Save")
725 b.clicked.connect(do_save)
727 b = QPushButton("Close")
729 b.clicked.connect(d.accept)
735 def question(self, msg):
736 return QMessageBox.question(self, 'Message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
738 def show_message(self, msg):
739 QMessageBox.information(self, 'Message', msg, 'OK')
741 def password_dialog(self ):
749 msg = 'Please enter your password'
750 vbox.addWidget(QLabel(msg))
754 grid.addWidget(QLabel('Password'), 1, 0)
755 grid.addWidget(pw, 1, 1)
758 vbox.addLayout(ok_cancel_buttons(d))
761 if not d.exec_(): return
762 return unicode(pw.text())
765 def change_password_dialog( wallet, parent=None ):
772 new_pw.setEchoMode(2)
773 conf_pw = QLineEdit()
774 conf_pw.setEchoMode(2)
778 msg = 'Your wallet is encrypted. Use this dialog to change your password.\nTo disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted'
780 msg = "Please choose a password to encrypt your wallet keys.\nLeave these fields empty if you want to disable encryption."
781 vbox.addWidget(QLabel(msg))
786 if wallet.use_encryption:
787 grid.addWidget(QLabel('Password'), 1, 0)
788 grid.addWidget(pw, 1, 1)
790 grid.addWidget(QLabel('New Password'), 2, 0)
791 grid.addWidget(new_pw, 2, 1)
793 grid.addWidget(QLabel('Confirm Password'), 3, 0)
794 grid.addWidget(conf_pw, 3, 1)
797 vbox.addLayout(ok_cancel_buttons(d))
800 if not d.exec_(): return
802 password = unicode(pw.text()) if wallet.use_encryption else None
803 new_password = unicode(new_pw.text())
804 new_password2 = unicode(conf_pw.text())
807 seed = wallet.pw_decode( wallet.seed, password)
809 QMessageBox.warning(parent, 'Error', 'Incorrect Password', 'OK')
812 if new_password != new_password2:
813 QMessageBox.warning(parent, 'Error', 'Passwords do not match', 'OK')
816 wallet.update_password(seed, new_password)
819 def seed_dialog(wallet, parent=None):
824 msg = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
825 vbox.addWidget(QLabel(msg))
831 grid.addWidget(QLabel('Seed or mnemonic'), 1, 0)
832 grid.addWidget(seed_e, 1, 1)
836 grid.addWidget(QLabel('Gap limit'), 2, 0)
837 grid.addWidget(gap_e, 2, 1)
838 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
841 vbox.addLayout(ok_cancel_buttons(d))
844 if not d.exec_(): return
847 gap = int(unicode(gap_e.text()))
849 QMessageBox.warning(None, 'Error', 'error', 'OK')
853 seed = unicode(seed_e.text())
857 print "not hex, trying decode"
859 seed = mnemonic.mn_decode( seed.split(' ') )
861 QMessageBox.warning(None, 'Error', 'I cannot decode this', 'OK')
864 QMessageBox.warning(None, 'Error', 'no seed', 'OK')
867 wallet.seed = str(seed)
868 #print repr(wallet.seed)
869 wallet.gap_limit = gap
873 def settings_dialog(self):
879 msg = 'Here are the settings of your wallet.'
880 vbox.addWidget(QLabel(msg))
886 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
887 grid.addWidget(QLabel('Fee per tx. input'), 2, 0)
888 grid.addWidget(fee_e, 2, 1)
890 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
892 vbox.addLayout(ok_cancel_buttons(d))
895 if not d.exec_(): return
897 fee = unicode(fee_e.text())
899 fee = int( 100000000 * Decimal(fee) )
901 QMessageBox.warning(self, 'Error', 'Invalid value:%s'%fee, 'OK')
904 self.wallet.fee = fee
908 def network_dialog(wallet, parent=None):
909 interface = wallet.interface
911 if interface.is_connected:
912 status = "Connected to %s:%d\n%d blocks\nresponse time: %f"%(interface.host, interface.port, wallet.blocks, interface.rtime)
914 status = "Not connected"
915 server = wallet.server
918 status = "Please choose a server."
919 server = random.choice( DEFAULT_SERVERS )
922 for item in wallet.interface.servers:
926 protocol, port = item2
932 d.setWindowTitle('Server')
933 d.setMinimumSize(375, 20)
940 l.setPixmap(QPixmap(":icons/network.png"))
942 hbox.addWidget(QLabel(status))
947 host_line = QLineEdit()
948 host_line.setText(server)
949 hbox.addWidget(QLabel('Connect to:'))
950 hbox.addWidget(host_line)
955 buttonGroup = QGroupBox("protocol")
956 radio1 = QRadioButton("tcp", buttonGroup)
957 radio2 = QRadioButton("http", buttonGroup)
960 return unicode(host_line.text()).split(':')
962 def set_button(protocol):
965 elif protocol == 'h':
968 def set_protocol(protocol):
969 host = current_line()[0]
971 if protocol not in pp.keys():
972 protocol = pp.keys()[0]
975 host_line.setText( host + ':' + port + ':' + protocol)
977 radio1.clicked.connect(lambda x: set_protocol('t') )
978 radio2.clicked.connect(lambda x: set_protocol('h') )
980 set_button(current_line()[2])
982 hbox.addWidget(QLabel('Protocol:'))
983 hbox.addWidget(radio1)
984 hbox.addWidget(radio2)
988 if wallet.interface.servers:
989 servers_list = QTreeWidget(parent)
990 servers_list.setHeaderLabels( [ 'Active servers'] )
991 servers_list.setMaximumHeight(150)
992 for host in plist.keys():
993 servers_list.addTopLevelItem(QTreeWidgetItem( [ host ] ))
996 host = unicode(x.text(0))
1001 protocol = pp.keys()[0]
1003 host_line.setText( host + ':' + port + ':' + protocol)
1004 set_button(protocol)
1006 servers_list.connect(servers_list, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line)
1007 vbox.addWidget(servers_list)
1009 hbox = QHBoxLayout()
1010 hbox.addWidget(QLabel('No nodes available'))
1011 b = EnterButton("Find nodes", lambda: wallet.interface.get_servers(wallet) )
1013 vbox.addLayout(hbox)
1015 vbox.addLayout(ok_cancel_buttons(d))
1018 if not d.exec_(): return
1019 server = unicode( host_line.text() )
1022 wallet.set_server(server)
1024 QMessageBox.information(None, 'Error', 'error', 'OK')
1034 class ElectrumGui():
1036 def __init__(self, wallet):
1037 self.wallet = wallet
1038 self.app = QApplication(sys.argv)
1040 def waiting_dialog(self):
1046 w.setWindowTitle('Electrum')
1048 vbox = QVBoxLayout()
1053 if self.wallet.up_to_date: w.close()
1055 l.setText("Please wait...\nGenerating addresses: %d"%len(self.wallet.all_addresses()))
1057 w.connect(s, QtCore.SIGNAL('timersignal'), f)
1058 self.wallet.interface.poke()
1063 def restore_or_create(self):
1065 msg = "Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?"
1066 r = QMessageBox.question(None, 'Message', msg, 'create', 'restore', 'cancel', 0, 2)
1067 if r==2: return False
1069 is_recovery = (r==1)
1070 wallet = self.wallet
1071 # ask for the server.
1072 if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1075 wallet.new_seed(None)
1076 wallet.init_mpk( wallet.seed )
1077 wallet.up_to_date_event.clear()
1078 wallet.up_to_date = False
1079 self.waiting_dialog()
1080 # run a dialog indicating the seed, ask the user to remember it
1081 ElectrumWindow.show_seed_dialog(wallet)
1083 ElectrumWindow.change_password_dialog(wallet)
1085 # ask for seed and gap.
1086 if not ElectrumWindow.seed_dialog( wallet ): return False
1087 wallet.init_mpk( wallet.seed )
1088 wallet.up_to_date_event.clear()
1089 wallet.up_to_date = False
1090 self.waiting_dialog()
1091 if wallet.is_found():
1092 # history and addressbook
1093 wallet.update_tx_history()
1094 wallet.fill_addressbook()
1095 print "recovery successful"
1098 QMessageBox.information(None, 'Message', "No transactions found for this seed", 'OK')
1106 w = ElectrumWindow(self.wallet)
1107 if url: w.set_url(url)