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
21 from util import print_error
22 import os.path, json, util
27 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
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
38 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
40 from wallet import format_satoshis
41 import bmp, mnemonic, pyqrnative, qrscanner
44 from decimal import Decimal
51 if platform.system() == 'Windows':
52 MONOSPACE_FONT = 'Lucida Console'
53 elif platform.system() == 'Darwin':
54 MONOSPACE_FONT = 'Monaco'
56 MONOSPACE_FONT = 'monospace'
58 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
60 from version import ELECTRUM_VERSION
63 class UpdateLabel(QtGui.QLabel):
64 def __init__(self, config, parent=None):
65 QtGui.QLabel.__init__(self, parent)
68 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
69 con.request("GET", "/version")
70 res = con.getresponse()
71 except socket.error as msg:
72 print_error("Could not retrieve version information")
76 self.latest_version = res.read()
77 self.latest_version = self.latest_version.replace("\n","")
78 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
80 self.current_version = ELECTRUM_VERSION
81 if(self.compare_versions(self.latest_version, self.current_version) == 1):
82 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
83 if(self.compare_versions(self.latest_version, latest_seen) == 1):
84 self.setText(_("New version available") + ": " + self.latest_version)
87 def compare_versions(self, version1, version2):
89 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
90 return cmp(normalize(version1), normalize(version2))
92 def ignore_this_version(self):
94 self.config.set_key("last_seen_version", self.latest_version, True)
95 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
98 def ignore_all_version(self):
100 self.config.set_key("last_seen_version", "9.9.9", True)
101 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
104 def open_website(self):
105 webbrowser.open("http://electrum.org/download.html")
108 def mouseReleaseEvent(self, event):
109 dialog = QDialog(self)
110 dialog.setWindowTitle(_('Electrum update'))
113 main_layout = QGridLayout()
114 main_layout.addWidget(QLabel("A new version of Electrum is available: " + self.latest_version), 0,0,1,3)
116 ignore_version = QPushButton(_("Ignore this version"))
117 ignore_version.clicked.connect(self.ignore_this_version)
119 ignore_all_versions = QPushButton(_("Ignore all versions"))
120 ignore_all_versions.clicked.connect(self.ignore_all_version)
122 open_website = QPushButton(_("Goto download page"))
123 open_website.clicked.connect(self.open_website)
125 main_layout.addWidget(ignore_version, 1, 0)
126 main_layout.addWidget(ignore_all_versions, 1, 1)
127 main_layout.addWidget(open_website, 1, 2)
129 dialog.setLayout(main_layout)
133 if not dialog.exec_(): return
135 def numbify(entry, is_int = False):
136 text = unicode(entry.text()).strip()
137 pos = entry.cursorPosition()
139 if not is_int: chars +='.'
140 s = ''.join([i for i in text if i in chars])
144 s = s.replace('.','')
145 s = s[:p] + '.' + s[p:p+8]
147 amount = int( Decimal(s) * 100000000 )
156 entry.setCursorPosition(pos)
160 class Timer(QtCore.QThread):
163 self.emit(QtCore.SIGNAL('timersignal'))
166 class HelpButton(QPushButton):
167 def __init__(self, text):
168 QPushButton.__init__(self, '?')
169 self.setFocusPolicy(Qt.NoFocus)
170 self.setFixedWidth(20)
171 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
174 class EnterButton(QPushButton):
175 def __init__(self, text, func):
176 QPushButton.__init__(self, text)
178 self.clicked.connect(func)
180 def keyPressEvent(self, e):
181 if e.key() == QtCore.Qt.Key_Return:
184 class MyTreeWidget(QTreeWidget):
185 def __init__(self, parent):
186 QTreeWidget.__init__(self, parent)
189 for i in range(0,self.viewport().height()/5):
190 if self.itemAt(QPoint(0,i*5)) == item:
194 for j in range(0,30):
195 if self.itemAt(QPoint(0,i*5 + j)) != item:
197 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
199 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
204 class StatusBarButton(QPushButton):
205 def __init__(self, icon, tooltip, func):
206 QPushButton.__init__(self, icon, '')
207 self.setToolTip(tooltip)
209 self.setMaximumWidth(25)
210 self.clicked.connect(func)
213 def keyPressEvent(self, e):
214 if e.key() == QtCore.Qt.Key_Return:
218 class QRCodeWidget(QWidget):
220 def __init__(self, data = None, size=4):
221 QWidget.__init__(self)
222 self.setMinimumSize(210, 210)
230 def set_addr(self, addr):
231 if self.addr != addr:
237 if self.addr and not self.qr:
238 self.qr = pyqrnative.QRCode(self.size, pyqrnative.QRErrorCorrectLevel.L)
239 self.qr.addData(self.addr)
243 def paintEvent(self, e):
248 black = QColor(0, 0, 0, 255)
249 white = QColor(255, 255, 255, 255)
252 qp = QtGui.QPainter()
256 qp.drawRect(0, 0, 198, 198)
260 k = self.qr.getModuleCount()
261 qp = QtGui.QPainter()
264 boxsize = min(r.width(), r.height())*0.8/k
266 left = (r.width() - size)/2
267 top = (r.height() - size)/2
271 if self.qr.isDark(r, c):
277 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
282 class QR_Window(QWidget):
284 def __init__(self, exchanger):
285 QWidget.__init__(self)
286 self.exchanger = exchanger
287 self.setWindowTitle('Electrum - Invoice')
288 self.setMinimumSize(800, 250)
292 self.setFocusPolicy(QtCore.Qt.NoFocus)
294 main_box = QHBoxLayout()
296 self.qrw = QRCodeWidget()
297 main_box.addWidget(self.qrw, 1)
300 main_box.addLayout(vbox)
302 self.address_label = QLabel("")
303 self.address_label.setFont(QFont(MONOSPACE_FONT))
304 vbox.addWidget(self.address_label)
306 self.label_label = QLabel("")
307 vbox.addWidget(self.label_label)
309 self.amount_label = QLabel("")
310 vbox.addWidget(self.amount_label)
313 self.setLayout(main_box)
316 def set_content(self, addr, label, amount, currency):
318 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
319 self.address_label.setText(address_text)
321 if currency == 'BTC': currency = None
325 self.amount = Decimal(amount) / self.exchanger.exchange(1, currency) if currency else amount
327 self.amount = Decimal(amount)
328 self.amount = self.amount.quantize(Decimal('1.0000'))
331 amount_text += "<span style='font-size: 18pt'>%s %s</span><br/>" % (amount, currency)
332 amount_text += "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % str(self.amount)
333 self.amount_label.setText(amount_text)
336 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
337 self.label_label.setText(label_text)
339 msg = 'bitcoin:'+self.address
340 if self.amount is not None:
341 msg += '?amount=%s'%(str( self.amount))
342 if self.label is not None:
343 msg += '&label=%s'%(self.label)
344 elif self.label is not None:
345 msg += '?label=%s'%(self.label)
347 self.qrw.set_addr( msg )
352 def waiting_dialog(f):
358 w.setWindowTitle('Electrum')
368 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
373 def ok_cancel_buttons(dialog):
376 b = QPushButton("OK")
378 b.clicked.connect(dialog.accept)
379 b = QPushButton("Cancel")
381 b.clicked.connect(dialog.reject)
385 default_column_widths = { "history":[40,140,350,140,140], "contacts":[350,330,100],
386 "receive":[[50,310,200,130,130,10],[50,310,200,130,130,10],[50,310,200,130,130,10]] }
388 class ElectrumWindow(QMainWindow):
390 def __init__(self, wallet, config):
391 QMainWindow.__init__(self)
395 self.wallet.interface.register_callback('updated', self.update_callback)
396 self.wallet.interface.register_callback('connected', self.update_callback)
397 self.wallet.interface.register_callback('disconnected', self.update_callback)
398 self.wallet.interface.register_callback('disconnecting', self.update_callback)
400 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
401 self.merchant_name = config.get('merchant_name', 'Invoice')
403 self.qr_window = None
404 self.funds_error = False
405 self.completions = QStringListModel()
407 self.tabs = tabs = QTabWidget(self)
408 self.column_widths = self.config.get("column-widths", default_column_widths )
409 tabs.addTab(self.create_history_tab(), _('History') )
410 tabs.addTab(self.create_send_tab(), _('Send') )
411 tabs.addTab(self.create_receive_tab(), _('Receive') )
412 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
413 tabs.addTab(self.create_wall_tab(), _('Wall') )
414 tabs.setMinimumSize(600, 400)
415 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
416 self.setCentralWidget(tabs)
417 self.create_status_bar()
419 g = self.config.get("winpos-qt",[100, 100, 840, 400])
420 self.setGeometry(g[0], g[1], g[2], g[3])
421 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
422 if not self.wallet.seed: title += ' [seedless]'
423 self.setWindowTitle( title )
425 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
426 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
427 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
428 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
430 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
431 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
432 self.history_list.setFocus(True)
434 self.exchanger = exchange_rate.Exchanger(self)
435 self.toggle_QR_window(self.receive_tab_mode == 2)
436 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
438 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
439 if platform.system() == 'Windows':
440 n = 3 if self.wallet.seed else 2
441 tabs.setCurrentIndex (n)
442 tabs.setCurrentIndex (0)
445 QMainWindow.close(self)
447 self.qr_window.close()
448 self.qr_window = None
450 def connect_slots(self, sender):
451 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
452 self.previous_payto_e=''
454 def timer_actions(self):
456 self.qr_window.qrw.update_qr()
458 if self.payto_e.hasFocus():
460 r = unicode( self.payto_e.text() )
461 if r != self.previous_payto_e:
462 self.previous_payto_e = r
464 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
466 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
470 s = r + ' <' + to_address + '>'
471 self.payto_e.setText(s)
474 def update_callback(self):
475 self.emit(QtCore.SIGNAL('updatesignal'))
477 def update_wallet(self):
478 if self.wallet.interface and self.wallet.interface.is_connected:
479 if not self.wallet.up_to_date:
480 text = _( "Synchronizing..." )
481 icon = QIcon(":icons/status_waiting.png")
483 c, u = self.wallet.get_balance()
484 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
485 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
486 text += self.create_quote_text(Decimal(c+u)/100000000)
487 icon = QIcon(":icons/status_connected.png")
489 text = _( "Not connected" )
490 icon = QIcon(":icons/status_disconnected.png")
492 self.status_text = text
493 self.statusBar().showMessage(text)
494 self.status_button.setIcon( icon )
496 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
497 self.textbox.setText( self.wallet.banner )
498 self.update_history_tab()
499 self.update_receive_tab()
500 self.update_contacts_tab()
501 self.update_completions()
503 def create_quote_text(self, btc_balance):
504 quote_currency = self.config.get("currency", "None")
505 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
506 if quote_balance is None:
509 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
512 def create_history_tab(self):
513 self.history_list = l = MyTreeWidget(self)
515 for i,width in enumerate(self.column_widths['history']):
516 l.setColumnWidth(i, width)
517 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
518 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
519 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
521 l.setContextMenuPolicy(Qt.CustomContextMenu)
522 l.customContextMenuRequested.connect(self.create_history_menu)
526 def create_history_menu(self, position):
527 self.history_list.selectedIndexes()
528 item = self.history_list.currentItem()
530 tx_hash = str(item.data(0, Qt.UserRole).toString())
531 if not tx_hash: return
533 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
534 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
535 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
536 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
539 def tx_details(self, tx_hash):
540 dialog = QDialog(None)
542 dialog.setWindowTitle(_("Transaction Details"))
544 main_text = QTextEdit()
545 main_text.setText(self.wallet.get_tx_details(tx_hash))
546 main_text.setReadOnly(True)
547 main_text.setMinimumSize(550,275)
549 ok_button = QPushButton(_("OK"))
550 ok_button.setDefault(True)
551 ok_button.clicked.connect(dialog.accept)
555 hbox.addWidget(ok_button)
558 vbox.addWidget(main_text)
560 dialog.setLayout(vbox)
563 def tx_label_clicked(self, item, column):
564 if column==2 and item.isSelected():
566 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
567 self.history_list.editItem( item, column )
568 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
571 def tx_label_changed(self, item, column):
575 tx_hash = str(item.data(0, Qt.UserRole).toString())
576 tx = self.wallet.transactions.get(tx_hash)
577 s = self.wallet.labels.get(tx_hash)
578 text = unicode( item.text(2) )
580 self.wallet.labels[tx_hash] = text
581 item.setForeground(2, QBrush(QColor('black')))
583 if s: self.wallet.labels.pop(tx_hash)
584 text = self.wallet.get_default_label(tx_hash)
585 item.setText(2, text)
586 item.setForeground(2, QBrush(QColor('gray')))
590 def edit_label(self, is_recv):
591 l = self.receive_list if is_recv else self.contacts_list
592 c = 2 if is_recv else 1
593 item = l.currentItem()
594 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
595 l.editItem( item, c )
596 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
598 def edit_amount(self):
599 l = self.receive_list
600 item = l.currentItem()
601 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
602 l.editItem( item, 3 )
603 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
606 def address_label_clicked(self, item, column, l, column_addr, column_label):
607 if column == column_label and item.isSelected():
608 addr = unicode( item.text(column_addr) )
609 label = unicode( item.text(column_label) )
610 if label in self.wallet.aliases.keys():
612 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
613 l.editItem( item, column )
614 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
617 def address_label_changed(self, item, column, l, column_addr, column_label):
619 if column == column_label:
620 addr = unicode( item.text(column_addr) )
621 text = unicode( item.text(column_label) )
625 if text not in self.wallet.aliases.keys():
626 old_addr = self.wallet.labels.get(text)
628 self.wallet.labels[addr] = text
631 print_error("Error: This is one of your aliases")
632 label = self.wallet.labels.get(addr,'')
633 item.setText(column_label, QString(label))
635 s = self.wallet.labels.get(addr)
637 self.wallet.labels.pop(addr)
641 self.update_history_tab()
642 self.update_completions()
644 self.recv_changed(item)
647 address = str( item.text(column_addr) )
648 text = str( item.text(3) )
650 index = self.wallet.addresses.index(address)
654 text = text.strip().upper()
655 m = re.match('^(\d+(|\.\d*))\s*(|BTC|EUR|USD|GBP|CNY|JPY|RUB|BRL)$', text)
658 currency = m.group(3)
662 currency = currency.upper()
663 self.wallet.requested_amounts[address] = (amount, currency)
665 label = self.wallet.labels.get(address)
667 label = self.merchant_name + ' - %04d'%(index+1)
668 self.wallet.labels[address] = label
671 self.qr_window.set_content( address, label, amount, currency )
675 if address in self.wallet.requested_amounts:
676 self.wallet.requested_amounts.pop(address)
678 self.update_receive_item(self.receive_list.currentItem())
681 def recv_changed(self, a):
682 "current item changed"
683 if a is not None and self.qr_window and self.qr_window.isVisible():
684 address = str(a.text(1))
685 label = self.wallet.labels.get(address)
687 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
689 amount, currency = None, None
690 self.qr_window.set_content( address, label, amount, currency )
693 def update_history_tab(self):
695 self.history_list.clear()
696 for item in self.wallet.get_tx_history():
697 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
700 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
706 icon = QIcon(":icons/unconfirmed.png")
708 icon = QIcon(":icons/clock%d.png"%conf)
710 icon = QIcon(":icons/confirmed.png")
713 icon = QIcon(":icons/unconfirmed.png")
715 if value is not None:
716 v_str = format_satoshis(value, True, self.wallet.num_zeros)
720 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
723 label, is_default_label = self.wallet.get_label(tx_hash)
725 label = _('Pruned transaction outputs')
726 is_default_label = False
728 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
729 item.setFont(2, QFont(MONOSPACE_FONT))
730 item.setFont(3, QFont(MONOSPACE_FONT))
731 item.setFont(4, QFont(MONOSPACE_FONT))
733 item.setForeground(3, QBrush(QColor("#BC1E1E")))
735 item.setData(0, Qt.UserRole, tx_hash)
736 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
738 item.setForeground(2, QBrush(QColor('grey')))
740 item.setIcon(0, icon)
741 self.history_list.insertTopLevelItem(0,item)
744 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
747 def create_send_tab(self):
752 grid.setColumnMinimumWidth(3,300)
753 grid.setColumnStretch(5,1)
755 self.payto_e = QLineEdit()
756 grid.addWidget(QLabel(_('Pay to')), 1, 0)
757 grid.addWidget(self.payto_e, 1, 1, 1, 3)
760 qrcode = qrscanner.scan_qr()
761 if 'address' in qrcode:
762 self.payto_e.setText(qrcode['address'])
763 if 'amount' in qrcode:
764 self.amount_e.setText(str(qrcode['amount']))
765 if 'label' in qrcode:
766 self.message_e.setText(qrcode['label'])
767 if 'message' in qrcode:
768 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
771 if qrscanner.is_available():
772 b = QPushButton(_("Scan QR code"))
773 b.clicked.connect(fill_from_qr)
774 grid.addWidget(b, 1, 5)
776 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)
778 completer = QCompleter()
779 completer.setCaseSensitivity(False)
780 self.payto_e.setCompleter(completer)
781 completer.setModel(self.completions)
783 self.message_e = QLineEdit()
784 grid.addWidget(QLabel(_('Description')), 2, 0)
785 grid.addWidget(self.message_e, 2, 1, 1, 3)
786 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)
788 self.amount_e = QLineEdit()
789 grid.addWidget(QLabel(_('Amount')), 3, 0)
790 grid.addWidget(self.amount_e, 3, 1, 1, 2)
791 grid.addWidget(HelpButton(
792 _('Amount to be sent.') + '\n\n' \
793 + _('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)
795 self.fee_e = QLineEdit()
796 grid.addWidget(QLabel(_('Fee')), 4, 0)
797 grid.addWidget(self.fee_e, 4, 1, 1, 2)
798 grid.addWidget(HelpButton(
799 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
800 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
801 + _('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)
803 b = EnterButton(_("Send"), self.do_send)
804 grid.addWidget(b, 6, 1)
806 b = EnterButton(_("Clear"),self.do_clear)
807 grid.addWidget(b, 6, 2)
809 self.payto_sig = QLabel('')
810 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
812 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
813 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
822 def entry_changed( is_fee ):
823 self.funds_error = False
824 amount = numbify(self.amount_e)
825 fee = numbify(self.fee_e)
826 if not is_fee: fee = None
829 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
831 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
834 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
835 text = self.status_text
838 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
839 self.funds_error = True
840 text = _( "Not enough funds" )
842 self.statusBar().showMessage(text)
843 self.amount_e.setPalette(palette)
844 self.fee_e.setPalette(palette)
846 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
847 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
852 def update_completions(self):
854 for addr,label in self.wallet.labels.items():
855 if addr in self.wallet.addressbook:
856 l.append( label + ' <' + addr + '>')
857 l = l + self.wallet.aliases.keys()
859 self.completions.setStringList(l)
865 label = unicode( self.message_e.text() )
866 r = unicode( self.payto_e.text() )
870 m1 = re.match(ALIAS_REGEXP, r)
871 # label or alias, with address in brackets
872 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
875 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
879 to_address = m2.group(2)
883 if not self.wallet.is_valid(to_address):
884 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
888 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
890 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
893 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
895 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
898 if self.wallet.use_encryption:
899 password = self.password_dialog()
906 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
907 except BaseException, e:
908 self.show_message(str(e))
912 h = self.wallet.send_tx(tx)
913 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
914 status, msg = self.wallet.receive_tx( h )
916 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
918 self.update_contacts_tab()
920 QMessageBox.warning(self, _('Error'), msg, _('OK'))
922 filename = 'unsigned_tx'
923 f = open(filename,'w')
926 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
929 def set_url(self, url):
930 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
931 self.tabs.setCurrentIndex(1)
932 label = self.wallet.labels.get(payto)
933 m_addr = label + ' <'+ payto+'>' if label else payto
934 self.payto_e.setText(m_addr)
936 self.message_e.setText(message)
937 self.amount_e.setText(amount)
939 self.set_frozen(self.payto_e,True)
940 self.set_frozen(self.amount_e,True)
941 self.set_frozen(self.message_e,True)
942 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
944 self.payto_sig.setVisible(False)
947 self.payto_sig.setVisible(False)
948 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
950 self.set_frozen(e,False)
952 def set_frozen(self,entry,frozen):
954 entry.setReadOnly(True)
955 entry.setFrame(False)
957 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
958 entry.setPalette(palette)
960 entry.setReadOnly(False)
963 palette.setColor(entry.backgroundRole(), QColor('white'))
964 entry.setPalette(palette)
967 def toggle_freeze(self,addr):
969 if addr in self.wallet.frozen_addresses:
970 self.wallet.unfreeze(addr)
972 self.wallet.freeze(addr)
973 self.update_receive_tab()
975 def toggle_priority(self,addr):
977 if addr in self.wallet.prioritized_addresses:
978 self.wallet.unprioritize(addr)
980 self.wallet.prioritize(addr)
981 self.update_receive_tab()
984 def create_list_tab(self, headers):
985 "generic tab creation method"
986 l = MyTreeWidget(self)
987 l.setColumnCount( len(headers) )
988 l.setHeaderLabels( headers )
998 vbox.addWidget(buttons)
1000 hbox = QHBoxLayout()
1003 buttons.setLayout(hbox)
1008 def create_receive_tab(self):
1009 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
1010 l.setContextMenuPolicy(Qt.CustomContextMenu)
1011 l.customContextMenuRequested.connect(self.create_receive_menu)
1012 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
1013 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
1014 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
1015 self.receive_list = l
1016 self.receive_buttons_hbox = hbox
1022 def receive_tab_set_mode(self, i):
1023 self.save_column_widths()
1024 self.receive_tab_mode = i
1025 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
1027 self.update_receive_tab()
1028 self.toggle_QR_window(self.receive_tab_mode == 2)
1030 def save_column_widths(self):
1032 for i in range(self.receive_list.columnCount()):
1033 widths.append(self.receive_list.columnWidth(i))
1034 self.column_widths["receive"][self.receive_tab_mode] = widths
1035 self.column_widths["history"] = []
1036 for i in range(self.history_list.columnCount()):
1037 self.column_widths["history"].append(self.history_list.columnWidth(i))
1038 self.column_widths["contacts"] = []
1039 for i in range(self.contacts_list.columnCount()):
1040 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1042 def create_contacts_tab(self):
1043 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1044 l.setContextMenuPolicy(Qt.CustomContextMenu)
1045 l.customContextMenuRequested.connect(self.create_contact_menu)
1046 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1047 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1048 self.contacts_list = l
1049 self.contacts_buttons_hbox = hbox
1050 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1055 def delete_imported_key(self, addr):
1056 if self.question("Do you want to remove %s from your wallet?"%addr):
1057 self.wallet.imported_keys.pop(addr)
1058 self.update_receive_tab()
1059 self.update_history_tab()
1063 def create_receive_menu(self, position):
1064 # fixme: this function apparently has a side effect.
1065 # if it is not called the menu pops up several times
1066 #self.receive_list.selectedIndexes()
1068 item = self.receive_list.itemAt(position)
1070 addr = unicode(item.text(1))
1072 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1073 if self.receive_tab_mode == 2:
1074 menu.addAction(_("Request amount"), lambda: self.edit_amount())
1075 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
1076 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1077 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1078 if addr in self.wallet.imported_keys:
1079 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1081 if self.receive_tab_mode == 1:
1082 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1083 menu.addAction(t, lambda: self.toggle_freeze(addr))
1084 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1085 menu.addAction(t, lambda: self.toggle_priority(addr))
1087 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1090 def payto(self, x, is_alias):
1097 label = self.wallet.labels.get(addr)
1098 m_addr = label + ' <' + addr + '>' if label else addr
1099 self.tabs.setCurrentIndex(1)
1100 self.payto_e.setText(m_addr)
1101 self.amount_e.setFocus()
1103 def delete_contact(self, x, is_alias):
1104 if self.question("Do you want to remove %s from your list of contacts?"%x):
1105 if not is_alias and x in self.wallet.addressbook:
1106 self.wallet.addressbook.remove(x)
1107 if x in self.wallet.labels.keys():
1108 self.wallet.labels.pop(x)
1109 elif is_alias and x in self.wallet.aliases:
1110 self.wallet.aliases.pop(x)
1111 self.update_history_tab()
1112 self.update_contacts_tab()
1113 self.update_completions()
1115 def create_contact_menu(self, position):
1116 # fixme: this function apparently has a side effect.
1117 # if it is not called the menu pops up several times
1118 #self.contacts_list.selectedIndexes()
1120 item = self.contacts_list.itemAt(position)
1122 addr = unicode(item.text(0))
1123 label = unicode(item.text(1))
1124 is_alias = label in self.wallet.aliases.keys()
1125 x = label if is_alias else addr
1127 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1128 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1129 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
1131 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1133 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1134 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1135 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1138 def update_receive_item(self, item):
1139 address = str( item.data(1,0).toString() )
1141 flags = self.wallet.get_address_flags(address)
1142 item.setData(0,0,flags)
1144 label = self.wallet.labels.get(address,'')
1145 item.setData(2,0,label)
1148 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1150 amount, currency = None, None
1152 amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
1153 item.setData(3,0,amount_str)
1155 c, u = self.wallet.get_addr_balance(address)
1156 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1157 item.setData(4,0,balance)
1159 if self.receive_tab_mode == 1:
1160 if address in self.wallet.frozen_addresses:
1161 item.setBackgroundColor(1, QColor('lightblue'))
1162 elif address in self.wallet.prioritized_addresses:
1163 item.setBackgroundColor(1, QColor('lightgreen'))
1166 def update_receive_tab(self):
1167 l = self.receive_list
1170 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1171 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1172 l.setColumnHidden(4, self.receive_tab_mode == 0)
1173 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1174 for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
1175 l.setColumnWidth(i, width)
1179 for address in self.wallet.all_addresses():
1181 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1185 h = self.wallet.history.get(address,[])
1188 for tx_hash, tx_height in h:
1189 tx = self.wallet.transactions.get(tx_hash)
1197 if address in self.wallet.addresses:
1199 if gap > self.wallet.gap_limit:
1202 if address in self.wallet.addresses:
1205 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1206 item.setFont(0, QFont(MONOSPACE_FONT))
1207 item.setFont(1, QFont(MONOSPACE_FONT))
1208 item.setFont(3, QFont(MONOSPACE_FONT))
1209 self.update_receive_item(item)
1210 if is_red and address in self.wallet.addresses:
1211 item.setBackgroundColor(1, QColor('red'))
1212 l.addTopLevelItem(item)
1214 # we use column 1 because column 0 may be hidden
1215 l.setCurrentItem(l.topLevelItem(0),1)
1217 def show_contact_details(self, m):
1218 a = self.wallet.aliases.get(m)
1220 if a[0] in self.wallet.authorities.keys():
1221 s = self.wallet.authorities.get(a[0])
1224 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1225 QMessageBox.information(self, 'Alias', msg, 'OK')
1227 def update_contacts_tab(self):
1229 l = self.contacts_list
1231 for i,width in enumerate(self.column_widths['contacts']):
1232 l.setColumnWidth(i, width)
1235 for alias, v in self.wallet.aliases.items():
1237 alias_targets.append(target)
1238 item = QTreeWidgetItem( [ target, alias, '-'] )
1239 item.setBackgroundColor(0, QColor('lightgray'))
1240 l.addTopLevelItem(item)
1242 for address in self.wallet.addressbook:
1243 if address in alias_targets: continue
1244 label = self.wallet.labels.get(address,'')
1246 for item in self.wallet.transactions.values():
1247 if address in item['outputs'] : n=n+1
1249 item = QTreeWidgetItem( [ address, label, tx] )
1250 item.setFont(0, QFont(MONOSPACE_FONT))
1251 l.addTopLevelItem(item)
1253 l.setCurrentItem(l.topLevelItem(0))
1255 def create_wall_tab(self):
1256 self.textbox = textbox = QTextEdit(self)
1257 textbox.setFont(QFont(MONOSPACE_FONT))
1258 textbox.setReadOnly(True)
1262 def create_status_bar(self):
1263 self.status_text = ""
1265 sb.setFixedHeight(35)
1266 qtVersion = qVersion()
1268 update_notification = UpdateLabel(self.config)
1269 sb.addPermanentWidget(update_notification)
1271 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1272 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1273 if self.wallet.seed:
1274 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1275 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1276 if self.wallet.seed:
1277 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1278 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1279 sb.addPermanentWidget( self.status_button )
1281 self.setStatusBar(sb)
1285 self.config.set_key('gui', 'lite', True)
1288 self.lite.mini.show()
1290 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1291 self.lite.main(None)
1293 def new_contact_dialog(self):
1294 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1295 address = unicode(text)
1297 if self.wallet.is_valid(address):
1298 self.wallet.addressbook.append(address)
1300 self.update_contacts_tab()
1301 self.update_history_tab()
1302 self.update_completions()
1304 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1306 def show_master_public_key(self):
1307 dialog = QDialog(None)
1309 dialog.setWindowTitle("Master Public Key")
1311 main_text = QTextEdit()
1312 main_text.setText(self.wallet.master_public_key)
1313 main_text.setReadOnly(True)
1314 main_text.setMaximumHeight(170)
1315 qrw = QRCodeWidget(self.wallet.master_public_key, 6)
1317 ok_button = QPushButton(_("OK"))
1318 ok_button.setDefault(True)
1319 ok_button.clicked.connect(dialog.accept)
1321 main_layout = QGridLayout()
1322 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1324 main_layout.addWidget(main_text, 1, 0)
1325 main_layout.addWidget(qrw, 1, 1 )
1327 vbox = QVBoxLayout()
1328 vbox.addLayout(main_layout)
1329 hbox = QHBoxLayout()
1331 hbox.addWidget(ok_button)
1332 vbox.addLayout(hbox)
1334 dialog.setLayout(vbox)
1339 def show_seed_dialog(wallet, parent=None):
1341 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1344 if wallet.use_encryption:
1345 password = parent.password_dialog()
1352 seed = wallet.decode_seed(password)
1354 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1357 dialog = QDialog(None)
1359 dialog.setWindowTitle(_("Electrum") + ' - ' + _('Seed'))
1361 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1363 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1365 seed_text = QTextEdit(brainwallet)
1366 seed_text.setReadOnly(True)
1367 seed_text.setMaximumHeight(130)
1369 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1370 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1371 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1372 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1373 label2 = QLabel(msg2)
1374 label2.setWordWrap(True)
1377 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1378 logo.setMaximumWidth(60)
1380 qrw = QRCodeWidget(seed, 4)
1382 ok_button = QPushButton(_("OK"))
1383 ok_button.setDefault(True)
1384 ok_button.clicked.connect(dialog.accept)
1386 grid = QGridLayout()
1387 #main_layout.addWidget(logo, 0, 0)
1389 grid.addWidget(logo, 0, 0)
1390 grid.addWidget(label1, 0, 1)
1392 grid.addWidget(seed_text, 1, 0, 1, 2)
1394 grid.addWidget(qrw, 0, 2, 2, 1)
1396 vbox = QVBoxLayout()
1397 vbox.addLayout(grid)
1398 vbox.addWidget(label2)
1400 hbox = QHBoxLayout()
1402 hbox.addWidget(ok_button)
1403 vbox.addLayout(hbox)
1405 dialog.setLayout(vbox)
1409 def show_qrcode(title, data):
1413 d.setWindowTitle(title)
1414 d.setMinimumSize(270, 300)
1415 vbox = QVBoxLayout()
1416 qrw = QRCodeWidget(data)
1417 vbox.addWidget(qrw, 1)
1418 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1419 hbox = QHBoxLayout()
1423 filename = "qrcode.bmp"
1424 bmp.save_qrcode(qrw.qr, filename)
1425 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1427 b = QPushButton(_("Print"))
1429 b.clicked.connect(print_qr)
1431 b = QPushButton(_("Close"))
1433 b.clicked.connect(d.accept)
1435 vbox.addLayout(hbox)
1439 def sign_message(self,address):
1440 if not address: return
1443 d.setWindowTitle('Sign Message')
1444 d.setMinimumSize(410, 290)
1446 tab_widget = QTabWidget()
1448 layout = QGridLayout(tab)
1450 sign_address = QLineEdit()
1452 sign_address.setText(address)
1453 layout.addWidget(QLabel(_('Address')), 1, 0)
1454 layout.addWidget(sign_address, 1, 1)
1456 sign_message = QTextEdit()
1457 layout.addWidget(QLabel(_('Message')), 2, 0)
1458 layout.addWidget(sign_message, 2, 1)
1459 layout.setRowStretch(2,3)
1461 sign_signature = QTextEdit()
1462 layout.addWidget(QLabel(_('Signature')), 3, 0)
1463 layout.addWidget(sign_signature, 3, 1)
1464 layout.setRowStretch(3,1)
1467 if self.wallet.use_encryption:
1468 password = self.password_dialog()
1475 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1476 sign_signature.setText(signature)
1477 except BaseException, e:
1478 self.show_message(str(e))
1481 hbox = QHBoxLayout()
1482 b = QPushButton(_("Sign"))
1484 b.clicked.connect(do_sign)
1485 b = QPushButton(_("Close"))
1486 b.clicked.connect(d.accept)
1488 layout.addLayout(hbox, 4, 1)
1489 tab_widget.addTab(tab, "Sign")
1493 layout = QGridLayout(tab)
1495 verify_address = QLineEdit()
1496 layout.addWidget(QLabel(_('Address')), 1, 0)
1497 layout.addWidget(verify_address, 1, 1)
1499 verify_message = QTextEdit()
1500 layout.addWidget(QLabel(_('Message')), 2, 0)
1501 layout.addWidget(verify_message, 2, 1)
1502 layout.setRowStretch(2,3)
1504 verify_signature = QTextEdit()
1505 layout.addWidget(QLabel(_('Signature')), 3, 0)
1506 layout.addWidget(verify_signature, 3, 1)
1507 layout.setRowStretch(3,1)
1511 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1512 self.show_message("Signature verified")
1513 except BaseException, e:
1514 self.show_message(str(e))
1517 hbox = QHBoxLayout()
1518 b = QPushButton(_("Verify"))
1519 b.clicked.connect(do_verify)
1521 b = QPushButton(_("Close"))
1522 b.clicked.connect(d.accept)
1524 layout.addLayout(hbox, 4, 1)
1525 tab_widget.addTab(tab, "Verify")
1527 vbox = QVBoxLayout()
1528 vbox.addWidget(tab_widget)
1533 def toggle_QR_window(self, show):
1534 if show and not self.qr_window:
1535 self.qr_window = QR_Window(self.exchanger)
1536 self.qr_window.setVisible(True)
1537 self.qr_window_geometry = self.qr_window.geometry()
1538 item = self.receive_list.currentItem()
1540 address = str(item.text(1))
1541 label = self.wallet.labels.get(address)
1542 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1543 self.qr_window.set_content( address, label, amount, currency )
1545 elif show and self.qr_window and not self.qr_window.isVisible():
1546 self.qr_window.setVisible(True)
1547 self.qr_window.setGeometry(self.qr_window_geometry)
1549 elif not show and self.qr_window and self.qr_window.isVisible():
1550 self.qr_window_geometry = self.qr_window.geometry()
1551 self.qr_window.setVisible(False)
1553 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1554 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1555 self.receive_list.setColumnWidth(2, 200)
1558 def question(self, msg):
1559 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1561 def show_message(self, msg):
1562 QMessageBox.information(self, _('Message'), msg, _('OK'))
1564 def password_dialog(self ):
1571 vbox = QVBoxLayout()
1572 msg = _('Please enter your password')
1573 vbox.addWidget(QLabel(msg))
1575 grid = QGridLayout()
1577 grid.addWidget(QLabel(_('Password')), 1, 0)
1578 grid.addWidget(pw, 1, 1)
1579 vbox.addLayout(grid)
1581 vbox.addLayout(ok_cancel_buttons(d))
1584 if not d.exec_(): return
1585 return unicode(pw.text())
1592 def change_password_dialog( wallet, parent=None ):
1595 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1603 new_pw = QLineEdit()
1604 new_pw.setEchoMode(2)
1605 conf_pw = QLineEdit()
1606 conf_pw.setEchoMode(2)
1608 vbox = QVBoxLayout()
1610 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1611 +_('To disable wallet encryption, enter an empty new password.')) \
1612 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1614 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1615 +_("Leave these fields empty if you want to disable encryption.")
1616 vbox.addWidget(QLabel(msg))
1618 grid = QGridLayout()
1621 if wallet.use_encryption:
1622 grid.addWidget(QLabel(_('Password')), 1, 0)
1623 grid.addWidget(pw, 1, 1)
1625 grid.addWidget(QLabel(_('New Password')), 2, 0)
1626 grid.addWidget(new_pw, 2, 1)
1628 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1629 grid.addWidget(conf_pw, 3, 1)
1630 vbox.addLayout(grid)
1632 vbox.addLayout(ok_cancel_buttons(d))
1635 if not d.exec_(): return
1637 password = unicode(pw.text()) if wallet.use_encryption else None
1638 new_password = unicode(new_pw.text())
1639 new_password2 = unicode(conf_pw.text())
1642 seed = wallet.decode_seed(password)
1644 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1647 if new_password != new_password2:
1648 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1649 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1651 wallet.update_password(seed, password, new_password)
1654 def seed_dialog(wallet, parent=None):
1658 vbox = QVBoxLayout()
1659 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1660 vbox.addWidget(QLabel(msg))
1662 grid = QGridLayout()
1665 seed_e = QLineEdit()
1666 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1667 grid.addWidget(seed_e, 1, 1)
1671 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1672 grid.addWidget(gap_e, 2, 1)
1673 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1674 vbox.addLayout(grid)
1676 vbox.addLayout(ok_cancel_buttons(d))
1679 if not d.exec_(): return
1682 gap = int(unicode(gap_e.text()))
1684 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1688 seed = str(seed_e.text())
1691 print_error("Warning: Not hex, trying decode")
1693 seed = mnemonic.mn_decode( seed.split(' ') )
1695 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1699 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1705 def do_import_labels(self):
1706 labelsFile = QFileDialog.getOpenFileName(QWidget(), "Open text file", util.user_dir(), self.tr("Text Files (labels.dat)"))
1707 if not labelsFile: return
1709 f = open(labelsFile, 'r')
1712 for key, value in json.loads(data).items():
1713 self.wallet.labels[key] = value
1715 QMessageBox.information(None, "Labels imported", "Your labels where imported from '%s'" % str(labelsFile))
1716 except (IOError, os.error), reason:
1717 QMessageBox.critical(None, "Unable to import labels", "Electrum was unable to import your labels.\n" + str(reason))
1721 def do_export_labels(self):
1722 labels = self.wallet.labels
1724 labelsFile = util.user_dir() + '/labels.dat'
1725 f = open(labelsFile, 'w+')
1726 json.dump(labels, f)
1728 QMessageBox.information(None, "Labels exported", "Your labels where exported to '%s'" % str(labelsFile))
1729 except (IOError, os.error), reason:
1730 QMessageBox.critical(None, "Unable to export labels", "Electrum was unable to export your labels.\n" + str(reason))
1732 def do_export_history(self):
1733 from gui_lite import csv_transaction
1734 csv_transaction(self.wallet)
1736 def do_import_privkey(self):
1737 if not self.wallet.imported_keys:
1738 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1739 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1740 + _('Are you sure you understand what you are doing?'), 3, 4)
1743 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1745 sec = str(text).strip()
1746 if self.wallet.use_encryption:
1747 password = self.password_dialog()
1753 addr = self.wallet.import_key(sec, password)
1755 QMessageBox.critical(None, "Unable to import key", "error")
1757 QMessageBox.information(None, "Key imported", addr)
1758 self.update_receive_tab()
1759 self.update_history_tab()
1760 except BaseException as e:
1761 QMessageBox.critical(None, "Unable to import key", str(e))
1763 def settings_dialog(self):
1765 d.setWindowTitle(_('Electrum Settings'))
1767 vbox = QVBoxLayout()
1769 tabs = QTabWidget(self)
1770 vbox.addWidget(tabs)
1773 grid_ui = QGridLayout(tab1)
1774 grid_ui.setColumnStretch(0,1)
1775 tabs.addTab(tab1, _('Display') )
1777 nz_label = QLabel(_('Display zeros'))
1778 grid_ui.addWidget(nz_label, 3, 0)
1780 nz_e.setText("%d"% self.wallet.num_zeros)
1781 grid_ui.addWidget(nz_e, 3, 1)
1782 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1783 grid_ui.addWidget(HelpButton(msg), 3, 2)
1784 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1785 if not self.config.is_modifiable('num_zeros'):
1786 for w in [nz_e, nz_label]: w.setEnabled(False)
1788 lang_label=QLabel(_('Language') + ':')
1789 grid_ui.addWidget(lang_label , 8, 0)
1790 lang_combo = QComboBox()
1791 from i18n import languages
1792 lang_combo.addItems(languages.values())
1794 index = languages.keys().index(self.config.get("language",''))
1797 lang_combo.setCurrentIndex(index)
1798 grid_ui.addWidget(lang_combo, 8, 1)
1799 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1800 if not self.config.is_modifiable('language'):
1801 for w in [lang_combo, lang_label]: w.setEnabled(False)
1803 currencies = self.exchanger.get_currencies()
1804 currencies.insert(0, "None")
1806 cur_label=QLabel(_('Currency') + ':')
1807 grid_ui.addWidget(cur_label , 9, 0)
1808 cur_combo = QComboBox()
1809 cur_combo.addItems(currencies)
1811 index = currencies.index(self.config.get('currency', "None"))
1814 cur_combo.setCurrentIndex(index)
1815 grid_ui.addWidget(cur_combo, 9, 1)
1816 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1818 view_label=QLabel(_('Receive Tab') + ':')
1819 grid_ui.addWidget(view_label , 10, 0)
1820 view_combo = QComboBox()
1821 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1822 view_combo.setCurrentIndex(self.receive_tab_mode)
1823 grid_ui.addWidget(view_combo, 10, 1)
1824 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1825 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1826 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1827 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1829 grid_ui.addWidget(HelpButton(hh), 10, 2)
1833 grid_wallet = QGridLayout(tab2)
1834 grid_wallet.setColumnStretch(0,1)
1835 tabs.addTab(tab2, _('Wallet') )
1837 fee_label = QLabel(_('Transaction fee'))
1838 grid_wallet.addWidget(fee_label, 0, 0)
1840 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1841 grid_wallet.addWidget(fee_e, 0, 1)
1842 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1843 + _('Recommended value') + ': 0.001'
1844 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1845 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1846 if not self.config.is_modifiable('fee'):
1847 for w in [fee_e, fee_label]: w.setEnabled(False)
1849 usechange_label = QLabel(_('Use change addresses'))
1850 grid_wallet.addWidget(usechange_label, 1, 0)
1851 usechange_combo = QComboBox()
1852 usechange_combo.addItems([_('Yes'), _('No')])
1853 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1854 grid_wallet.addWidget(usechange_combo, 1, 1)
1855 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1856 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1858 gap_label = QLabel(_('Gap limit'))
1859 grid_wallet.addWidget(gap_label, 2, 0)
1861 gap_e.setText("%d"% self.wallet.gap_limit)
1862 grid_wallet.addWidget(gap_e, 2, 1)
1863 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1864 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1865 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1866 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1867 + _('Warning') + ': ' \
1868 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1869 + _('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'
1870 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1871 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1872 if not self.config.is_modifiable('gap_limit'):
1873 for w in [gap_e, gap_label]: w.setEnabled(False)
1875 grid_wallet.setRowStretch(3,1)
1880 grid_io = QGridLayout(tab3)
1881 grid_io.setColumnStretch(0,1)
1882 tabs.addTab(tab3, _('Import/Export') )
1884 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1885 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1886 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1887 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1889 grid_io.addWidget(QLabel(_('History')), 2, 0)
1890 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1891 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1893 grid_io.addWidget(QLabel(_('Private key')), 3, 0)
1894 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1895 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1897 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1898 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1899 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1900 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1901 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1903 grid_io.setRowStretch(4,1)
1904 vbox.addLayout(ok_cancel_buttons(d))
1908 if not d.exec_(): return
1910 fee = unicode(fee_e.text())
1912 fee = int( 100000000 * Decimal(fee) )
1914 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1917 if self.wallet.fee != fee:
1918 self.wallet.fee = fee
1921 nz = unicode(nz_e.text())
1926 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1929 if self.wallet.num_zeros != nz:
1930 self.wallet.num_zeros = nz
1931 self.config.set_key('num_zeros', nz, True)
1932 self.update_history_tab()
1933 self.update_receive_tab()
1935 usechange_result = usechange_combo.currentIndex() == 0
1936 if self.wallet.use_change != usechange_result:
1937 self.wallet.use_change = usechange_result
1938 self.config.set_key('use_change', self.wallet.use_change, True)
1941 n = int(gap_e.text())
1943 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1946 if self.wallet.gap_limit != n:
1947 r = self.wallet.change_gap_limit(n)
1949 self.update_receive_tab()
1950 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1952 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1954 need_restart = False
1956 lang_request = languages.keys()[lang_combo.currentIndex()]
1957 if lang_request != self.config.get('language'):
1958 self.config.set_key("language", lang_request, True)
1961 cur_request = str(currencies[cur_combo.currentIndex()])
1962 if cur_request != self.config.get('currency', "None"):
1963 self.config.set_key('currency', cur_request, True)
1964 self.update_wallet()
1967 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1969 self.receive_tab_set_mode(view_combo.currentIndex())
1973 def network_dialog(wallet, parent=None):
1974 interface = wallet.interface
1976 if interface.is_connected:
1977 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
1979 status = _("Not connected")
1980 server = interface.server
1983 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1984 server = interface.server
1986 plist, servers_list = interface.get_servers_list()
1990 d.setWindowTitle(_('Server'))
1991 d.setMinimumSize(375, 20)
1993 vbox = QVBoxLayout()
1996 hbox = QHBoxLayout()
1998 l.setPixmap(QPixmap(":icons/network.png"))
2001 hbox.addWidget(QLabel(status))
2003 vbox.addLayout(hbox)
2007 grid = QGridLayout()
2009 vbox.addLayout(grid)
2012 server_protocol = QComboBox()
2013 server_host = QLineEdit()
2014 server_host.setFixedWidth(200)
2015 server_port = QLineEdit()
2016 server_port.setFixedWidth(60)
2018 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2019 protocol_letters = 'thsg'
2020 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2021 server_protocol.addItems(protocol_names)
2023 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2024 grid.addWidget(server_protocol, 0, 1)
2025 grid.addWidget(server_host, 0, 2)
2026 grid.addWidget(server_port, 0, 3)
2028 def change_protocol(p):
2029 protocol = protocol_letters[p]
2030 host = unicode(server_host.text())
2031 pp = plist.get(host,DEFAULT_PORTS)
2032 if protocol not in pp.keys():
2033 protocol = pp.keys()[0]
2035 server_host.setText( host )
2036 server_port.setText( port )
2038 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2040 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2041 servers_list_widget = QTreeWidget(parent)
2042 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2043 servers_list_widget.setMaximumHeight(150)
2044 servers_list_widget.setColumnWidth(0, 240)
2045 for _host in servers_list.keys():
2046 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2047 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2049 def change_server(host, protocol=None):
2050 pp = plist.get(host,DEFAULT_PORTS)
2052 port = pp.get(protocol)
2053 if not port: protocol = None
2056 if 't' in pp.keys():
2058 port = pp.get(protocol)
2060 protocol = pp.keys()[0]
2061 port = pp.get(protocol)
2063 server_host.setText( host )
2064 server_port.setText( port )
2065 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2067 if not plist: return
2068 for p in protocol_letters:
2069 i = protocol_letters.index(p)
2070 j = server_protocol.model().index(i,0)
2071 if p not in pp.keys():
2072 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2074 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2078 host, port, protocol = server.split(':')
2079 change_server(host,protocol)
2081 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2082 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2084 if not wallet.config.is_modifiable('server'):
2085 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2088 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2089 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2090 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2091 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2094 proxy_mode = QComboBox()
2095 proxy_host = QLineEdit()
2096 proxy_host.setFixedWidth(200)
2097 proxy_port = QLineEdit()
2098 proxy_port.setFixedWidth(60)
2099 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2101 def check_for_disable(index = False):
2102 if proxy_mode.currentText() != 'NONE':
2103 proxy_host.setEnabled(True)
2104 proxy_port.setEnabled(True)
2106 proxy_host.setEnabled(False)
2107 proxy_port.setEnabled(False)
2110 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2112 if not wallet.config.is_modifiable('proxy'):
2113 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2115 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2116 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2117 proxy_host.setText(proxy_config.get("host"))
2118 proxy_port.setText(proxy_config.get("port"))
2120 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2121 grid.addWidget(proxy_mode, 2, 1)
2122 grid.addWidget(proxy_host, 2, 2)
2123 grid.addWidget(proxy_port, 2, 3)
2126 vbox.addLayout(ok_cancel_buttons(d))
2129 if not d.exec_(): return
2131 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2132 if proxy_mode.currentText() != 'NONE':
2133 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2137 wallet.config.set_key("proxy", proxy, True)
2138 wallet.config.set_key("server", server, True)
2139 interface.set_server(server, proxy)
2140 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2143 def closeEvent(self, event):
2145 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2146 self.save_column_widths()
2147 self.config.set_key("column-widths", self.column_widths, True)
2153 def __init__(self, wallet, config, app=None):
2154 self.wallet = wallet
2155 self.config = config
2157 self.app = QApplication(sys.argv)
2160 def restore_or_create(self):
2161 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2162 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2163 if r==2: return None
2164 return 'restore' if r==1 else 'create'
2166 def seed_dialog(self):
2167 return ElectrumWindow.seed_dialog( self.wallet )
2169 def network_dialog(self):
2170 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2173 def show_seed(self):
2174 ElectrumWindow.show_seed_dialog(self.wallet)
2177 def password_dialog(self):
2178 ElectrumWindow.change_password_dialog(self.wallet)
2181 def restore_wallet(self):
2182 wallet = self.wallet
2183 # wait until we are connected, because the user might have selected another server
2184 if not wallet.interface.is_connected:
2185 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
2186 waiting_dialog(waiting)
2188 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
2189 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
2191 wallet.set_up_to_date(False)
2192 wallet.interface.poke('synchronizer')
2193 waiting_dialog(waiting)
2194 if wallet.is_found():
2195 print_error( "Recovery successful" )
2197 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2204 w = ElectrumWindow(self.wallet, self.config)
2205 if url: w.set_url(url)