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:
1184 h = self.wallet.history.get(address,[])
1186 if address in self.wallet.addresses:
1189 if gap > self.wallet.gap_limit:
1194 num_tx = '*' if h == ['*'] else "%d"%len(h)
1195 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1196 item.setFont(0, QFont(MONOSPACE_FONT))
1197 item.setFont(1, QFont(MONOSPACE_FONT))
1198 item.setFont(3, QFont(MONOSPACE_FONT))
1199 self.update_receive_item(item)
1200 if is_red and address in self.wallet.addresses:
1201 item.setBackgroundColor(1, QColor('red'))
1202 l.addTopLevelItem(item)
1204 # we use column 1 because column 0 may be hidden
1205 l.setCurrentItem(l.topLevelItem(0),1)
1207 def show_contact_details(self, m):
1208 a = self.wallet.aliases.get(m)
1210 if a[0] in self.wallet.authorities.keys():
1211 s = self.wallet.authorities.get(a[0])
1214 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1215 QMessageBox.information(self, 'Alias', msg, 'OK')
1217 def update_contacts_tab(self):
1219 l = self.contacts_list
1221 for i,width in enumerate(self.column_widths['contacts']):
1222 l.setColumnWidth(i, width)
1225 for alias, v in self.wallet.aliases.items():
1227 alias_targets.append(target)
1228 item = QTreeWidgetItem( [ target, alias, '-'] )
1229 item.setBackgroundColor(0, QColor('lightgray'))
1230 l.addTopLevelItem(item)
1232 for address in self.wallet.addressbook:
1233 if address in alias_targets: continue
1234 label = self.wallet.labels.get(address,'')
1236 for item in self.wallet.transactions.values():
1237 if address in item['outputs'] : n=n+1
1239 item = QTreeWidgetItem( [ address, label, tx] )
1240 item.setFont(0, QFont(MONOSPACE_FONT))
1241 l.addTopLevelItem(item)
1243 l.setCurrentItem(l.topLevelItem(0))
1245 def create_wall_tab(self):
1246 self.textbox = textbox = QTextEdit(self)
1247 textbox.setFont(QFont(MONOSPACE_FONT))
1248 textbox.setReadOnly(True)
1252 def create_status_bar(self):
1253 self.status_text = ""
1255 sb.setFixedHeight(35)
1256 qtVersion = qVersion()
1258 update_notification = UpdateLabel(self.config)
1259 sb.addPermanentWidget(update_notification)
1261 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1262 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1263 if self.wallet.seed:
1264 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1265 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1266 if self.wallet.seed:
1267 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1268 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1269 sb.addPermanentWidget( self.status_button )
1271 self.setStatusBar(sb)
1275 self.config.set_key('gui', 'lite', True)
1278 self.lite.mini.show()
1280 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1281 self.lite.main(None)
1283 def new_contact_dialog(self):
1284 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1285 address = unicode(text)
1287 if self.wallet.is_valid(address):
1288 self.wallet.addressbook.append(address)
1290 self.update_contacts_tab()
1291 self.update_history_tab()
1292 self.update_completions()
1294 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1296 def show_master_public_key(self):
1297 dialog = QDialog(None)
1299 dialog.setWindowTitle("Master Public Key")
1301 main_text = QTextEdit()
1302 main_text.setText(self.wallet.master_public_key)
1303 main_text.setReadOnly(True)
1304 main_text.setMaximumHeight(170)
1305 qrw = QRCodeWidget(self.wallet.master_public_key, 6)
1307 ok_button = QPushButton(_("OK"))
1308 ok_button.setDefault(True)
1309 ok_button.clicked.connect(dialog.accept)
1311 main_layout = QGridLayout()
1312 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1314 main_layout.addWidget(main_text, 1, 0)
1315 main_layout.addWidget(qrw, 1, 1 )
1317 vbox = QVBoxLayout()
1318 vbox.addLayout(main_layout)
1319 hbox = QHBoxLayout()
1321 hbox.addWidget(ok_button)
1322 vbox.addLayout(hbox)
1324 dialog.setLayout(vbox)
1329 def show_seed_dialog(wallet, parent=None):
1331 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1334 if wallet.use_encryption:
1335 password = parent.password_dialog()
1342 seed = wallet.decode_seed(password)
1344 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1347 dialog = QDialog(None)
1349 dialog.setWindowTitle(_("Electrum") + ' - ' + _('Seed'))
1351 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1353 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1355 seed_text = QTextEdit(brainwallet)
1356 seed_text.setReadOnly(True)
1357 seed_text.setMaximumHeight(130)
1359 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1360 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1361 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1362 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1363 label2 = QLabel(msg2)
1364 label2.setWordWrap(True)
1367 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1368 logo.setMaximumWidth(60)
1370 qrw = QRCodeWidget(seed, 4)
1372 ok_button = QPushButton(_("OK"))
1373 ok_button.setDefault(True)
1374 ok_button.clicked.connect(dialog.accept)
1376 grid = QGridLayout()
1377 #main_layout.addWidget(logo, 0, 0)
1379 grid.addWidget(logo, 0, 0)
1380 grid.addWidget(label1, 0, 1)
1382 grid.addWidget(seed_text, 1, 0, 1, 2)
1384 grid.addWidget(qrw, 0, 2, 2, 1)
1386 vbox = QVBoxLayout()
1387 vbox.addLayout(grid)
1388 vbox.addWidget(label2)
1390 hbox = QHBoxLayout()
1392 hbox.addWidget(ok_button)
1393 vbox.addLayout(hbox)
1395 dialog.setLayout(vbox)
1399 def show_qrcode(title, data):
1403 d.setWindowTitle(title)
1404 d.setMinimumSize(270, 300)
1405 vbox = QVBoxLayout()
1406 qrw = QRCodeWidget(data)
1407 vbox.addWidget(qrw, 1)
1408 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1409 hbox = QHBoxLayout()
1413 filename = "qrcode.bmp"
1414 bmp.save_qrcode(qrw.qr, filename)
1415 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1417 b = QPushButton(_("Print"))
1419 b.clicked.connect(print_qr)
1421 b = QPushButton(_("Close"))
1423 b.clicked.connect(d.accept)
1425 vbox.addLayout(hbox)
1429 def sign_message(self,address):
1430 if not address: return
1433 d.setWindowTitle('Sign Message')
1434 d.setMinimumSize(410, 290)
1436 tab_widget = QTabWidget()
1438 layout = QGridLayout(tab)
1440 sign_address = QLineEdit()
1442 sign_address.setText(address)
1443 layout.addWidget(QLabel(_('Address')), 1, 0)
1444 layout.addWidget(sign_address, 1, 1)
1446 sign_message = QTextEdit()
1447 layout.addWidget(QLabel(_('Message')), 2, 0)
1448 layout.addWidget(sign_message, 2, 1)
1449 layout.setRowStretch(2,3)
1451 sign_signature = QTextEdit()
1452 layout.addWidget(QLabel(_('Signature')), 3, 0)
1453 layout.addWidget(sign_signature, 3, 1)
1454 layout.setRowStretch(3,1)
1457 if self.wallet.use_encryption:
1458 password = self.password_dialog()
1465 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1466 sign_signature.setText(signature)
1467 except BaseException, e:
1468 self.show_message(str(e))
1471 hbox = QHBoxLayout()
1472 b = QPushButton(_("Sign"))
1474 b.clicked.connect(do_sign)
1475 b = QPushButton(_("Close"))
1476 b.clicked.connect(d.accept)
1478 layout.addLayout(hbox, 4, 1)
1479 tab_widget.addTab(tab, "Sign")
1483 layout = QGridLayout(tab)
1485 verify_address = QLineEdit()
1486 layout.addWidget(QLabel(_('Address')), 1, 0)
1487 layout.addWidget(verify_address, 1, 1)
1489 verify_message = QTextEdit()
1490 layout.addWidget(QLabel(_('Message')), 2, 0)
1491 layout.addWidget(verify_message, 2, 1)
1492 layout.setRowStretch(2,3)
1494 verify_signature = QTextEdit()
1495 layout.addWidget(QLabel(_('Signature')), 3, 0)
1496 layout.addWidget(verify_signature, 3, 1)
1497 layout.setRowStretch(3,1)
1501 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1502 self.show_message("Signature verified")
1503 except BaseException, e:
1504 self.show_message(str(e))
1507 hbox = QHBoxLayout()
1508 b = QPushButton(_("Verify"))
1509 b.clicked.connect(do_verify)
1511 b = QPushButton(_("Close"))
1512 b.clicked.connect(d.accept)
1514 layout.addLayout(hbox, 4, 1)
1515 tab_widget.addTab(tab, "Verify")
1517 vbox = QVBoxLayout()
1518 vbox.addWidget(tab_widget)
1523 def toggle_QR_window(self, show):
1524 if show and not self.qr_window:
1525 self.qr_window = QR_Window(self.exchanger)
1526 self.qr_window.setVisible(True)
1527 self.qr_window_geometry = self.qr_window.geometry()
1528 item = self.receive_list.currentItem()
1530 address = str(item.text(1))
1531 label = self.wallet.labels.get(address)
1532 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1533 self.qr_window.set_content( address, label, amount, currency )
1535 elif show and self.qr_window and not self.qr_window.isVisible():
1536 self.qr_window.setVisible(True)
1537 self.qr_window.setGeometry(self.qr_window_geometry)
1539 elif not show and self.qr_window and self.qr_window.isVisible():
1540 self.qr_window_geometry = self.qr_window.geometry()
1541 self.qr_window.setVisible(False)
1543 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1544 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1545 self.receive_list.setColumnWidth(2, 200)
1548 def question(self, msg):
1549 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1551 def show_message(self, msg):
1552 QMessageBox.information(self, _('Message'), msg, _('OK'))
1554 def password_dialog(self ):
1561 vbox = QVBoxLayout()
1562 msg = _('Please enter your password')
1563 vbox.addWidget(QLabel(msg))
1565 grid = QGridLayout()
1567 grid.addWidget(QLabel(_('Password')), 1, 0)
1568 grid.addWidget(pw, 1, 1)
1569 vbox.addLayout(grid)
1571 vbox.addLayout(ok_cancel_buttons(d))
1574 if not d.exec_(): return
1575 return unicode(pw.text())
1582 def change_password_dialog( wallet, parent=None ):
1585 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1593 new_pw = QLineEdit()
1594 new_pw.setEchoMode(2)
1595 conf_pw = QLineEdit()
1596 conf_pw.setEchoMode(2)
1598 vbox = QVBoxLayout()
1600 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1601 +_('To disable wallet encryption, enter an empty new password.')) \
1602 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1604 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1605 +_("Leave these fields empty if you want to disable encryption.")
1606 vbox.addWidget(QLabel(msg))
1608 grid = QGridLayout()
1611 if wallet.use_encryption:
1612 grid.addWidget(QLabel(_('Password')), 1, 0)
1613 grid.addWidget(pw, 1, 1)
1615 grid.addWidget(QLabel(_('New Password')), 2, 0)
1616 grid.addWidget(new_pw, 2, 1)
1618 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1619 grid.addWidget(conf_pw, 3, 1)
1620 vbox.addLayout(grid)
1622 vbox.addLayout(ok_cancel_buttons(d))
1625 if not d.exec_(): return
1627 password = unicode(pw.text()) if wallet.use_encryption else None
1628 new_password = unicode(new_pw.text())
1629 new_password2 = unicode(conf_pw.text())
1632 seed = wallet.decode_seed(password)
1634 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1637 if new_password != new_password2:
1638 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1639 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1641 wallet.update_password(seed, password, new_password)
1644 def seed_dialog(wallet, parent=None):
1648 vbox = QVBoxLayout()
1649 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1650 vbox.addWidget(QLabel(msg))
1652 grid = QGridLayout()
1655 seed_e = QLineEdit()
1656 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1657 grid.addWidget(seed_e, 1, 1)
1661 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1662 grid.addWidget(gap_e, 2, 1)
1663 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1664 vbox.addLayout(grid)
1666 vbox.addLayout(ok_cancel_buttons(d))
1669 if not d.exec_(): return
1672 gap = int(unicode(gap_e.text()))
1674 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1678 seed = str(seed_e.text())
1681 print_error("Warning: Not hex, trying decode")
1683 seed = mnemonic.mn_decode( seed.split(' ') )
1685 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1689 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1695 def do_import_labels(self):
1696 labelsFile = QFileDialog.getOpenFileName(QWidget(), "Open text file", util.user_dir(), self.tr("Text Files (labels.dat)"))
1697 if not labelsFile: return
1699 f = open(labelsFile, 'r')
1702 for key, value in json.loads(data).items():
1703 self.wallet.labels[key] = value
1705 QMessageBox.information(None, "Labels imported", "Your labels where imported from '%s'" % str(labelsFile))
1706 except (IOError, os.error), reason:
1707 QMessageBox.critical(None, "Unable to import labels", "Electrum was unable to import your labels.\n" + str(reason))
1711 def do_export_labels(self):
1712 labels = self.wallet.labels
1714 labelsFile = util.user_dir() + '/labels.dat'
1715 f = open(labelsFile, 'w+')
1716 json.dump(labels, f)
1718 QMessageBox.information(None, "Labels exported", "Your labels where exported to '%s'" % str(labelsFile))
1719 except (IOError, os.error), reason:
1720 QMessageBox.critical(None, "Unable to export labels", "Electrum was unable to export your labels.\n" + str(reason))
1722 def do_export_history(self):
1723 from gui_lite import csv_transaction
1724 csv_transaction(self.wallet)
1726 def do_import_privkey(self):
1727 if not self.wallet.imported_keys:
1728 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1729 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1730 + _('Are you sure you understand what you are doing?'), 3, 4)
1733 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1735 sec = str(text).strip()
1736 if self.wallet.use_encryption:
1737 password = self.password_dialog()
1743 addr = self.wallet.import_key(sec, password)
1745 QMessageBox.critical(None, "Unable to import key", "error")
1747 QMessageBox.information(None, "Key imported", addr)
1748 self.update_receive_tab()
1749 self.update_history_tab()
1750 except BaseException as e:
1751 QMessageBox.critical(None, "Unable to import key", str(e))
1753 def settings_dialog(self):
1755 d.setWindowTitle(_('Electrum Settings'))
1757 vbox = QVBoxLayout()
1759 tabs = QTabWidget(self)
1760 vbox.addWidget(tabs)
1763 grid_ui = QGridLayout(tab1)
1764 grid_ui.setColumnStretch(0,1)
1765 tabs.addTab(tab1, _('Display') )
1767 nz_label = QLabel(_('Display zeros'))
1768 grid_ui.addWidget(nz_label, 3, 0)
1770 nz_e.setText("%d"% self.wallet.num_zeros)
1771 grid_ui.addWidget(nz_e, 3, 1)
1772 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1773 grid_ui.addWidget(HelpButton(msg), 3, 2)
1774 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1775 if not self.config.is_modifiable('num_zeros'):
1776 for w in [nz_e, nz_label]: w.setEnabled(False)
1778 lang_label=QLabel(_('Language') + ':')
1779 grid_ui.addWidget(lang_label , 8, 0)
1780 lang_combo = QComboBox()
1781 from i18n import languages
1782 lang_combo.addItems(languages.values())
1784 index = languages.keys().index(self.config.get("language",''))
1787 lang_combo.setCurrentIndex(index)
1788 grid_ui.addWidget(lang_combo, 8, 1)
1789 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1790 if not self.config.is_modifiable('language'):
1791 for w in [lang_combo, lang_label]: w.setEnabled(False)
1793 currencies = self.exchanger.get_currencies()
1794 currencies.insert(0, "None")
1796 cur_label=QLabel(_('Currency') + ':')
1797 grid_ui.addWidget(cur_label , 9, 0)
1798 cur_combo = QComboBox()
1799 cur_combo.addItems(currencies)
1801 index = currencies.index(self.config.get('currency', "None"))
1804 cur_combo.setCurrentIndex(index)
1805 grid_ui.addWidget(cur_combo, 9, 1)
1806 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1808 view_label=QLabel(_('Receive Tab') + ':')
1809 grid_ui.addWidget(view_label , 10, 0)
1810 view_combo = QComboBox()
1811 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1812 view_combo.setCurrentIndex(self.receive_tab_mode)
1813 grid_ui.addWidget(view_combo, 10, 1)
1814 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1815 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1816 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1817 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1819 grid_ui.addWidget(HelpButton(hh), 10, 2)
1823 grid_wallet = QGridLayout(tab2)
1824 grid_wallet.setColumnStretch(0,1)
1825 tabs.addTab(tab2, _('Wallet') )
1827 fee_label = QLabel(_('Transaction fee'))
1828 grid_wallet.addWidget(fee_label, 0, 0)
1830 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1831 grid_wallet.addWidget(fee_e, 0, 1)
1832 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1833 + _('Recommended value') + ': 0.001'
1834 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1835 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1836 if not self.config.is_modifiable('fee'):
1837 for w in [fee_e, fee_label]: w.setEnabled(False)
1839 usechange_label = QLabel(_('Use change addresses'))
1840 grid_wallet.addWidget(usechange_label, 1, 0)
1841 usechange_combo = QComboBox()
1842 usechange_combo.addItems([_('Yes'), _('No')])
1843 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1844 grid_wallet.addWidget(usechange_combo, 1, 1)
1845 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1846 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1848 gap_label = QLabel(_('Gap limit'))
1849 grid_wallet.addWidget(gap_label, 2, 0)
1851 gap_e.setText("%d"% self.wallet.gap_limit)
1852 grid_wallet.addWidget(gap_e, 2, 1)
1853 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1854 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1855 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1856 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1857 + _('Warning') + ': ' \
1858 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1859 + _('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'
1860 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1861 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1862 if not self.config.is_modifiable('gap_limit'):
1863 for w in [gap_e, gap_label]: w.setEnabled(False)
1865 grid_wallet.setRowStretch(3,1)
1870 grid_io = QGridLayout(tab3)
1871 grid_io.setColumnStretch(0,1)
1872 tabs.addTab(tab3, _('Import/Export') )
1874 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1875 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1876 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1877 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1879 grid_io.addWidget(QLabel(_('History')), 2, 0)
1880 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1881 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1883 grid_io.addWidget(QLabel(_('Private key')), 3, 0)
1884 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1885 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1887 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1888 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1889 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1890 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1891 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1893 grid_io.setRowStretch(4,1)
1894 vbox.addLayout(ok_cancel_buttons(d))
1898 if not d.exec_(): return
1900 fee = unicode(fee_e.text())
1902 fee = int( 100000000 * Decimal(fee) )
1904 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1907 if self.wallet.fee != fee:
1908 self.wallet.fee = fee
1911 nz = unicode(nz_e.text())
1916 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1919 if self.wallet.num_zeros != nz:
1920 self.wallet.num_zeros = nz
1921 self.config.set_key('num_zeros', nz, True)
1922 self.update_history_tab()
1923 self.update_receive_tab()
1925 usechange_result = usechange_combo.currentIndex() == 0
1926 if self.wallet.use_change != usechange_result:
1927 self.wallet.use_change = usechange_result
1928 self.config.set_key('use_change', self.wallet.use_change, True)
1931 n = int(gap_e.text())
1933 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1936 if self.wallet.gap_limit != n:
1937 r = self.wallet.change_gap_limit(n)
1939 self.update_receive_tab()
1940 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1942 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1944 need_restart = False
1946 lang_request = languages.keys()[lang_combo.currentIndex()]
1947 if lang_request != self.config.get('language'):
1948 self.config.set_key("language", lang_request, True)
1951 cur_request = str(currencies[cur_combo.currentIndex()])
1952 if cur_request != self.config.get('currency', "None"):
1953 self.config.set_key('currency', cur_request, True)
1954 self.update_wallet()
1957 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1959 self.receive_tab_set_mode(view_combo.currentIndex())
1963 def network_dialog(wallet, parent=None):
1964 interface = wallet.interface
1966 if interface.is_connected:
1967 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
1969 status = _("Not connected")
1970 server = interface.server
1973 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1974 server = interface.server
1976 plist, servers_list = interface.get_servers_list()
1980 d.setWindowTitle(_('Server'))
1981 d.setMinimumSize(375, 20)
1983 vbox = QVBoxLayout()
1986 hbox = QHBoxLayout()
1988 l.setPixmap(QPixmap(":icons/network.png"))
1991 hbox.addWidget(QLabel(status))
1993 vbox.addLayout(hbox)
1997 grid = QGridLayout()
1999 vbox.addLayout(grid)
2002 server_protocol = QComboBox()
2003 server_host = QLineEdit()
2004 server_host.setFixedWidth(200)
2005 server_port = QLineEdit()
2006 server_port.setFixedWidth(60)
2008 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2009 protocol_letters = 'thsg'
2010 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2011 server_protocol.addItems(protocol_names)
2013 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2014 grid.addWidget(server_protocol, 0, 1)
2015 grid.addWidget(server_host, 0, 2)
2016 grid.addWidget(server_port, 0, 3)
2018 def change_protocol(p):
2019 protocol = protocol_letters[p]
2020 host = unicode(server_host.text())
2021 pp = plist.get(host,DEFAULT_PORTS)
2022 if protocol not in pp.keys():
2023 protocol = pp.keys()[0]
2025 server_host.setText( host )
2026 server_port.setText( port )
2028 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2030 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2031 servers_list_widget = QTreeWidget(parent)
2032 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2033 servers_list_widget.setMaximumHeight(150)
2034 servers_list_widget.setColumnWidth(0, 240)
2035 for _host in servers_list.keys():
2036 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2037 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2039 def change_server(host, protocol=None):
2040 pp = plist.get(host,DEFAULT_PORTS)
2042 port = pp.get(protocol)
2043 if not port: protocol = None
2046 if 't' in pp.keys():
2048 port = pp.get(protocol)
2050 protocol = pp.keys()[0]
2051 port = pp.get(protocol)
2053 server_host.setText( host )
2054 server_port.setText( port )
2055 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2057 if not plist: return
2058 for p in protocol_letters:
2059 i = protocol_letters.index(p)
2060 j = server_protocol.model().index(i,0)
2061 if p not in pp.keys():
2062 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2064 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2068 host, port, protocol = server.split(':')
2069 change_server(host,protocol)
2071 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2072 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2074 if not wallet.config.is_modifiable('server'):
2075 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2078 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2079 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2080 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2081 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2084 proxy_mode = QComboBox()
2085 proxy_host = QLineEdit()
2086 proxy_host.setFixedWidth(200)
2087 proxy_port = QLineEdit()
2088 proxy_port.setFixedWidth(60)
2089 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2091 def check_for_disable(index = False):
2092 if proxy_mode.currentText() != 'NONE':
2093 proxy_host.setEnabled(True)
2094 proxy_port.setEnabled(True)
2096 proxy_host.setEnabled(False)
2097 proxy_port.setEnabled(False)
2100 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2102 if not wallet.config.is_modifiable('proxy'):
2103 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2105 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2106 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2107 proxy_host.setText(proxy_config.get("host"))
2108 proxy_port.setText(proxy_config.get("port"))
2110 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2111 grid.addWidget(proxy_mode, 2, 1)
2112 grid.addWidget(proxy_host, 2, 2)
2113 grid.addWidget(proxy_port, 2, 3)
2116 vbox.addLayout(ok_cancel_buttons(d))
2119 if not d.exec_(): return
2121 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2122 if proxy_mode.currentText() != 'NONE':
2123 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2127 wallet.config.set_key("proxy", proxy, True)
2128 wallet.config.set_key("server", server, True)
2129 interface.set_server(server, proxy)
2130 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2133 def closeEvent(self, event):
2135 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2136 self.save_column_widths()
2137 self.config.set_key("column-widths", self.column_widths, True)
2143 def __init__(self, wallet, config, app=None):
2144 self.wallet = wallet
2145 self.config = config
2147 self.app = QApplication(sys.argv)
2150 def restore_or_create(self):
2151 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2152 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2153 if r==2: return None
2154 return 'restore' if r==1 else 'create'
2156 def seed_dialog(self):
2157 return ElectrumWindow.seed_dialog( self.wallet )
2159 def network_dialog(self):
2160 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2163 def show_seed(self):
2164 ElectrumWindow.show_seed_dialog(self.wallet)
2167 def password_dialog(self):
2168 ElectrumWindow.change_password_dialog(self.wallet)
2171 def restore_wallet(self):
2172 wallet = self.wallet
2173 # wait until we are connected, because the user might have selected another server
2174 if not wallet.interface.is_connected:
2175 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
2176 waiting_dialog(waiting)
2178 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
2179 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
2181 wallet.set_up_to_date(False)
2182 wallet.interface.poke('synchronizer')
2183 waiting_dialog(waiting)
2184 if wallet.is_found():
2185 print_error( "Recovery successful" )
2187 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2194 w = ElectrumWindow(self.wallet, self.config)
2195 if url: w.set_url(url)