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, threading
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
28 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
30 from PyQt4.QtGui import *
31 from PyQt4.QtCore import *
32 import PyQt4.QtCore as QtCore
34 from electrum.bitcoin import MIN_RELAY_TX_FEE
39 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41 from electrum.wallet import format_satoshis
42 from electrum.bitcoin import Transaction, is_valid
43 from electrum import mnemonic
44 from electrum import util, bitcoin, commands
46 import bmp, pyqrnative
49 from amountedit import AmountEdit
50 from network_dialog import NetworkDialog
51 from qrcodewidget import QRCodeWidget
53 from decimal import Decimal
61 if platform.system() == 'Windows':
62 MONOSPACE_FONT = 'Lucida Console'
63 elif platform.system() == 'Darwin':
64 MONOSPACE_FONT = 'Monaco'
66 MONOSPACE_FONT = 'monospace'
68 from electrum import ELECTRUM_VERSION
73 class UpdateLabel(QLabel):
74 def __init__(self, config, parent=None):
75 QLabel.__init__(self, parent)
76 self.new_version = False
79 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
80 con.request("GET", "/version")
81 res = con.getresponse()
82 except socket.error as msg:
83 print_error("Could not retrieve version information")
87 self.latest_version = res.read()
88 self.latest_version = self.latest_version.replace("\n","")
89 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
91 self.current_version = ELECTRUM_VERSION
92 if(self.compare_versions(self.latest_version, self.current_version) == 1):
93 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
94 if(self.compare_versions(self.latest_version, latest_seen) == 1):
95 self.new_version = True
96 self.setText(_("New version available") + ": " + self.latest_version)
99 def compare_versions(self, version1, version2):
101 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
102 return cmp(normalize(version1), normalize(version2))
104 def ignore_this_version(self):
106 self.config.set_key("last_seen_version", self.latest_version, True)
107 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
110 def ignore_all_version(self):
112 self.config.set_key("last_seen_version", "9.9.9", True)
113 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
116 def open_website(self):
117 webbrowser.open("http://electrum.org/download.html")
120 def mouseReleaseEvent(self, event):
121 dialog = QDialog(self)
122 dialog.setWindowTitle(_('Electrum update'))
125 main_layout = QGridLayout()
126 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
128 ignore_version = QPushButton(_("Ignore this version"))
129 ignore_version.clicked.connect(self.ignore_this_version)
131 ignore_all_versions = QPushButton(_("Ignore all versions"))
132 ignore_all_versions.clicked.connect(self.ignore_all_version)
134 open_website = QPushButton(_("Goto download page"))
135 open_website.clicked.connect(self.open_website)
137 main_layout.addWidget(ignore_version, 1, 0)
138 main_layout.addWidget(ignore_all_versions, 1, 1)
139 main_layout.addWidget(open_website, 1, 2)
141 dialog.setLayout(main_layout)
145 if not dialog.exec_(): return
149 class Timer(QtCore.QThread):
152 self.emit(QtCore.SIGNAL('timersignal'))
155 class HelpButton(QPushButton):
156 def __init__(self, text):
157 QPushButton.__init__(self, '?')
158 self.setFocusPolicy(Qt.NoFocus)
159 self.setFixedWidth(20)
160 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
163 class EnterButton(QPushButton):
164 def __init__(self, text, func):
165 QPushButton.__init__(self, text)
167 self.clicked.connect(func)
169 def keyPressEvent(self, e):
170 if e.key() == QtCore.Qt.Key_Return:
173 class MyTreeWidget(QTreeWidget):
174 def __init__(self, parent):
175 QTreeWidget.__init__(self, parent)
178 for i in range(0,self.viewport().height()/5):
179 if self.itemAt(QPoint(0,i*5)) == item:
183 for j in range(0,30):
184 if self.itemAt(QPoint(0,i*5 + j)) != item:
186 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
188 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
193 class StatusBarButton(QPushButton):
194 def __init__(self, icon, tooltip, func):
195 QPushButton.__init__(self, icon, '')
196 self.setToolTip(tooltip)
198 self.setMaximumWidth(25)
199 self.clicked.connect(func)
202 def keyPressEvent(self, e):
203 if e.key() == QtCore.Qt.Key_Return:
210 def waiting_dialog(f):
216 w.setWindowTitle('Electrum')
226 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
235 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
237 class ElectrumWindow(QMainWindow):
239 def __init__(self, wallet, config):
240 QMainWindow.__init__(self)
244 self.current_account = self.config.get("current_account", None)
247 self.create_status_bar()
249 self.need_update = threading.Event()
250 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
251 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
252 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
253 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
255 self.expert_mode = config.get('classic_expert_mode', False)
256 self.decimal_point = config.get('decimal_point', 8)
258 set_language(config.get('language'))
260 self.funds_error = False
261 self.completions = QStringListModel()
263 self.tabs = tabs = QTabWidget(self)
264 self.column_widths = self.config.get("column_widths", default_column_widths )
265 tabs.addTab(self.create_history_tab(), _('History') )
266 tabs.addTab(self.create_send_tab(), _('Send') )
267 tabs.addTab(self.create_receive_tab(), _('Receive') )
268 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
269 tabs.addTab(self.create_console_tab(), _('Console') )
270 tabs.setMinimumSize(600, 400)
271 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
272 self.setCentralWidget(tabs)
274 g = self.config.get("winpos-qt",[100, 100, 840, 400])
275 self.setGeometry(g[0], g[1], g[2], g[3])
276 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
277 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
278 self.setWindowTitle( title )
280 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
281 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
282 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
283 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
285 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
286 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
287 self.history_list.setFocus(True)
289 self.exchanger = exchange_rate.Exchanger(self)
290 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
292 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
293 if platform.system() == 'Windows':
294 n = 3 if self.wallet.seed else 2
295 tabs.setCurrentIndex (n)
296 tabs.setCurrentIndex (0)
298 # set initial message
299 self.console.showMessage(self.wallet.interface.banner)
301 # plugins that need to change the GUI do it here
302 self.run_hook('init_gui')
306 def init_plugins(self):
307 import imp, pkgutil, __builtin__
308 if __builtin__.use_local_modules:
309 fp, pathname, description = imp.find_module('plugins')
310 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
311 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
312 imp.load_module('electrum_plugins', fp, pathname, description)
313 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
315 import electrum_plugins
316 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
317 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
322 self.plugins.append( p.Plugin(self) )
324 print_msg("Error:cannot initialize plugin",p)
325 traceback.print_exc(file=sys.stdout)
328 def run_hook(self, name, *args):
329 for p in self.plugins:
330 if not p.is_enabled():
339 print_error("Plugin error")
340 traceback.print_exc(file=sys.stdout)
345 def set_label(self, name, text = None):
347 old_text = self.wallet.labels.get(name)
350 self.wallet.labels[name] = text
354 self.wallet.labels.pop(name)
356 self.run_hook('set_label', name, text, changed)
360 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
361 def getOpenFileName(self, title, filter = None):
362 directory = self.config.get('io_dir', os.path.expanduser('~'))
363 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
364 if fileName and directory != os.path.dirname(fileName):
365 self.config.set_key('io_dir', os.path.dirname(fileName), True)
368 def getSaveFileName(self, title, filename, filter = None):
369 directory = self.config.get('io_dir', os.path.expanduser('~'))
370 path = os.path.join( directory, filename )
371 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
372 if fileName and directory != os.path.dirname(fileName):
373 self.config.set_key('io_dir', os.path.dirname(fileName), True)
379 QMainWindow.close(self)
380 self.run_hook('close_main_window')
382 def connect_slots(self, sender):
383 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
384 self.previous_payto_e=''
386 def timer_actions(self):
387 if self.need_update.is_set():
389 self.need_update.clear()
390 self.run_hook('timer_actions')
392 def format_amount(self, x, is_diff=False):
393 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
395 def read_amount(self, x):
396 if x in['.', '']: return None
397 p = pow(10, self.decimal_point)
398 return int( p * Decimal(x) )
401 assert self.decimal_point in [5,8]
402 return "BTC" if self.decimal_point == 8 else "mBTC"
404 def update_status(self):
405 if self.wallet.interface and self.wallet.interface.is_connected:
406 if not self.wallet.up_to_date:
407 text = _("Synchronizing...")
408 icon = QIcon(":icons/status_waiting.png")
410 c, u = self.wallet.get_account_balance(self.current_account)
411 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
412 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
413 text += self.create_quote_text(Decimal(c+u)/100000000)
414 icon = QIcon(":icons/status_connected.png")
416 text = _("Not connected")
417 icon = QIcon(":icons/status_disconnected.png")
419 self.status_text = text
420 self.statusBar().showMessage(text)
421 self.status_button.setIcon( icon )
423 def update_wallet(self):
425 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
426 self.update_history_tab()
427 self.update_receive_tab()
428 self.update_contacts_tab()
429 self.update_completions()
432 def create_quote_text(self, btc_balance):
433 quote_currency = self.config.get("currency", "None")
434 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
435 if quote_balance is None:
438 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
441 def create_history_tab(self):
442 self.history_list = l = MyTreeWidget(self)
444 for i,width in enumerate(self.column_widths['history']):
445 l.setColumnWidth(i, width)
446 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
447 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
448 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
450 l.setContextMenuPolicy(Qt.CustomContextMenu)
451 l.customContextMenuRequested.connect(self.create_history_menu)
455 def create_history_menu(self, position):
456 self.history_list.selectedIndexes()
457 item = self.history_list.currentItem()
459 tx_hash = str(item.data(0, Qt.UserRole).toString())
460 if not tx_hash: return
462 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
463 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
464 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
465 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
468 def show_tx_details(self, tx):
469 dialog = QDialog(self)
471 dialog.setWindowTitle(_("Transaction Details"))
473 dialog.setLayout(vbox)
474 dialog.setMinimumSize(600,300)
477 if tx_hash in self.wallet.transactions.keys():
478 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
479 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
481 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
487 vbox.addWidget(QLabel("Transaction ID:"))
488 e = QLineEdit(tx_hash)
492 vbox.addWidget(QLabel("Date: %s"%time_str))
493 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
496 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
497 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
499 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
500 vbox.addWidget(QLabel("Transaction fee: unknown"))
502 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
504 vbox.addWidget( self.generate_transaction_information_widget(tx) )
506 ok_button = QPushButton(_("Close"))
507 ok_button.setDefault(True)
508 ok_button.clicked.connect(dialog.accept)
512 hbox.addWidget(ok_button)
516 def tx_label_clicked(self, item, column):
517 if column==2 and item.isSelected():
519 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
520 self.history_list.editItem( item, column )
521 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
524 def tx_label_changed(self, item, column):
528 tx_hash = str(item.data(0, Qt.UserRole).toString())
529 tx = self.wallet.transactions.get(tx_hash)
530 text = unicode( item.text(2) )
531 self.set_label(tx_hash, text)
533 item.setForeground(2, QBrush(QColor('black')))
535 text = self.wallet.get_default_label(tx_hash)
536 item.setText(2, text)
537 item.setForeground(2, QBrush(QColor('gray')))
541 def edit_label(self, is_recv):
542 l = self.receive_list if is_recv else self.contacts_list
543 item = l.currentItem()
544 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
545 l.editItem( item, 1 )
546 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
550 def address_label_clicked(self, item, column, l, column_addr, column_label):
551 if column == column_label and item.isSelected():
552 is_editable = item.data(0, 32).toBool()
555 addr = unicode( item.text(column_addr) )
556 label = unicode( item.text(column_label) )
557 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 l.editItem( item, column )
559 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
562 def address_label_changed(self, item, column, l, column_addr, column_label):
563 if column == column_label:
564 addr = unicode( item.text(column_addr) )
565 text = unicode( item.text(column_label) )
566 is_editable = item.data(0, 32).toBool()
570 changed = self.set_label(addr, text)
572 self.update_history_tab()
573 self.update_completions()
575 self.current_item_changed(item)
577 self.run_hook('item_changed', item, column)
580 def current_item_changed(self, a):
581 self.run_hook('current_item_changed', a)
585 def update_history_tab(self):
587 self.history_list.clear()
588 for item in self.wallet.get_tx_history(self.current_account):
589 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
592 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
597 time_str = 'unverified'
598 icon = QIcon(":icons/unconfirmed.png")
601 icon = QIcon(":icons/unconfirmed.png")
603 icon = QIcon(":icons/clock%d.png"%conf)
605 icon = QIcon(":icons/confirmed.png")
607 if value is not None:
608 v_str = self.format_amount(value, True)
612 balance_str = self.format_amount(balance)
615 label, is_default_label = self.wallet.get_label(tx_hash)
617 label = _('Pruned transaction outputs')
618 is_default_label = False
620 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
621 item.setFont(2, QFont(MONOSPACE_FONT))
622 item.setFont(3, QFont(MONOSPACE_FONT))
623 item.setFont(4, QFont(MONOSPACE_FONT))
625 item.setForeground(3, QBrush(QColor("#BC1E1E")))
627 item.setData(0, Qt.UserRole, tx_hash)
628 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
630 item.setForeground(2, QBrush(QColor('grey')))
632 item.setIcon(0, icon)
633 self.history_list.insertTopLevelItem(0,item)
636 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
639 def create_send_tab(self):
644 grid.setColumnMinimumWidth(3,300)
645 grid.setColumnStretch(5,1)
648 self.payto_e = QLineEdit()
649 grid.addWidget(QLabel(_('Pay to')), 1, 0)
650 grid.addWidget(self.payto_e, 1, 1, 1, 3)
652 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)
654 completer = QCompleter()
655 completer.setCaseSensitivity(False)
656 self.payto_e.setCompleter(completer)
657 completer.setModel(self.completions)
659 self.message_e = QLineEdit()
660 grid.addWidget(QLabel(_('Description')), 2, 0)
661 grid.addWidget(self.message_e, 2, 1, 1, 3)
662 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)
664 self.amount_e = AmountEdit(self.base_unit)
665 grid.addWidget(QLabel(_('Amount')), 3, 0)
666 grid.addWidget(self.amount_e, 3, 1, 1, 2)
667 grid.addWidget(HelpButton(
668 _('Amount to be sent.') + '\n\n' \
669 + _('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.') \
670 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
672 self.fee_e = AmountEdit(self.base_unit)
673 grid.addWidget(QLabel(_('Fee')), 4, 0)
674 grid.addWidget(self.fee_e, 4, 1, 1, 2)
675 grid.addWidget(HelpButton(
676 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
677 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
678 + _('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)
681 b = EnterButton(_("Send"), self.do_send)
683 b = EnterButton(_("Create unsigned transaction"), self.do_send)
684 grid.addWidget(b, 6, 1)
686 b = EnterButton(_("Clear"),self.do_clear)
687 grid.addWidget(b, 6, 2)
689 self.payto_sig = QLabel('')
690 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
692 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
693 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
702 def entry_changed( is_fee ):
703 self.funds_error = False
705 if self.amount_e.is_shortcut:
706 self.amount_e.is_shortcut = False
707 c, u = self.wallet.get_account_balance(self.current_account)
708 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
709 fee = self.wallet.estimated_fee(inputs)
711 self.amount_e.setText( self.format_amount(amount) )
712 self.fee_e.setText( self.format_amount( fee ) )
715 amount = self.read_amount(str(self.amount_e.text()))
716 fee = self.read_amount(str(self.fee_e.text()))
718 if not is_fee: fee = None
721 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
723 self.fee_e.setText( self.format_amount( fee ) )
726 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
727 text = self.status_text
730 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
731 self.funds_error = True
732 text = _( "Not enough funds" )
733 c, u = self.wallet.get_frozen_balance()
734 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
736 self.statusBar().showMessage(text)
737 self.amount_e.setPalette(palette)
738 self.fee_e.setPalette(palette)
740 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
741 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
743 self.run_hook('create_send_tab', grid)
747 def update_completions(self):
749 for addr,label in self.wallet.labels.items():
750 if addr in self.wallet.addressbook:
751 l.append( label + ' <' + addr + '>')
753 self.run_hook('update_completions', l)
754 self.completions.setStringList(l)
758 return lambda s, *args: s.do_protect(func, args)
762 def do_send(self, password):
764 label = unicode( self.message_e.text() )
765 r = unicode( self.payto_e.text() )
768 # label or alias, with address in brackets
769 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
770 to_address = m.group(2) if m else r
772 if not is_valid(to_address):
773 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
777 amount = self.read_amount(unicode( self.amount_e.text()))
779 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
782 fee = self.read_amount(unicode( self.fee_e.text()))
784 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
788 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
789 except BaseException, e:
790 self.show_message(str(e))
793 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
794 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
797 self.run_hook('send_tx', tx)
800 self.set_label(tx.hash(), label)
803 h = self.wallet.send_tx(tx)
804 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
805 status, msg = self.wallet.receive_tx( h )
807 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
809 self.update_contacts_tab()
811 QMessageBox.warning(self, _('Error'), msg, _('OK'))
813 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
815 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
816 with open(fileName,'w') as f:
817 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
818 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
820 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
825 def set_url(self, url):
826 address, amount, label, message, signature, identity, url = util.parse_url(url)
827 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
829 if label and self.wallet.labels.get(address) != label:
830 if self.question('Give label "%s" to address %s ?'%(label,address)):
831 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
832 self.wallet.addressbook.append(address)
833 self.set_label(address, label)
835 self.run_hook('set_url', url, self.show_message, self.question)
837 self.tabs.setCurrentIndex(1)
838 label = self.wallet.labels.get(address)
839 m_addr = label + ' <'+ address +'>' if label else address
840 self.payto_e.setText(m_addr)
842 self.message_e.setText(message)
843 self.amount_e.setText(amount)
845 self.set_frozen(self.payto_e,True)
846 self.set_frozen(self.amount_e,True)
847 self.set_frozen(self.message_e,True)
848 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
850 self.payto_sig.setVisible(False)
853 self.payto_sig.setVisible(False)
854 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
856 self.set_frozen(e,False)
859 def set_frozen(self,entry,frozen):
861 entry.setReadOnly(True)
862 entry.setFrame(False)
864 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
865 entry.setPalette(palette)
867 entry.setReadOnly(False)
870 palette.setColor(entry.backgroundRole(), QColor('white'))
871 entry.setPalette(palette)
874 def toggle_freeze(self,addr):
876 if addr in self.wallet.frozen_addresses:
877 self.wallet.unfreeze(addr)
879 self.wallet.freeze(addr)
880 self.update_receive_tab()
882 def toggle_priority(self,addr):
884 if addr in self.wallet.prioritized_addresses:
885 self.wallet.unprioritize(addr)
887 self.wallet.prioritize(addr)
888 self.update_receive_tab()
891 def create_list_tab(self, headers):
892 "generic tab creation method"
893 l = MyTreeWidget(self)
894 l.setColumnCount( len(headers) )
895 l.setHeaderLabels( headers )
905 vbox.addWidget(buttons)
910 buttons.setLayout(hbox)
915 def create_receive_tab(self):
916 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
917 l.setContextMenuPolicy(Qt.CustomContextMenu)
918 l.customContextMenuRequested.connect(self.create_receive_menu)
919 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
920 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
921 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
922 self.receive_list = l
923 self.receive_buttons_hbox = hbox
928 def receive_tab_set_mode(self, i):
929 self.save_column_widths()
930 self.expert_mode = (i == 1)
931 self.config.set_key('classic_expert_mode', self.expert_mode, True)
933 self.update_receive_tab()
936 def save_column_widths(self):
937 if not self.expert_mode:
938 widths = [ self.receive_list.columnWidth(0) ]
941 for i in range(self.receive_list.columnCount() -1):
942 widths.append(self.receive_list.columnWidth(i))
943 self.column_widths["receive"][self.expert_mode] = widths
945 self.column_widths["history"] = []
946 for i in range(self.history_list.columnCount() - 1):
947 self.column_widths["history"].append(self.history_list.columnWidth(i))
949 self.column_widths["contacts"] = []
950 for i in range(self.contacts_list.columnCount() - 1):
951 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
953 self.config.set_key("column_widths", self.column_widths, True)
956 def create_contacts_tab(self):
957 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
958 l.setContextMenuPolicy(Qt.CustomContextMenu)
959 l.customContextMenuRequested.connect(self.create_contact_menu)
960 for i,width in enumerate(self.column_widths['contacts']):
961 l.setColumnWidth(i, width)
963 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
964 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
965 self.contacts_list = l
966 self.contacts_buttons_hbox = hbox
967 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
972 def delete_imported_key(self, addr):
973 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
974 self.wallet.delete_imported_key(addr)
975 self.update_receive_tab()
976 self.update_history_tab()
979 def create_receive_menu(self, position):
980 # fixme: this function apparently has a side effect.
981 # if it is not called the menu pops up several times
982 #self.receive_list.selectedIndexes()
984 item = self.receive_list.itemAt(position)
986 addr = unicode(item.text(0))
987 if not is_valid(addr):
988 item.setExpanded(not item.isExpanded())
991 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
992 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
993 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
994 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
995 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
996 if addr in self.wallet.imported_keys:
997 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1000 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1001 menu.addAction(t, lambda: self.toggle_freeze(addr))
1002 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1003 menu.addAction(t, lambda: self.toggle_priority(addr))
1005 self.run_hook('receive_menu', menu)
1006 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1009 def payto(self, addr):
1011 label = self.wallet.labels.get(addr)
1012 m_addr = label + ' <' + addr + '>' if label else addr
1013 self.tabs.setCurrentIndex(1)
1014 self.payto_e.setText(m_addr)
1015 self.amount_e.setFocus()
1018 def delete_contact(self, x):
1019 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1020 if x in self.wallet.addressbook:
1021 self.wallet.addressbook.remove(x)
1022 self.set_label(x, None)
1023 self.update_history_tab()
1024 self.update_contacts_tab()
1025 self.update_completions()
1028 def create_contact_menu(self, position):
1029 item = self.contacts_list.itemAt(position)
1031 addr = unicode(item.text(0))
1032 label = unicode(item.text(1))
1033 is_editable = item.data(0,32).toBool()
1034 payto_addr = item.data(0,33).toString()
1036 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1037 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1038 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1040 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1041 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1043 self.run_hook('create_contact_menu', menu, item)
1044 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1047 def update_receive_item(self, item):
1048 item.setFont(0, QFont(MONOSPACE_FONT))
1049 address = str(item.data(0,0).toString())
1050 label = self.wallet.labels.get(address,'')
1051 item.setData(1,0,label)
1052 item.setData(0,32, True) # is editable
1054 self.run_hook('update_receive_item', address, item)
1056 c, u = self.wallet.get_addr_balance(address)
1057 balance = self.format_amount(c + u)
1058 item.setData(2,0,balance)
1060 if self.expert_mode:
1061 if address in self.wallet.frozen_addresses:
1062 item.setBackgroundColor(0, QColor('lightblue'))
1063 elif address in self.wallet.prioritized_addresses:
1064 item.setBackgroundColor(0, QColor('lightgreen'))
1067 def update_receive_tab(self):
1068 l = self.receive_list
1071 l.setColumnHidden(2, not self.expert_mode)
1072 l.setColumnHidden(3, not self.expert_mode)
1073 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1074 l.setColumnWidth(i, width)
1076 if self.current_account is None:
1077 account_items = self.wallet.accounts.items()
1078 elif self.current_account != -1:
1079 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1083 for k, account in account_items:
1084 name = account.get('name',str(k))
1085 c,u = self.wallet.get_account_balance(k)
1086 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1087 l.addTopLevelItem(account_item)
1088 account_item.setExpanded(True)
1090 for is_change in ([0,1] if self.expert_mode else [0]):
1091 if self.expert_mode:
1092 name = "Receiving" if not is_change else "Change"
1093 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1094 account_item.addChild(seq_item)
1095 if not is_change: seq_item.setExpanded(True)
1097 seq_item = account_item
1101 for address in account[is_change]:
1102 h = self.wallet.history.get(address,[])
1106 if gap > self.wallet.gap_limit:
1111 num_tx = '*' if h == ['*'] else "%d"%len(h)
1112 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1113 self.update_receive_item(item)
1115 item.setBackgroundColor(1, QColor('red'))
1116 seq_item.addChild(item)
1119 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1120 c,u = self.wallet.get_imported_balance()
1121 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1122 l.addTopLevelItem(account_item)
1123 account_item.setExpanded(True)
1124 for address in self.wallet.imported_keys.keys():
1125 item = QTreeWidgetItem( [ address, '', '', ''] )
1126 self.update_receive_item(item)
1127 account_item.addChild(item)
1130 # we use column 1 because column 0 may be hidden
1131 l.setCurrentItem(l.topLevelItem(0),1)
1134 def update_contacts_tab(self):
1136 l = self.contacts_list
1139 for address in self.wallet.addressbook:
1140 label = self.wallet.labels.get(address,'')
1141 n = self.wallet.get_num_tx(address)
1142 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1143 item.setFont(0, QFont(MONOSPACE_FONT))
1144 # 32 = label can be edited (bool)
1145 item.setData(0,32, True)
1147 item.setData(0,33, address)
1148 l.addTopLevelItem(item)
1150 self.run_hook('update_contacts_tab', l)
1151 l.setCurrentItem(l.topLevelItem(0))
1155 def create_console_tab(self):
1156 from qt_console import Console
1157 self.console = console = Console()
1158 self.console.history = self.config.get("console-history",[])
1159 self.console.history_index = len(self.console.history)
1161 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1162 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1164 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1166 def mkfunc(f, method):
1167 return lambda *args: apply( f, (method, args, self.password_dialog ))
1169 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1170 methods[m] = mkfunc(c._run, m)
1172 console.updateNamespace(methods)
1175 def change_account(self,s):
1176 if s == _("All accounts"):
1177 self.current_account = None
1179 accounts = self.wallet.get_accounts()
1180 for k, v in accounts.items():
1182 self.current_account = k
1183 self.update_history_tab()
1184 self.update_status()
1185 self.update_receive_tab()
1187 def create_status_bar(self):
1188 self.status_text = ""
1190 sb.setFixedHeight(35)
1191 qtVersion = qVersion()
1193 update_notification = UpdateLabel(self.config)
1194 if(update_notification.new_version):
1195 sb.addPermanentWidget(update_notification)
1197 accounts = self.wallet.get_accounts()
1198 if len(accounts) > 1:
1199 from_combo = QComboBox()
1200 from_combo.addItems([_("All accounts")] + accounts.values())
1201 from_combo.setCurrentIndex(0)
1202 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1203 sb.addPermanentWidget(from_combo)
1205 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1206 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1207 if self.wallet.seed:
1208 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1209 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1210 sb.addPermanentWidget( self.password_button )
1211 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1212 if self.wallet.seed:
1213 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1214 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1215 sb.addPermanentWidget( self.status_button )
1217 self.run_hook('create_status_bar', (sb,))
1219 self.setStatusBar(sb)
1223 self.config.set_key('gui', 'lite', True)
1226 self.lite.mini.show()
1228 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1229 self.lite.main(None)
1231 def new_contact_dialog(self):
1232 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1233 address = unicode(text)
1235 if is_valid(address):
1236 self.wallet.addressbook.append(address)
1238 self.update_contacts_tab()
1239 self.update_history_tab()
1240 self.update_completions()
1242 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1244 def show_master_public_key(self):
1245 dialog = QDialog(self)
1247 dialog.setWindowTitle(_("Master Public Key"))
1249 main_text = QTextEdit()
1250 main_text.setText(self.wallet.get_master_public_key())
1251 main_text.setReadOnly(True)
1252 main_text.setMaximumHeight(170)
1253 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1255 ok_button = QPushButton(_("OK"))
1256 ok_button.setDefault(True)
1257 ok_button.clicked.connect(dialog.accept)
1259 main_layout = QGridLayout()
1260 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1262 main_layout.addWidget(main_text, 1, 0)
1263 main_layout.addWidget(qrw, 1, 1 )
1265 vbox = QVBoxLayout()
1266 vbox.addLayout(main_layout)
1267 hbox = QHBoxLayout()
1269 hbox.addWidget(ok_button)
1270 vbox.addLayout(hbox)
1272 dialog.setLayout(vbox)
1277 def show_seed_dialog(self, password):
1278 if not self.wallet.seed:
1279 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1282 seed = self.wallet.decode_seed(password)
1284 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1286 self.show_seed(seed, self.wallet.imported_keys, self)
1290 def show_seed(self, seed, imported_keys, parent=None):
1291 dialog = QDialog(parent)
1293 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1295 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1297 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1299 seed_text = QTextEdit(brainwallet)
1300 seed_text.setReadOnly(True)
1301 seed_text.setMaximumHeight(130)
1303 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1304 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1305 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1306 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1308 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1309 label2 = QLabel(msg2)
1310 label2.setWordWrap(True)
1313 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1314 logo.setMaximumWidth(60)
1316 qrw = QRCodeWidget(seed)
1318 ok_button = QPushButton(_("OK"))
1319 ok_button.setDefault(True)
1320 ok_button.clicked.connect(dialog.accept)
1322 grid = QGridLayout()
1323 #main_layout.addWidget(logo, 0, 0)
1325 grid.addWidget(logo, 0, 0)
1326 grid.addWidget(label1, 0, 1)
1328 grid.addWidget(seed_text, 1, 0, 1, 2)
1330 grid.addWidget(qrw, 0, 2, 2, 1)
1332 vbox = QVBoxLayout()
1333 vbox.addLayout(grid)
1334 vbox.addWidget(label2)
1336 hbox = QHBoxLayout()
1338 hbox.addWidget(ok_button)
1339 vbox.addLayout(hbox)
1341 dialog.setLayout(vbox)
1344 def show_qrcode(self, data, title = "QR code"):
1348 d.setWindowTitle(title)
1349 d.setMinimumSize(270, 300)
1350 vbox = QVBoxLayout()
1351 qrw = QRCodeWidget(data)
1352 vbox.addWidget(qrw, 1)
1353 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1354 hbox = QHBoxLayout()
1358 filename = "qrcode.bmp"
1359 bmp.save_qrcode(qrw.qr, filename)
1360 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1362 b = QPushButton(_("Save"))
1364 b.clicked.connect(print_qr)
1366 b = QPushButton(_("Close"))
1368 b.clicked.connect(d.accept)
1371 vbox.addLayout(hbox)
1376 def do_protect(self, func, args):
1377 if self.wallet.use_encryption:
1378 password = self.password_dialog()
1384 if args != (False,):
1385 args = (self,) + args + (password,)
1387 args = (self,password)
1392 def show_private_key(self, address, password):
1393 if not address: return
1395 pk = self.wallet.get_private_key(address, password)
1396 except BaseException, e:
1397 self.show_message(str(e))
1399 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1403 def do_sign(self, address, message, signature, password):
1405 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1406 signature.setText(sig)
1407 except BaseException, e:
1408 self.show_message(str(e))
1410 def sign_message(self, address):
1411 if not address: return
1414 d.setWindowTitle(_('Sign Message'))
1415 d.setMinimumSize(410, 290)
1417 tab_widget = QTabWidget()
1419 layout = QGridLayout(tab)
1421 sign_address = QLineEdit()
1423 sign_address.setText(address)
1424 layout.addWidget(QLabel(_('Address')), 1, 0)
1425 layout.addWidget(sign_address, 1, 1)
1427 sign_message = QTextEdit()
1428 layout.addWidget(QLabel(_('Message')), 2, 0)
1429 layout.addWidget(sign_message, 2, 1)
1430 layout.setRowStretch(2,3)
1432 sign_signature = QTextEdit()
1433 layout.addWidget(QLabel(_('Signature')), 3, 0)
1434 layout.addWidget(sign_signature, 3, 1)
1435 layout.setRowStretch(3,1)
1438 hbox = QHBoxLayout()
1439 b = QPushButton(_("Sign"))
1441 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1442 b = QPushButton(_("Close"))
1443 b.clicked.connect(d.accept)
1445 layout.addLayout(hbox, 4, 1)
1446 tab_widget.addTab(tab, _("Sign"))
1450 layout = QGridLayout(tab)
1452 verify_address = QLineEdit()
1453 layout.addWidget(QLabel(_('Address')), 1, 0)
1454 layout.addWidget(verify_address, 1, 1)
1456 verify_message = QTextEdit()
1457 layout.addWidget(QLabel(_('Message')), 2, 0)
1458 layout.addWidget(verify_message, 2, 1)
1459 layout.setRowStretch(2,3)
1461 verify_signature = QTextEdit()
1462 layout.addWidget(QLabel(_('Signature')), 3, 0)
1463 layout.addWidget(verify_signature, 3, 1)
1464 layout.setRowStretch(3,1)
1467 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1468 self.show_message(_("Signature verified"))
1470 self.show_message(_("Error: wrong signature"))
1472 hbox = QHBoxLayout()
1473 b = QPushButton(_("Verify"))
1474 b.clicked.connect(do_verify)
1476 b = QPushButton(_("Close"))
1477 b.clicked.connect(d.accept)
1479 layout.addLayout(hbox, 4, 1)
1480 tab_widget.addTab(tab, _("Verify"))
1482 vbox = QVBoxLayout()
1483 vbox.addWidget(tab_widget)
1490 def question(self, msg):
1491 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1493 def show_message(self, msg):
1494 QMessageBox.information(self, _('Message'), msg, _('OK'))
1496 def password_dialog(self ):
1503 vbox = QVBoxLayout()
1504 msg = _('Please enter your password')
1505 vbox.addWidget(QLabel(msg))
1507 grid = QGridLayout()
1509 grid.addWidget(QLabel(_('Password')), 1, 0)
1510 grid.addWidget(pw, 1, 1)
1511 vbox.addLayout(grid)
1513 vbox.addLayout(ok_cancel_buttons(d))
1516 self.run_hook('password_dialog', pw, grid, 1)
1517 if not d.exec_(): return
1518 return unicode(pw.text())
1525 def change_password_dialog( wallet, parent=None ):
1528 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1536 new_pw = QLineEdit()
1537 new_pw.setEchoMode(2)
1538 conf_pw = QLineEdit()
1539 conf_pw.setEchoMode(2)
1541 vbox = QVBoxLayout()
1543 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1544 +_('To disable wallet encryption, enter an empty new password.')) \
1545 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1547 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1548 +_("Leave these fields empty if you want to disable encryption.")
1549 vbox.addWidget(QLabel(msg))
1551 grid = QGridLayout()
1554 if wallet.use_encryption:
1555 grid.addWidget(QLabel(_('Password')), 1, 0)
1556 grid.addWidget(pw, 1, 1)
1558 grid.addWidget(QLabel(_('New Password')), 2, 0)
1559 grid.addWidget(new_pw, 2, 1)
1561 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1562 grid.addWidget(conf_pw, 3, 1)
1563 vbox.addLayout(grid)
1565 vbox.addLayout(ok_cancel_buttons(d))
1568 if not d.exec_(): return
1570 password = unicode(pw.text()) if wallet.use_encryption else None
1571 new_password = unicode(new_pw.text())
1572 new_password2 = unicode(conf_pw.text())
1575 seed = wallet.decode_seed(password)
1577 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1580 if new_password != new_password2:
1581 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1582 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1585 wallet.update_password(seed, password, new_password)
1587 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1590 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1593 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1594 parent.password_button.setIcon( icon )
1598 def generate_transaction_information_widget(self, tx):
1599 tabs = QTabWidget(self)
1602 grid_ui = QGridLayout(tab1)
1603 grid_ui.setColumnStretch(0,1)
1604 tabs.addTab(tab1, _('Outputs') )
1606 tree_widget = MyTreeWidget(self)
1607 tree_widget.setColumnCount(2)
1608 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1609 tree_widget.setColumnWidth(0, 300)
1610 tree_widget.setColumnWidth(1, 50)
1612 for address, value in tx.outputs:
1613 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1614 tree_widget.addTopLevelItem(item)
1616 tree_widget.setMaximumHeight(100)
1618 grid_ui.addWidget(tree_widget)
1621 grid_ui = QGridLayout(tab2)
1622 grid_ui.setColumnStretch(0,1)
1623 tabs.addTab(tab2, _('Inputs') )
1625 tree_widget = MyTreeWidget(self)
1626 tree_widget.setColumnCount(2)
1627 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1629 for input_line in tx.inputs:
1630 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1631 tree_widget.addTopLevelItem(item)
1633 tree_widget.setMaximumHeight(100)
1635 grid_ui.addWidget(tree_widget)
1639 def tx_dict_from_text(self, txt):
1641 tx_dict = json.loads(str(txt))
1642 assert "hex" in tx_dict.keys()
1643 assert "complete" in tx_dict.keys()
1644 if not tx_dict["complete"]:
1645 assert "input_info" in tx_dict.keys()
1647 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1652 def read_tx_from_file(self):
1653 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1657 with open(fileName, "r") as f:
1658 file_content = f.read()
1659 except (ValueError, IOError, os.error), reason:
1660 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1662 return self.tx_dict_from_text(file_content)
1666 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1668 self.wallet.signrawtransaction(tx, input_info, [], password)
1670 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1672 with open(fileName, "w+") as f:
1673 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1674 self.show_message(_("Transaction saved successfully"))
1677 except BaseException, e:
1678 self.show_message(str(e))
1681 def send_raw_transaction(self, raw_tx, dialog = ""):
1682 result, result_message = self.wallet.sendtx( raw_tx )
1684 self.show_message("Transaction successfully sent: %s" % (result_message))
1688 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1690 def do_process_from_text(self):
1691 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1694 tx_dict = self.tx_dict_from_text(text)
1696 self.create_process_transaction_window(tx_dict)
1698 def do_process_from_file(self):
1699 tx_dict = self.read_tx_from_file()
1701 self.create_process_transaction_window(tx_dict)
1703 def create_process_transaction_window(self, tx_dict):
1704 tx = Transaction(tx_dict["hex"])
1706 dialog = QDialog(self)
1707 dialog.setMinimumWidth(500)
1708 dialog.setWindowTitle(_('Process raw transaction'))
1714 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1715 l.addWidget(QLabel(_("Actions")), 4,0)
1717 if tx_dict["complete"] == False:
1718 l.addWidget(QLabel(_("Unsigned")), 3,1)
1719 if self.wallet.seed :
1720 b = QPushButton("Sign transaction")
1721 input_info = json.loads(tx_dict["input_info"])
1722 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1723 l.addWidget(b, 4, 1)
1725 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1727 l.addWidget(QLabel(_("Signed")), 3,1)
1728 b = QPushButton("Broadcast transaction")
1729 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1732 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1733 cancelButton = QPushButton(_("Cancel"))
1734 cancelButton.clicked.connect(lambda: dialog.done(0))
1735 l.addWidget(cancelButton, 4,2)
1741 def do_export_privkeys(self, password):
1742 self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
1745 select_export = _('Select file to export your private keys to')
1746 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1748 with open(fileName, "w+") as csvfile:
1749 transaction = csv.writer(csvfile)
1750 transaction.writerow(["address", "private_key"])
1753 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1754 transaction.writerow(["%34s"%addr,pk])
1756 self.show_message(_("Private keys exported."))
1758 except (IOError, os.error), reason:
1759 export_error_label = _("Electrum was unable to produce a private key-export.")
1760 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1762 except BaseException, e:
1763 self.show_message(str(e))
1767 def do_import_labels(self):
1768 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1769 if not labelsFile: return
1771 f = open(labelsFile, 'r')
1774 for key, value in json.loads(data).items():
1775 self.wallet.labels[key] = value
1777 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1778 except (IOError, os.error), reason:
1779 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1782 def do_export_labels(self):
1783 labels = self.wallet.labels
1785 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1787 with open(fileName, 'w+') as f:
1788 json.dump(labels, f)
1789 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1790 except (IOError, os.error), reason:
1791 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1794 def do_export_history(self):
1795 from gui_lite import csv_transaction
1796 csv_transaction(self.wallet)
1800 def do_import_privkey(self, password):
1801 if not self.wallet.imported_keys:
1802 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1803 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1804 + _('Are you sure you understand what you are doing?'), 3, 4)
1807 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1810 text = str(text).split()
1815 addr = self.wallet.import_key(key, password)
1816 except BaseException as e:
1822 addrlist.append(addr)
1824 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1826 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1827 self.update_receive_tab()
1828 self.update_history_tab()
1831 def settings_dialog(self):
1833 d.setWindowTitle(_('Electrum Settings'))
1835 vbox = QVBoxLayout()
1837 tabs = QTabWidget(self)
1838 self.settings_tab = tabs
1839 vbox.addWidget(tabs)
1842 grid_ui = QGridLayout(tab1)
1843 grid_ui.setColumnStretch(0,1)
1844 tabs.addTab(tab1, _('Display') )
1846 nz_label = QLabel(_('Display zeros'))
1847 grid_ui.addWidget(nz_label, 0, 0)
1848 nz_e = AmountEdit(None,True)
1849 nz_e.setText("%d"% self.wallet.num_zeros)
1850 grid_ui.addWidget(nz_e, 0, 1)
1851 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1852 grid_ui.addWidget(HelpButton(msg), 0, 2)
1853 if not self.config.is_modifiable('num_zeros'):
1854 for w in [nz_e, nz_label]: w.setEnabled(False)
1856 lang_label=QLabel(_('Language') + ':')
1857 grid_ui.addWidget(lang_label, 1, 0)
1858 lang_combo = QComboBox()
1859 from i18n import languages
1860 lang_combo.addItems(languages.values())
1862 index = languages.keys().index(self.config.get("language",''))
1865 lang_combo.setCurrentIndex(index)
1866 grid_ui.addWidget(lang_combo, 1, 1)
1867 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1868 if not self.config.is_modifiable('language'):
1869 for w in [lang_combo, lang_label]: w.setEnabled(False)
1871 currencies = self.exchanger.get_currencies()
1872 currencies.insert(0, "None")
1874 cur_label=QLabel(_('Currency') + ':')
1875 grid_ui.addWidget(cur_label , 2, 0)
1876 cur_combo = QComboBox()
1877 cur_combo.addItems(currencies)
1879 index = currencies.index(self.config.get('currency', "None"))
1882 cur_combo.setCurrentIndex(index)
1883 grid_ui.addWidget(cur_combo, 2, 1)
1884 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1886 expert_cb = QCheckBox(_('Expert mode'))
1887 expert_cb.setChecked(self.expert_mode)
1888 grid_ui.addWidget(expert_cb, 3, 0)
1889 hh = _('In expert mode, your client will:') + '\n' \
1890 + _(' - Show change addresses in the Receive tab') + '\n' \
1891 + _(' - Display the balance of each address') + '\n' \
1892 + _(' - Add freeze/prioritize actions to addresses.')
1893 grid_ui.addWidget(HelpButton(hh), 3, 2)
1894 grid_ui.setRowStretch(4,1)
1898 grid_wallet = QGridLayout(tab2)
1899 grid_wallet.setColumnStretch(0,1)
1900 tabs.addTab(tab2, _('Wallet') )
1902 fee_label = QLabel(_('Transaction fee'))
1903 grid_wallet.addWidget(fee_label, 0, 0)
1904 fee_e = AmountEdit(self.base_unit)
1905 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1906 grid_wallet.addWidget(fee_e, 0, 2)
1907 msg = _('Fee per kilobyte of transaction.') + ' ' \
1908 + _('Recommended value') + ': ' + self.format_amount(20000)
1909 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1910 if not self.config.is_modifiable('fee_per_kb'):
1911 for w in [fee_e, fee_label]: w.setEnabled(False)
1913 usechange_cb = QCheckBox(_('Use change addresses'))
1914 usechange_cb.setChecked(self.wallet.use_change)
1915 grid_wallet.addWidget(usechange_cb, 1, 0)
1916 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1917 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1919 gap_label = QLabel(_('Gap limit'))
1920 grid_wallet.addWidget(gap_label, 2, 0)
1921 gap_e = AmountEdit(None,True)
1922 gap_e.setText("%d"% self.wallet.gap_limit)
1923 grid_wallet.addWidget(gap_e, 2, 2)
1924 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1925 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1926 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1927 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1928 + _('Warning') + ': ' \
1929 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1930 + _('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'
1931 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1932 if not self.config.is_modifiable('gap_limit'):
1933 for w in [gap_e, gap_label]: w.setEnabled(False)
1935 units = ['BTC', 'mBTC']
1936 unit_label = QLabel(_('Base unit'))
1937 grid_wallet.addWidget(unit_label, 3, 0)
1938 unit_combo = QComboBox()
1939 unit_combo.addItems(units)
1940 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1941 grid_wallet.addWidget(unit_combo, 3, 2)
1942 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1943 + '\n1BTC=1000mBTC.\n' \
1944 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1945 grid_wallet.setRowStretch(4,1)
1950 grid_io = QGridLayout(tab3)
1951 grid_io.setColumnStretch(0,1)
1952 tabs.addTab(tab3, _('Import/Export') )
1954 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1955 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1956 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1957 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1959 grid_io.addWidget(QLabel(_('History')), 2, 0)
1960 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1961 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1963 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1965 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1966 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1967 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1969 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1970 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1971 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1972 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1973 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1976 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1977 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1978 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1979 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1981 grid_io.setRowStretch(6,1)
1986 tab5 = QScrollArea()
1987 tab5.setEnabled(True)
1988 tab5.setWidgetResizable(True)
1990 grid_plugins = QGridLayout()
1991 grid_plugins.setColumnStretch(0,1)
1994 w.setLayout(grid_plugins)
1996 tab5.setMaximumSize(tab3.size()) # optional
1998 w.setMinimumHeight(len(self.plugins)*35)
2000 tabs.addTab(tab5, _('Plugins') )
2001 def mk_toggle(cb, p):
2002 return lambda: cb.setChecked(p.toggle())
2003 for i, p in enumerate(self.plugins):
2005 name, description = p.get_info()
2006 cb = QCheckBox(name)
2007 cb.setDisabled(not p.is_available())
2008 cb.setChecked(p.is_enabled())
2009 cb.clicked.connect(mk_toggle(cb,p))
2010 grid_plugins.addWidget(cb, i, 0)
2011 if p.requires_settings():
2012 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2013 grid_plugins.addWidget(HelpButton(description), i, 2)
2015 print_msg("Error: cannot display plugin", p)
2016 traceback.print_exc(file=sys.stdout)
2017 grid_plugins.setRowStretch(i+1,1)
2019 self.run_hook('create_settings_tab', tabs)
2021 vbox.addLayout(ok_cancel_buttons(d))
2025 if not d.exec_(): return
2027 fee = unicode(fee_e.text())
2029 fee = self.read_amount(fee)
2031 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2034 self.wallet.set_fee(fee)
2036 nz = unicode(nz_e.text())
2041 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2044 if self.wallet.num_zeros != nz:
2045 self.wallet.num_zeros = nz
2046 self.config.set_key('num_zeros', nz, True)
2047 self.update_history_tab()
2048 self.update_receive_tab()
2050 usechange_result = usechange_cb.isChecked()
2051 if self.wallet.use_change != usechange_result:
2052 self.wallet.use_change = usechange_result
2053 self.config.set_key('use_change', self.wallet.use_change, True)
2055 unit_result = units[unit_combo.currentIndex()]
2056 if self.base_unit() != unit_result:
2057 self.decimal_point = 8 if unit_result == 'BTC' else 5
2058 self.config.set_key('decimal_point', self.decimal_point, True)
2059 self.update_history_tab()
2060 self.update_status()
2063 n = int(gap_e.text())
2065 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2068 if self.wallet.gap_limit != n:
2069 r = self.wallet.change_gap_limit(n)
2071 self.update_receive_tab()
2072 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2074 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2076 need_restart = False
2078 lang_request = languages.keys()[lang_combo.currentIndex()]
2079 if lang_request != self.config.get('language'):
2080 self.config.set_key("language", lang_request, True)
2083 cur_request = str(currencies[cur_combo.currentIndex()])
2084 if cur_request != self.config.get('currency', "None"):
2085 self.config.set_key('currency', cur_request, True)
2086 self.update_wallet()
2088 self.run_hook('close_settings_dialog')
2091 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2093 self.receive_tab_set_mode(expert_cb.isChecked())
2095 def run_network_dialog(self):
2096 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2098 def closeEvent(self, event):
2100 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2101 self.save_column_widths()
2102 self.config.set_key("console-history",self.console.history[-50:])
2111 def __init__(self, wallet, config, app=None):
2112 self.wallet = wallet
2113 self.config = config
2115 self.app = QApplication(sys.argv)
2118 def restore_or_create(self):
2119 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2120 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2121 if r==2: return None
2122 return 'restore' if r==1 else 'create'
2125 def verify_seed(self):
2126 r = self.seed_dialog(False)
2127 if r != self.wallet.seed:
2128 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2135 def seed_dialog(self, is_restore=True):
2139 vbox = QVBoxLayout()
2141 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2143 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2145 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2148 label.setWordWrap(True)
2149 vbox.addWidget(label)
2151 seed_e = QTextEdit()
2152 seed_e.setMaximumHeight(100)
2153 vbox.addWidget(seed_e)
2156 grid = QGridLayout()
2158 gap_e = AmountEdit(None, True)
2160 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2161 grid.addWidget(gap_e, 2, 1)
2162 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2163 vbox.addLayout(grid)
2165 vbox.addLayout(ok_cancel_buttons(d))
2168 if not d.exec_(): return
2171 seed = str(seed_e.toPlainText())
2175 seed = mnemonic.mn_decode( seed.split() )
2177 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2181 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2188 gap = int(unicode(gap_e.text()))
2190 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2195 def network_dialog(self):
2196 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2199 def show_seed(self):
2200 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2202 def password_dialog(self):
2203 if self.wallet.seed:
2204 ElectrumWindow.change_password_dialog(self.wallet)
2207 def restore_wallet(self):
2208 wallet = self.wallet
2209 # wait until we are connected, because the user might have selected another server
2210 if not wallet.interface.is_connected:
2211 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2212 waiting_dialog(waiting)
2214 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2215 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2217 wallet.set_up_to_date(False)
2218 wallet.interface.poke('synchronizer')
2219 waiting_dialog(waiting)
2220 if wallet.is_found():
2221 print_error( "Recovery successful" )
2223 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2230 w = ElectrumWindow(self.wallet, self.config)
2231 if url: w.set_url(url)