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)
67 self.setText("New version available:")
69 con = httplib.HTTPConnection('electrum.bysh.me', 80, timeout=5)
70 con.request("GET", "/version")
71 res = con.getresponse()
73 latest_version = res.read()
74 latest_version = latest_version.replace("\n","")
75 if(re.match('^\d\.\d.\d$', latest_version)):
78 self.latest_version = tuple(latest_version.split("."))
79 self.latest_version_text = latest_version
80 self.current_version = tuple(ELECTRUM_VERSION.split("."))
82 if(self.latest_version > self.current_version):
83 latest_seen = self.config.get("last_seen_version")
84 if(self.latest_version > latest_seen):
85 self.setText("New version available: " + '.'.join(list(self.latest_version)))
87 except socket.error as msg:
88 print "Could not retrieve version information"
90 def ignore_this_version(self):
92 self.config.set_key("last_seen_version", self.latest_version)
93 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
96 def ignore_all_version(self):
98 self.config.set_key("last_seen_version", tuple(['9','9','9']))
99 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
102 def open_website(self):
103 webbrowser.open("http://electrum.org/download.html")
106 def mouseReleaseEvent(self, event):
107 dialog = QDialog(self)
108 dialog.setWindowTitle(_('Electrum update'))
111 main_layout = QGridLayout()
112 main_layout.addWidget(QLabel("A new version of Electrum is available: " + self.latest_version_text), 0,0,1,3)
114 ignore_version = QPushButton(_("Ignore this version"))
115 ignore_version.clicked.connect(self.ignore_this_version)
117 ignore_all_versions = QPushButton(_("Ignore all versions"))
118 ignore_all_versions.clicked.connect(self.ignore_all_version)
120 open_website = QPushButton(_("Goto download page"))
121 open_website.clicked.connect(self.open_website)
123 main_layout.addWidget(ignore_version, 1, 0)
124 main_layout.addWidget(ignore_all_versions, 1, 1)
125 main_layout.addWidget(open_website, 1, 2)
127 dialog.setLayout(main_layout)
131 if not dialog.exec_(): return
133 def numbify(entry, is_int = False):
134 text = unicode(entry.text()).strip()
135 pos = entry.cursorPosition()
137 if not is_int: chars +='.'
138 s = ''.join([i for i in text if i in chars])
142 s = s.replace('.','')
143 s = s[:p] + '.' + s[p:p+8]
145 amount = int( Decimal(s) * 100000000 )
154 entry.setCursorPosition(pos)
158 class Timer(QtCore.QThread):
161 self.emit(QtCore.SIGNAL('timersignal'))
164 class HelpButton(QPushButton):
165 def __init__(self, text):
166 QPushButton.__init__(self, '?')
167 self.setFocusPolicy(Qt.NoFocus)
168 self.setFixedWidth(20)
169 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
172 class EnterButton(QPushButton):
173 def __init__(self, text, func):
174 QPushButton.__init__(self, text)
176 self.clicked.connect(func)
178 def keyPressEvent(self, e):
179 if e.key() == QtCore.Qt.Key_Return:
182 class MyTreeWidget(QTreeWidget):
183 def __init__(self, parent):
184 QTreeWidget.__init__(self, parent)
187 for i in range(0,self.viewport().height()/5):
188 if self.itemAt(QPoint(0,i*5)) == item:
192 for j in range(0,30):
193 if self.itemAt(QPoint(0,i*5 + j)) != item:
195 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
197 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
202 class StatusBarButton(QPushButton):
203 def __init__(self, icon, tooltip, func):
204 QPushButton.__init__(self, icon, '')
205 self.setToolTip(tooltip)
207 self.setMaximumWidth(25)
208 self.clicked.connect(func)
211 def keyPressEvent(self, e):
212 if e.key() == QtCore.Qt.Key_Return:
216 class QRCodeWidget(QWidget):
218 def __init__(self, data = None, size=4):
219 QWidget.__init__(self)
220 self.setMinimumSize(210, 210)
228 def set_addr(self, addr):
229 if self.addr != addr:
235 if self.addr and not self.qr:
236 self.qr = pyqrnative.QRCode(self.size, pyqrnative.QRErrorCorrectLevel.L)
237 self.qr.addData(self.addr)
241 def paintEvent(self, e):
246 black = QColor(0, 0, 0, 255)
247 white = QColor(255, 255, 255, 255)
250 qp = QtGui.QPainter()
254 qp.drawRect(0, 0, 198, 198)
258 k = self.qr.getModuleCount()
259 qp = QtGui.QPainter()
262 boxsize = min(r.width(), r.height())*0.8/k
264 left = (r.width() - size)/2
265 top = (r.height() - size)/2
269 if self.qr.isDark(r, c):
275 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
280 class QR_Window(QWidget):
282 def __init__(self, exchanger):
283 QWidget.__init__(self)
284 self.exchanger = exchanger
285 self.setWindowTitle('Electrum - Invoice')
286 self.setMinimumSize(800, 250)
290 self.setFocusPolicy(QtCore.Qt.NoFocus)
292 main_box = QHBoxLayout()
294 self.qrw = QRCodeWidget()
295 main_box.addWidget(self.qrw, 1)
298 main_box.addLayout(vbox)
300 self.address_label = QLabel("")
301 self.address_label.setFont(QFont(MONOSPACE_FONT))
302 vbox.addWidget(self.address_label)
304 self.label_label = QLabel("")
305 vbox.addWidget(self.label_label)
307 self.amount_label = QLabel("")
308 vbox.addWidget(self.amount_label)
311 self.setLayout(main_box)
314 def set_content(self, addr, label, amount, currency):
316 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
317 self.address_label.setText(address_text)
319 if currency == 'BTC': currency = None
323 self.amount = Decimal(amount) / self.exchanger.exchange(1, currency) if currency else amount
325 self.amount = Decimal(amount)
326 self.amount = self.amount.quantize(Decimal('1.0000'))
329 amount_text += "<span style='font-size: 18pt'>%s %s</span><br/>" % (amount, currency)
330 amount_text += "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % str(self.amount)
331 self.amount_label.setText(amount_text)
334 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
335 self.label_label.setText(label_text)
337 msg = 'bitcoin:'+self.address
338 if self.amount is not None:
339 msg += '?amount=%s'%(str( self.amount))
340 if self.label is not None:
341 msg += '&label=%s'%(self.label)
342 elif self.label is not None:
343 msg += '?label=%s'%(self.label)
345 self.qrw.set_addr( msg )
350 def waiting_dialog(f):
356 w.setWindowTitle('Electrum')
366 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
371 def ok_cancel_buttons(dialog):
374 b = QPushButton("OK")
376 b.clicked.connect(dialog.accept)
377 b = QPushButton("Cancel")
379 b.clicked.connect(dialog.reject)
383 default_column_widths = { "history":[40,140,350,140,140], "contacts":[350,330,100],
384 "receive":[[50,310,200,130,130,10],[50,310,200,130,130,10],[50,310,200,130,130,10]] }
386 class ElectrumWindow(QMainWindow):
388 def __init__(self, wallet, config):
389 QMainWindow.__init__(self)
393 self.wallet.interface.register_callback('updated', self.update_callback)
394 self.wallet.interface.register_callback('connected', self.update_callback)
395 self.wallet.interface.register_callback('disconnected', self.update_callback)
396 self.wallet.interface.register_callback('disconnecting', self.update_callback)
398 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
399 self.merchant_name = config.get('merchant_name', 'Invoice')
401 self.qr_window = None
402 self.funds_error = False
403 self.completions = QStringListModel()
405 self.tabs = tabs = QTabWidget(self)
406 self.column_widths = self.config.get("column-widths", default_column_widths )
407 tabs.addTab(self.create_history_tab(), _('History') )
408 tabs.addTab(self.create_send_tab(), _('Send') )
409 tabs.addTab(self.create_receive_tab(), _('Receive') )
410 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
411 tabs.addTab(self.create_wall_tab(), _('Wall') )
412 tabs.setMinimumSize(600, 400)
413 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
414 self.setCentralWidget(tabs)
415 self.create_status_bar()
417 g = self.config.get("winpos-qt",[100, 100, 840, 400])
418 self.setGeometry(g[0], g[1], g[2], g[3])
419 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
420 if not self.wallet.seed: title += ' [seedless]'
421 self.setWindowTitle( title )
423 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
424 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
425 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
426 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
428 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
429 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
430 self.history_list.setFocus(True)
432 self.exchanger = exchange_rate.Exchanger(self)
433 self.toggle_QR_window(self.receive_tab_mode == 2)
434 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
436 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
437 if platform.system() == 'Windows':
438 n = 3 if self.wallet.seed else 2
439 tabs.setCurrentIndex (n)
440 tabs.setCurrentIndex (0)
443 QMainWindow.close(self)
445 self.qr_window.close()
446 self.qr_window = None
448 def connect_slots(self, sender):
449 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
450 self.previous_payto_e=''
452 def timer_actions(self):
454 self.qr_window.qrw.update_qr()
456 if self.payto_e.hasFocus():
458 r = unicode( self.payto_e.text() )
459 if r != self.previous_payto_e:
460 self.previous_payto_e = r
462 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
464 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
468 s = r + ' <' + to_address + '>'
469 self.payto_e.setText(s)
472 def update_callback(self):
473 self.emit(QtCore.SIGNAL('updatesignal'))
475 def update_wallet(self):
476 if self.wallet.interface and self.wallet.interface.is_connected:
477 if not self.wallet.up_to_date:
478 text = _( "Synchronizing..." )
479 icon = QIcon(":icons/status_waiting.png")
481 c, u = self.wallet.get_balance()
482 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
483 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
484 text += self.create_quote_text(Decimal(c+u)/100000000)
485 icon = QIcon(":icons/status_connected.png")
487 text = _( "Not connected" )
488 icon = QIcon(":icons/status_disconnected.png")
490 self.status_text = text
491 self.statusBar().showMessage(text)
492 self.status_button.setIcon( icon )
494 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
495 self.textbox.setText( self.wallet.banner )
496 self.update_history_tab()
497 self.update_receive_tab()
498 self.update_contacts_tab()
499 self.update_completions()
501 def create_quote_text(self, btc_balance):
502 quote_currency = self.config.get("currency", "None")
503 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
504 if quote_balance is None:
507 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
510 def create_history_tab(self):
511 self.history_list = l = MyTreeWidget(self)
513 for i,width in enumerate(self.column_widths['history']):
514 l.setColumnWidth(i, width)
515 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
516 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
517 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
519 l.setContextMenuPolicy(Qt.CustomContextMenu)
520 l.customContextMenuRequested.connect(self.create_history_menu)
524 def create_history_menu(self, position):
525 self.history_list.selectedIndexes()
526 item = self.history_list.currentItem()
528 tx_hash = str(item.data(0, Qt.UserRole).toString())
529 if not tx_hash: return
531 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
532 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
533 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
534 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
537 def tx_details(self, tx_hash):
538 dialog = QDialog(None)
540 dialog.setWindowTitle(_("Transaction Details"))
542 main_text = QTextEdit()
543 main_text.setText(self.wallet.get_tx_details(tx_hash))
544 main_text.setReadOnly(True)
545 main_text.setMinimumSize(550,275)
547 ok_button = QPushButton(_("OK"))
548 ok_button.setDefault(True)
549 ok_button.clicked.connect(dialog.accept)
553 hbox.addWidget(ok_button)
556 vbox.addWidget(main_text)
558 dialog.setLayout(vbox)
561 def tx_label_clicked(self, item, column):
562 if column==2 and item.isSelected():
564 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
565 self.history_list.editItem( item, column )
566 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
569 def tx_label_changed(self, item, column):
573 tx_hash = str(item.data(0, Qt.UserRole).toString())
574 tx = self.wallet.transactions.get(tx_hash)
575 s = self.wallet.labels.get(tx_hash)
576 text = unicode( item.text(2) )
578 self.wallet.labels[tx_hash] = text
579 item.setForeground(2, QBrush(QColor('black')))
581 if s: self.wallet.labels.pop(tx_hash)
582 text = self.wallet.get_default_label(tx_hash)
583 item.setText(2, text)
584 item.setForeground(2, QBrush(QColor('gray')))
588 def edit_label(self, is_recv):
589 l = self.receive_list if is_recv else self.contacts_list
590 c = 2 if is_recv else 1
591 item = l.currentItem()
592 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
593 l.editItem( item, c )
594 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
596 def edit_amount(self):
597 l = self.receive_list
598 item = l.currentItem()
599 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
600 l.editItem( item, 3 )
601 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
604 def address_label_clicked(self, item, column, l, column_addr, column_label):
605 if column == column_label and item.isSelected():
606 addr = unicode( item.text(column_addr) )
607 label = unicode( item.text(column_label) )
608 if label in self.wallet.aliases.keys():
610 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
611 l.editItem( item, column )
612 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
615 def address_label_changed(self, item, column, l, column_addr, column_label):
617 if column == column_label:
618 addr = unicode( item.text(column_addr) )
619 text = unicode( item.text(column_label) )
623 if text not in self.wallet.aliases.keys():
624 old_addr = self.wallet.labels.get(text)
626 self.wallet.labels[addr] = text
629 print_error("Error: This is one of your aliases")
630 label = self.wallet.labels.get(addr,'')
631 item.setText(column_label, QString(label))
633 s = self.wallet.labels.get(addr)
635 self.wallet.labels.pop(addr)
639 self.update_history_tab()
640 self.update_completions()
642 self.recv_changed(item)
645 address = str( item.text(column_addr) )
646 text = str( item.text(3) )
648 index = self.wallet.addresses.index(address)
652 text = text.strip().upper()
653 m = re.match('^(\d+(|\.\d*))\s*(|BTC|EUR|USD|GBP|CNY|JPY|RUB|BRL)$', text)
656 currency = m.group(3)
660 currency = currency.upper()
661 self.wallet.requested_amounts[address] = (amount, currency)
663 label = self.wallet.labels.get(address)
665 label = self.merchant_name + ' - %04d'%(index+1)
666 self.wallet.labels[address] = label
669 self.qr_window.set_content( address, label, amount, currency )
673 if address in self.wallet.requested_amounts:
674 self.wallet.requested_amounts.pop(address)
676 self.update_receive_item(self.receive_list.currentItem())
679 def recv_changed(self, a):
680 "current item changed"
681 if a is not None and self.qr_window and self.qr_window.isVisible():
682 address = str(a.text(1))
683 label = self.wallet.labels.get(address)
685 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
687 amount, currency = None, None
688 self.qr_window.set_content( address, label, amount, currency )
691 def update_history_tab(self):
693 self.history_list.clear()
694 for item in self.wallet.get_tx_history():
695 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
698 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
704 icon = QIcon(":icons/unconfirmed.png")
706 icon = QIcon(":icons/clock%d.png"%conf)
708 icon = QIcon(":icons/confirmed.png")
711 icon = QIcon(":icons/unconfirmed.png")
713 if value is not None:
714 v_str = format_satoshis(value, True, self.wallet.num_zeros)
718 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
721 label, is_default_label = self.wallet.get_label(tx_hash)
723 label = _('Pruned transaction outputs')
724 is_default_label = False
726 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
727 item.setFont(2, QFont(MONOSPACE_FONT))
728 item.setFont(3, QFont(MONOSPACE_FONT))
729 item.setFont(4, QFont(MONOSPACE_FONT))
731 item.setForeground(3, QBrush(QColor("#BC1E1E")))
733 item.setData(0, Qt.UserRole, tx_hash)
734 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
736 item.setForeground(2, QBrush(QColor('grey')))
738 item.setIcon(0, icon)
739 self.history_list.insertTopLevelItem(0,item)
742 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
745 def create_send_tab(self):
750 grid.setColumnMinimumWidth(3,300)
751 grid.setColumnStretch(5,1)
753 self.payto_e = QLineEdit()
754 grid.addWidget(QLabel(_('Pay to')), 1, 0)
755 grid.addWidget(self.payto_e, 1, 1, 1, 3)
758 qrcode = qrscanner.scan_qr()
759 if 'address' in qrcode:
760 self.payto_e.setText(qrcode['address'])
761 if 'amount' in qrcode:
762 self.amount_e.setText(str(qrcode['amount']))
763 if 'label' in qrcode:
764 self.message_e.setText(qrcode['label'])
765 if 'message' in qrcode:
766 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
769 if qrscanner.is_available():
770 b = QPushButton(_("Scan QR code"))
771 b.clicked.connect(fill_from_qr)
772 grid.addWidget(b, 1, 5)
774 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)
776 completer = QCompleter()
777 completer.setCaseSensitivity(False)
778 self.payto_e.setCompleter(completer)
779 completer.setModel(self.completions)
781 self.message_e = QLineEdit()
782 grid.addWidget(QLabel(_('Description')), 2, 0)
783 grid.addWidget(self.message_e, 2, 1, 1, 3)
784 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)
786 self.amount_e = QLineEdit()
787 grid.addWidget(QLabel(_('Amount')), 3, 0)
788 grid.addWidget(self.amount_e, 3, 1, 1, 2)
789 grid.addWidget(HelpButton(
790 _('Amount to be sent.') + '\n\n' \
791 + _('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)
793 self.fee_e = QLineEdit()
794 grid.addWidget(QLabel(_('Fee')), 4, 0)
795 grid.addWidget(self.fee_e, 4, 1, 1, 2)
796 grid.addWidget(HelpButton(
797 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
798 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
799 + _('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)
801 b = EnterButton(_("Send"), self.do_send)
802 grid.addWidget(b, 6, 1)
804 b = EnterButton(_("Clear"),self.do_clear)
805 grid.addWidget(b, 6, 2)
807 self.payto_sig = QLabel('')
808 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
810 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
811 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
820 def entry_changed( is_fee ):
821 self.funds_error = False
822 amount = numbify(self.amount_e)
823 fee = numbify(self.fee_e)
824 if not is_fee: fee = None
827 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
829 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
832 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
833 text = self.status_text
836 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
837 self.funds_error = True
838 text = _( "Not enough funds" )
840 self.statusBar().showMessage(text)
841 self.amount_e.setPalette(palette)
842 self.fee_e.setPalette(palette)
844 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
845 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
850 def update_completions(self):
852 for addr,label in self.wallet.labels.items():
853 if addr in self.wallet.addressbook:
854 l.append( label + ' <' + addr + '>')
855 l = l + self.wallet.aliases.keys()
857 self.completions.setStringList(l)
863 label = unicode( self.message_e.text() )
864 r = unicode( self.payto_e.text() )
868 m1 = re.match(ALIAS_REGEXP, r)
869 # label or alias, with address in brackets
870 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
873 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
877 to_address = m2.group(2)
881 if not self.wallet.is_valid(to_address):
882 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
886 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
888 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
891 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
893 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
896 if self.wallet.use_encryption:
897 password = self.password_dialog()
904 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
905 except BaseException, e:
906 self.show_message(str(e))
910 h = self.wallet.send_tx(tx)
911 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
912 status, msg = self.wallet.receive_tx( h )
914 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
916 self.update_contacts_tab()
918 QMessageBox.warning(self, _('Error'), msg, _('OK'))
920 filename = 'unsigned_tx'
921 f = open(filename,'w')
924 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
927 def set_url(self, url):
928 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
929 self.tabs.setCurrentIndex(1)
930 label = self.wallet.labels.get(payto)
931 m_addr = label + ' <'+ payto+'>' if label else payto
932 self.payto_e.setText(m_addr)
934 self.message_e.setText(message)
935 self.amount_e.setText(amount)
937 self.set_frozen(self.payto_e,True)
938 self.set_frozen(self.amount_e,True)
939 self.set_frozen(self.message_e,True)
940 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
942 self.payto_sig.setVisible(False)
945 self.payto_sig.setVisible(False)
946 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
948 self.set_frozen(e,False)
950 def set_frozen(self,entry,frozen):
952 entry.setReadOnly(True)
953 entry.setFrame(False)
955 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
956 entry.setPalette(palette)
958 entry.setReadOnly(False)
961 palette.setColor(entry.backgroundRole(), QColor('white'))
962 entry.setPalette(palette)
965 def toggle_freeze(self,addr):
967 if addr in self.wallet.frozen_addresses:
968 self.wallet.unfreeze(addr)
970 self.wallet.freeze(addr)
971 self.update_receive_tab()
973 def toggle_priority(self,addr):
975 if addr in self.wallet.prioritized_addresses:
976 self.wallet.unprioritize(addr)
978 self.wallet.prioritize(addr)
979 self.update_receive_tab()
982 def create_list_tab(self, headers):
983 "generic tab creation method"
984 l = MyTreeWidget(self)
985 l.setColumnCount( len(headers) )
986 l.setHeaderLabels( headers )
996 vbox.addWidget(buttons)
1001 buttons.setLayout(hbox)
1006 def create_receive_tab(self):
1007 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
1008 l.setContextMenuPolicy(Qt.CustomContextMenu)
1009 l.customContextMenuRequested.connect(self.create_receive_menu)
1010 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
1011 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
1012 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
1013 self.receive_list = l
1014 self.receive_buttons_hbox = hbox
1020 def receive_tab_set_mode(self, i):
1021 self.save_column_widths()
1022 self.receive_tab_mode = i
1023 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
1025 self.update_receive_tab()
1026 self.toggle_QR_window(self.receive_tab_mode == 2)
1028 def save_column_widths(self):
1030 for i in range(self.receive_list.columnCount()):
1031 widths.append(self.receive_list.columnWidth(i))
1032 self.column_widths["receive"][self.receive_tab_mode] = widths
1033 self.column_widths["history"] = []
1034 for i in range(self.history_list.columnCount()):
1035 self.column_widths["history"].append(self.history_list.columnWidth(i))
1036 self.column_widths["contacts"] = []
1037 for i in range(self.contacts_list.columnCount()):
1038 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1040 def create_contacts_tab(self):
1041 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1042 l.setContextMenuPolicy(Qt.CustomContextMenu)
1043 l.customContextMenuRequested.connect(self.create_contact_menu)
1044 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1045 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1046 self.contacts_list = l
1047 self.contacts_buttons_hbox = hbox
1048 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1053 def delete_imported_key(self, addr):
1054 if self.question("Do you want to remove %s from your wallet?"%addr):
1055 self.wallet.imported_keys.pop(addr)
1056 self.update_receive_tab()
1057 self.update_history_tab()
1061 def create_receive_menu(self, position):
1062 # fixme: this function apparently has a side effect.
1063 # if it is not called the menu pops up several times
1064 #self.receive_list.selectedIndexes()
1066 item = self.receive_list.itemAt(position)
1068 addr = unicode(item.text(1))
1070 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1071 if self.receive_tab_mode == 2:
1072 menu.addAction(_("Request amount"), lambda: self.edit_amount())
1073 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
1074 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1075 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1076 if addr in self.wallet.imported_keys:
1077 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1079 if self.receive_tab_mode == 1:
1080 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1081 menu.addAction(t, lambda: self.toggle_freeze(addr))
1082 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1083 menu.addAction(t, lambda: self.toggle_priority(addr))
1085 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1088 def payto(self, x, is_alias):
1095 label = self.wallet.labels.get(addr)
1096 m_addr = label + ' <' + addr + '>' if label else addr
1097 self.tabs.setCurrentIndex(1)
1098 self.payto_e.setText(m_addr)
1099 self.amount_e.setFocus()
1101 def delete_contact(self, x, is_alias):
1102 if self.question("Do you want to remove %s from your list of contacts?"%x):
1103 if not is_alias and x in self.wallet.addressbook:
1104 self.wallet.addressbook.remove(x)
1105 if x in self.wallet.labels.keys():
1106 self.wallet.labels.pop(x)
1107 elif is_alias and x in self.wallet.aliases:
1108 self.wallet.aliases.pop(x)
1109 self.update_history_tab()
1110 self.update_contacts_tab()
1111 self.update_completions()
1113 def create_contact_menu(self, position):
1114 # fixme: this function apparently has a side effect.
1115 # if it is not called the menu pops up several times
1116 #self.contacts_list.selectedIndexes()
1118 item = self.contacts_list.itemAt(position)
1120 addr = unicode(item.text(0))
1121 label = unicode(item.text(1))
1122 is_alias = label in self.wallet.aliases.keys()
1123 x = label if is_alias else addr
1125 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1126 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1127 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
1129 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1131 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1132 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1133 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1136 def update_receive_item(self, item):
1137 address = str( item.data(1,0).toString() )
1139 flags = self.wallet.get_address_flags(address)
1140 item.setData(0,0,flags)
1142 label = self.wallet.labels.get(address,'')
1143 item.setData(2,0,label)
1146 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1148 amount, currency = None, None
1150 amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
1151 item.setData(3,0,amount_str)
1153 c, u = self.wallet.get_addr_balance(address)
1154 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1155 item.setData(4,0,balance)
1157 if self.receive_tab_mode == 1:
1158 if address in self.wallet.frozen_addresses:
1159 item.setBackgroundColor(1, QColor('lightblue'))
1160 elif address in self.wallet.prioritized_addresses:
1161 item.setBackgroundColor(1, QColor('lightgreen'))
1164 def update_receive_tab(self):
1165 l = self.receive_list
1168 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1169 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1170 l.setColumnHidden(4, self.receive_tab_mode == 0)
1171 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1172 for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
1173 l.setColumnWidth(i, width)
1177 for address in self.wallet.all_addresses():
1179 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1183 h = self.wallet.history.get(address,[])
1186 for tx_hash, tx_height in h:
1187 tx = self.wallet.transactions.get(tx_hash)
1195 if address in self.wallet.addresses:
1197 if gap > self.wallet.gap_limit:
1200 if address in self.wallet.addresses:
1203 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1204 item.setFont(0, QFont(MONOSPACE_FONT))
1205 item.setFont(1, QFont(MONOSPACE_FONT))
1206 item.setFont(3, QFont(MONOSPACE_FONT))
1207 self.update_receive_item(item)
1208 if is_red and address in self.wallet.addresses:
1209 item.setBackgroundColor(1, QColor('red'))
1210 l.addTopLevelItem(item)
1212 # we use column 1 because column 0 may be hidden
1213 l.setCurrentItem(l.topLevelItem(0),1)
1215 def show_contact_details(self, m):
1216 a = self.wallet.aliases.get(m)
1218 if a[0] in self.wallet.authorities.keys():
1219 s = self.wallet.authorities.get(a[0])
1222 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1223 QMessageBox.information(self, 'Alias', msg, 'OK')
1225 def update_contacts_tab(self):
1227 l = self.contacts_list
1229 for i,width in enumerate(self.column_widths['contacts']):
1230 l.setColumnWidth(i, width)
1233 for alias, v in self.wallet.aliases.items():
1235 alias_targets.append(target)
1236 item = QTreeWidgetItem( [ target, alias, '-'] )
1237 item.setBackgroundColor(0, QColor('lightgray'))
1238 l.addTopLevelItem(item)
1240 for address in self.wallet.addressbook:
1241 if address in alias_targets: continue
1242 label = self.wallet.labels.get(address,'')
1244 for item in self.wallet.transactions.values():
1245 if address in item['outputs'] : n=n+1
1247 item = QTreeWidgetItem( [ address, label, tx] )
1248 item.setFont(0, QFont(MONOSPACE_FONT))
1249 l.addTopLevelItem(item)
1251 l.setCurrentItem(l.topLevelItem(0))
1253 def create_wall_tab(self):
1254 self.textbox = textbox = QTextEdit(self)
1255 textbox.setFont(QFont(MONOSPACE_FONT))
1256 textbox.setReadOnly(True)
1260 def create_status_bar(self):
1261 self.status_text = ""
1263 sb.setFixedHeight(35)
1264 qtVersion = qVersion()
1266 update_notification = UpdateLabel(self.config)
1267 sb.addPermanentWidget(update_notification)
1269 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1270 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1271 if self.wallet.seed:
1272 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1273 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1274 if self.wallet.seed:
1275 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1276 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1277 sb.addPermanentWidget( self.status_button )
1279 self.setStatusBar(sb)
1283 self.config.set_key('gui', 'lite', True)
1286 self.lite.mini.show()
1288 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1289 self.lite.main(None)
1291 def new_contact_dialog(self):
1292 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1293 address = unicode(text)
1295 if self.wallet.is_valid(address):
1296 self.wallet.addressbook.append(address)
1298 self.update_contacts_tab()
1299 self.update_history_tab()
1300 self.update_completions()
1302 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1304 def show_master_public_key(self):
1305 dialog = QDialog(None)
1307 dialog.setWindowTitle("Master Public Key")
1309 main_text = QTextEdit()
1310 main_text.setText(self.wallet.master_public_key)
1311 main_text.setReadOnly(True)
1312 main_text.setMaximumHeight(170)
1313 qrw = QRCodeWidget(self.wallet.master_public_key, 6)
1315 ok_button = QPushButton(_("OK"))
1316 ok_button.setDefault(True)
1317 ok_button.clicked.connect(dialog.accept)
1319 main_layout = QGridLayout()
1320 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1322 main_layout.addWidget(main_text, 1, 0)
1323 main_layout.addWidget(qrw, 1, 1 )
1325 vbox = QVBoxLayout()
1326 vbox.addLayout(main_layout)
1327 hbox = QHBoxLayout()
1329 hbox.addWidget(ok_button)
1330 vbox.addLayout(hbox)
1332 dialog.setLayout(vbox)
1337 def show_seed_dialog(wallet, parent=None):
1339 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1342 if wallet.use_encryption:
1343 password = parent.password_dialog()
1350 seed = wallet.decode_seed(password)
1352 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1355 dialog = QDialog(None)
1357 dialog.setWindowTitle(_("Electrum") + ' - ' + _('Seed'))
1359 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1361 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1363 seed_text = QTextEdit(brainwallet)
1364 seed_text.setReadOnly(True)
1365 seed_text.setMaximumHeight(130)
1367 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1368 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1369 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1370 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1371 label2 = QLabel(msg2)
1372 label2.setWordWrap(True)
1375 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1376 logo.setMaximumWidth(60)
1378 qrw = QRCodeWidget(seed, 4)
1380 ok_button = QPushButton(_("OK"))
1381 ok_button.setDefault(True)
1382 ok_button.clicked.connect(dialog.accept)
1384 grid = QGridLayout()
1385 #main_layout.addWidget(logo, 0, 0)
1387 grid.addWidget(logo, 0, 0)
1388 grid.addWidget(label1, 0, 1)
1390 grid.addWidget(seed_text, 1, 0, 1, 2)
1392 grid.addWidget(qrw, 0, 2, 2, 1)
1394 vbox = QVBoxLayout()
1395 vbox.addLayout(grid)
1396 vbox.addWidget(label2)
1398 hbox = QHBoxLayout()
1400 hbox.addWidget(ok_button)
1401 vbox.addLayout(hbox)
1403 dialog.setLayout(vbox)
1407 def show_qrcode(title, data):
1411 d.setWindowTitle(title)
1412 d.setMinimumSize(270, 300)
1413 vbox = QVBoxLayout()
1414 qrw = QRCodeWidget(data)
1415 vbox.addWidget(qrw, 1)
1416 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1417 hbox = QHBoxLayout()
1421 filename = "qrcode.bmp"
1422 bmp.save_qrcode(qrw.qr, filename)
1423 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1425 b = QPushButton(_("Print"))
1427 b.clicked.connect(print_qr)
1429 b = QPushButton(_("Close"))
1431 b.clicked.connect(d.accept)
1433 vbox.addLayout(hbox)
1437 def sign_message(self,address):
1438 if not address: return
1441 d.setWindowTitle('Sign Message')
1442 d.setMinimumSize(410, 290)
1444 tab_widget = QTabWidget()
1446 layout = QGridLayout(tab)
1448 sign_address = QLineEdit()
1450 sign_address.setText(address)
1451 layout.addWidget(QLabel(_('Address')), 1, 0)
1452 layout.addWidget(sign_address, 1, 1)
1454 sign_message = QTextEdit()
1455 layout.addWidget(QLabel(_('Message')), 2, 0)
1456 layout.addWidget(sign_message, 2, 1)
1457 layout.setRowStretch(2,3)
1459 sign_signature = QTextEdit()
1460 layout.addWidget(QLabel(_('Signature')), 3, 0)
1461 layout.addWidget(sign_signature, 3, 1)
1462 layout.setRowStretch(3,1)
1465 if self.wallet.use_encryption:
1466 password = self.password_dialog()
1473 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1474 sign_signature.setText(signature)
1475 except BaseException, e:
1476 self.show_message(str(e))
1479 hbox = QHBoxLayout()
1480 b = QPushButton(_("Sign"))
1482 b.clicked.connect(do_sign)
1483 b = QPushButton(_("Close"))
1484 b.clicked.connect(d.accept)
1486 layout.addLayout(hbox, 4, 1)
1487 tab_widget.addTab(tab, "Sign")
1491 layout = QGridLayout(tab)
1493 verify_address = QLineEdit()
1494 layout.addWidget(QLabel(_('Address')), 1, 0)
1495 layout.addWidget(verify_address, 1, 1)
1497 verify_message = QTextEdit()
1498 layout.addWidget(QLabel(_('Message')), 2, 0)
1499 layout.addWidget(verify_message, 2, 1)
1500 layout.setRowStretch(2,3)
1502 verify_signature = QTextEdit()
1503 layout.addWidget(QLabel(_('Signature')), 3, 0)
1504 layout.addWidget(verify_signature, 3, 1)
1505 layout.setRowStretch(3,1)
1509 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1510 self.show_message("Signature verified")
1511 except BaseException, e:
1512 self.show_message(str(e))
1515 hbox = QHBoxLayout()
1516 b = QPushButton(_("Verify"))
1517 b.clicked.connect(do_verify)
1519 b = QPushButton(_("Close"))
1520 b.clicked.connect(d.accept)
1522 layout.addLayout(hbox, 4, 1)
1523 tab_widget.addTab(tab, "Verify")
1525 vbox = QVBoxLayout()
1526 vbox.addWidget(tab_widget)
1531 def toggle_QR_window(self, show):
1532 if show and not self.qr_window:
1533 self.qr_window = QR_Window(self.exchanger)
1534 self.qr_window.setVisible(True)
1535 self.qr_window_geometry = self.qr_window.geometry()
1536 item = self.receive_list.currentItem()
1538 address = str(item.text(1))
1539 label = self.wallet.labels.get(address)
1540 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1541 self.qr_window.set_content( address, label, amount, currency )
1543 elif show and self.qr_window and not self.qr_window.isVisible():
1544 self.qr_window.setVisible(True)
1545 self.qr_window.setGeometry(self.qr_window_geometry)
1547 elif not show and self.qr_window and self.qr_window.isVisible():
1548 self.qr_window_geometry = self.qr_window.geometry()
1549 self.qr_window.setVisible(False)
1551 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1552 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1553 self.receive_list.setColumnWidth(2, 200)
1556 def question(self, msg):
1557 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1559 def show_message(self, msg):
1560 QMessageBox.information(self, _('Message'), msg, _('OK'))
1562 def password_dialog(self ):
1569 vbox = QVBoxLayout()
1570 msg = _('Please enter your password')
1571 vbox.addWidget(QLabel(msg))
1573 grid = QGridLayout()
1575 grid.addWidget(QLabel(_('Password')), 1, 0)
1576 grid.addWidget(pw, 1, 1)
1577 vbox.addLayout(grid)
1579 vbox.addLayout(ok_cancel_buttons(d))
1582 if not d.exec_(): return
1583 return unicode(pw.text())
1590 def change_password_dialog( wallet, parent=None ):
1593 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1601 new_pw = QLineEdit()
1602 new_pw.setEchoMode(2)
1603 conf_pw = QLineEdit()
1604 conf_pw.setEchoMode(2)
1606 vbox = QVBoxLayout()
1608 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1609 +_('To disable wallet encryption, enter an empty new password.')) \
1610 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1612 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1613 +_("Leave these fields empty if you want to disable encryption.")
1614 vbox.addWidget(QLabel(msg))
1616 grid = QGridLayout()
1619 if wallet.use_encryption:
1620 grid.addWidget(QLabel(_('Password')), 1, 0)
1621 grid.addWidget(pw, 1, 1)
1623 grid.addWidget(QLabel(_('New Password')), 2, 0)
1624 grid.addWidget(new_pw, 2, 1)
1626 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1627 grid.addWidget(conf_pw, 3, 1)
1628 vbox.addLayout(grid)
1630 vbox.addLayout(ok_cancel_buttons(d))
1633 if not d.exec_(): return
1635 password = unicode(pw.text()) if wallet.use_encryption else None
1636 new_password = unicode(new_pw.text())
1637 new_password2 = unicode(conf_pw.text())
1640 seed = wallet.decode_seed(password)
1642 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1645 if new_password != new_password2:
1646 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1647 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1649 wallet.update_password(seed, password, new_password)
1652 def seed_dialog(wallet, parent=None):
1656 vbox = QVBoxLayout()
1657 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1658 vbox.addWidget(QLabel(msg))
1660 grid = QGridLayout()
1663 seed_e = QLineEdit()
1664 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1665 grid.addWidget(seed_e, 1, 1)
1669 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1670 grid.addWidget(gap_e, 2, 1)
1671 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1672 vbox.addLayout(grid)
1674 vbox.addLayout(ok_cancel_buttons(d))
1677 if not d.exec_(): return
1680 gap = int(unicode(gap_e.text()))
1682 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1686 seed = str(seed_e.text())
1689 print_error("Warning: Not hex, trying decode")
1691 seed = mnemonic.mn_decode( seed.split(' ') )
1693 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1697 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1703 def do_import_labels(self):
1704 labelsFile = QFileDialog.getOpenFileName(QWidget(), "Open text file", util.user_dir(), self.tr("Text Files (labels.dat)"))
1705 if not labelsFile: return
1707 f = open(labelsFile, 'r')
1710 for key, value in json.loads(data).items():
1711 self.wallet.labels[key] = value
1713 QMessageBox.information(None, "Labels imported", "Your labels where imported from '%s'" % str(labelsFile))
1714 except (IOError, os.error), reason:
1715 QMessageBox.critical(None, "Unable to import labels", "Electrum was unable to import your labels.\n" + str(reason))
1719 def do_export_labels(self):
1720 labels = self.wallet.labels
1722 labelsFile = util.user_dir() + '/labels.dat'
1723 f = open(labelsFile, 'w+')
1724 json.dump(labels, f)
1726 QMessageBox.information(None, "Labels exported", "Your labels where exported to '%s'" % str(labelsFile))
1727 except (IOError, os.error), reason:
1728 QMessageBox.critical(None, "Unable to export labels", "Electrum was unable to export your labels.\n" + str(reason))
1730 def do_export_history(self):
1731 from gui_lite import csv_transaction
1732 csv_transaction(self.wallet)
1734 def do_import_privkey(self):
1735 if not self.wallet.imported_keys:
1736 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1737 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1738 + _('Are you sure you understand what you are doing?'), 3, 4)
1741 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1743 sec = str(text).strip()
1744 if self.wallet.use_encryption:
1745 password = self.password_dialog()
1751 addr = self.wallet.import_key(sec, password)
1753 QMessageBox.critical(None, "Unable to import key", "error")
1755 QMessageBox.information(None, "Key imported", addr)
1756 self.update_receive_tab()
1757 self.update_history_tab()
1758 except BaseException as e:
1759 QMessageBox.critical(None, "Unable to import key", str(e))
1761 def settings_dialog(self):
1763 d.setWindowTitle(_('Electrum Settings'))
1765 vbox = QVBoxLayout()
1767 tabs = QTabWidget(self)
1768 vbox.addWidget(tabs)
1771 grid_ui = QGridLayout(tab1)
1772 grid_ui.setColumnStretch(0,1)
1773 tabs.addTab(tab1, _('Display') )
1775 nz_label = QLabel(_('Display zeros'))
1776 grid_ui.addWidget(nz_label, 3, 0)
1778 nz_e.setText("%d"% self.wallet.num_zeros)
1779 grid_ui.addWidget(nz_e, 3, 1)
1780 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1781 grid_ui.addWidget(HelpButton(msg), 3, 2)
1782 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1783 if not self.config.is_modifiable('num_zeros'):
1784 for w in [nz_e, nz_label]: w.setEnabled(False)
1786 lang_label=QLabel(_('Language') + ':')
1787 grid_ui.addWidget(lang_label , 8, 0)
1788 lang_combo = QComboBox()
1789 from i18n import languages
1790 lang_combo.addItems(languages.values())
1792 index = languages.keys().index(self.config.get("language",''))
1795 lang_combo.setCurrentIndex(index)
1796 grid_ui.addWidget(lang_combo, 8, 1)
1797 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1798 if not self.config.is_modifiable('language'):
1799 for w in [lang_combo, lang_label]: w.setEnabled(False)
1801 currencies = self.exchanger.get_currencies()
1802 currencies.insert(0, "None")
1804 cur_label=QLabel(_('Currency') + ':')
1805 grid_ui.addWidget(cur_label , 9, 0)
1806 cur_combo = QComboBox()
1807 cur_combo.addItems(currencies)
1809 index = currencies.index(self.config.get('currency', "None"))
1812 cur_combo.setCurrentIndex(index)
1813 grid_ui.addWidget(cur_combo, 9, 1)
1814 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1816 view_label=QLabel(_('Receive Tab') + ':')
1817 grid_ui.addWidget(view_label , 10, 0)
1818 view_combo = QComboBox()
1819 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1820 view_combo.setCurrentIndex(self.receive_tab_mode)
1821 grid_ui.addWidget(view_combo, 10, 1)
1822 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1823 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1824 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1825 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1827 grid_ui.addWidget(HelpButton(hh), 10, 2)
1831 grid_wallet = QGridLayout(tab2)
1832 grid_wallet.setColumnStretch(0,1)
1833 tabs.addTab(tab2, _('Wallet') )
1835 fee_label = QLabel(_('Transaction fee'))
1836 grid_wallet.addWidget(fee_label, 0, 0)
1838 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1839 grid_wallet.addWidget(fee_e, 0, 1)
1840 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1841 + _('Recommended value') + ': 0.001'
1842 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1843 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1844 if not self.config.is_modifiable('fee'):
1845 for w in [fee_e, fee_label]: w.setEnabled(False)
1847 usechange_label = QLabel(_('Use change addresses'))
1848 grid_wallet.addWidget(usechange_label, 1, 0)
1849 usechange_combo = QComboBox()
1850 usechange_combo.addItems(['Yes', 'No'])
1851 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1852 grid_wallet.addWidget(usechange_combo, 1, 1)
1853 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1854 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1856 gap_label = QLabel(_('Gap limit'))
1857 grid_wallet.addWidget(gap_label, 2, 0)
1859 gap_e.setText("%d"% self.wallet.gap_limit)
1860 grid_wallet.addWidget(gap_e, 2, 1)
1861 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1862 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1863 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1864 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1865 + _('Warning') + ': ' \
1866 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1867 + _('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'
1868 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1869 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1870 if not self.config.is_modifiable('gap_limit'):
1871 for w in [gap_e, gap_label]: w.setEnabled(False)
1873 grid_wallet.setRowStretch(3,1)
1878 grid_io = QGridLayout(tab3)
1879 grid_io.setColumnStretch(0,1)
1880 tabs.addTab(tab3, _('Import/Export') )
1882 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1883 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1884 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1885 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1887 grid_io.addWidget(QLabel(_('History')), 2, 0)
1888 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1889 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1891 grid_io.addWidget(QLabel(_('Private key')), 3, 0)
1892 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1893 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1895 grid_io.addWidget(QLabel(_('Master Public key')), 4, 0)
1896 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1897 grid_io.addWidget(HelpButton(_('Your master public key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1898 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1899 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1901 grid_io.setRowStretch(4,1)
1902 vbox.addLayout(ok_cancel_buttons(d))
1906 if not d.exec_(): return
1908 fee = unicode(fee_e.text())
1910 fee = int( 100000000 * Decimal(fee) )
1912 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1915 if self.wallet.fee != fee:
1916 self.wallet.fee = fee
1919 nz = unicode(nz_e.text())
1924 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1927 if self.wallet.num_zeros != nz:
1928 self.wallet.num_zeros = nz
1929 self.config.set_key('num_zeros', nz, True)
1930 self.update_history_tab()
1931 self.update_receive_tab()
1933 usechange_result = usechange_combo.currentIndex() == 0
1934 if self.wallet.use_change != usechange_result:
1935 self.wallet.use_change = usechange_result
1936 self.config.set_key('use_change', self.wallet.use_change, True)
1939 n = int(gap_e.text())
1941 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1944 if self.wallet.gap_limit != n:
1945 r = self.wallet.change_gap_limit(n)
1947 self.update_receive_tab()
1948 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1950 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1952 need_restart = False
1954 lang_request = languages.keys()[lang_combo.currentIndex()]
1955 if lang_request != self.config.get('language'):
1956 self.config.set_key("language", lang_request, True)
1959 cur_request = str(currencies[cur_combo.currentIndex()])
1960 if cur_request != self.config.get('currency', "None"):
1961 self.config.set_key('currency', cur_request, True)
1962 self.update_wallet()
1965 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1967 self.receive_tab_set_mode(view_combo.currentIndex())
1971 def network_dialog(wallet, parent=None):
1972 interface = wallet.interface
1974 if interface.is_connected:
1975 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
1977 status = _("Not connected")
1978 server = interface.server
1981 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1982 server = interface.server
1984 plist, servers_list = interface.get_servers_list()
1988 d.setWindowTitle(_('Server'))
1989 d.setMinimumSize(375, 20)
1991 vbox = QVBoxLayout()
1994 hbox = QHBoxLayout()
1996 l.setPixmap(QPixmap(":icons/network.png"))
1999 hbox.addWidget(QLabel(status))
2001 vbox.addLayout(hbox)
2005 grid = QGridLayout()
2007 vbox.addLayout(grid)
2010 server_protocol = QComboBox()
2011 server_host = QLineEdit()
2012 server_host.setFixedWidth(200)
2013 server_port = QLineEdit()
2014 server_port.setFixedWidth(60)
2016 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2017 protocol_letters = 'thsg'
2018 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2019 server_protocol.addItems(protocol_names)
2021 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2022 grid.addWidget(server_protocol, 0, 1)
2023 grid.addWidget(server_host, 0, 2)
2024 grid.addWidget(server_port, 0, 3)
2026 def change_protocol(p):
2027 protocol = protocol_letters[p]
2028 host = unicode(server_host.text())
2029 pp = plist.get(host,DEFAULT_PORTS)
2030 if protocol not in pp.keys():
2031 protocol = pp.keys()[0]
2033 server_host.setText( host )
2034 server_port.setText( port )
2036 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2038 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2039 servers_list_widget = QTreeWidget(parent)
2040 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2041 servers_list_widget.setMaximumHeight(150)
2042 servers_list_widget.setColumnWidth(0, 240)
2043 for _host in servers_list.keys():
2044 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2045 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2047 def change_server(host, protocol=None):
2048 pp = plist.get(host,DEFAULT_PORTS)
2050 port = pp.get(protocol)
2051 if not port: protocol = None
2054 if 't' in pp.keys():
2056 port = pp.get(protocol)
2058 protocol = pp.keys()[0]
2059 port = pp.get(protocol)
2061 server_host.setText( host )
2062 server_port.setText( port )
2063 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2065 if not plist: return
2066 for p in protocol_letters:
2067 i = protocol_letters.index(p)
2068 j = server_protocol.model().index(i,0)
2069 if p not in pp.keys():
2070 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2072 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2076 host, port, protocol = server.split(':')
2077 change_server(host,protocol)
2079 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2080 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2082 if not wallet.config.is_modifiable('server'):
2083 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2086 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2087 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2088 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2089 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2092 proxy_mode = QComboBox()
2093 proxy_host = QLineEdit()
2094 proxy_host.setFixedWidth(200)
2095 proxy_port = QLineEdit()
2096 proxy_port.setFixedWidth(60)
2097 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2099 def check_for_disable(index = False):
2100 if proxy_mode.currentText() != 'NONE':
2101 proxy_host.setEnabled(True)
2102 proxy_port.setEnabled(True)
2104 proxy_host.setEnabled(False)
2105 proxy_port.setEnabled(False)
2108 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2110 if not wallet.config.is_modifiable('proxy'):
2111 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2113 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2114 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2115 proxy_host.setText(proxy_config.get("host"))
2116 proxy_port.setText(proxy_config.get("port"))
2118 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2119 grid.addWidget(proxy_mode, 2, 1)
2120 grid.addWidget(proxy_host, 2, 2)
2121 grid.addWidget(proxy_port, 2, 3)
2124 vbox.addLayout(ok_cancel_buttons(d))
2127 if not d.exec_(): return
2129 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2130 if proxy_mode.currentText() != 'NONE':
2131 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2135 wallet.config.set_key("proxy", proxy, True)
2136 wallet.config.set_key("server", server, True)
2137 interface.set_server(server, proxy)
2138 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2141 def closeEvent(self, event):
2143 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2144 self.save_column_widths()
2145 self.config.set_key("column-widths", self.column_widths, True)
2151 def __init__(self, wallet, config, app=None):
2152 self.wallet = wallet
2153 self.config = config
2155 self.app = QApplication(sys.argv)
2158 def restore_or_create(self):
2159 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2160 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2161 if r==2: return None
2162 return 'restore' if r==1 else 'create'
2164 def seed_dialog(self):
2165 return ElectrumWindow.seed_dialog( self.wallet )
2167 def network_dialog(self):
2168 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2171 def show_seed(self):
2172 ElectrumWindow.show_seed_dialog(self.wallet)
2175 def password_dialog(self):
2176 ElectrumWindow.change_password_dialog(self.wallet)
2179 def restore_wallet(self):
2180 wallet = self.wallet
2181 # wait until we are connected, because the user might have selected another server
2182 if not wallet.interface.is_connected:
2183 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
2184 waiting_dialog(waiting)
2186 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
2187 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
2189 wallet.set_up_to_date(False)
2190 wallet.interface.poke('synchronizer')
2191 waiting_dialog(waiting)
2192 if wallet.is_found():
2193 print_error( "Recovery successful" )
2195 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2202 w = ElectrumWindow(self.wallet, self.config)
2203 if url: w.set_url(url)