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)
932 self.update_receive_tab()
935 def save_column_widths(self):
936 if not self.expert_mode:
937 widths = [ self.receive_list.columnWidth(0) ]
940 for i in range(self.receive_list.columnCount() -1):
941 widths.append(self.receive_list.columnWidth(i))
942 self.column_widths["receive"][self.expert_mode] = widths
944 self.column_widths["history"] = []
945 for i in range(self.history_list.columnCount() - 1):
946 self.column_widths["history"].append(self.history_list.columnWidth(i))
948 self.column_widths["contacts"] = []
949 for i in range(self.contacts_list.columnCount() - 1):
950 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
952 self.config.set_key("column_widths", self.column_widths, True)
955 def create_contacts_tab(self):
956 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
957 l.setContextMenuPolicy(Qt.CustomContextMenu)
958 l.customContextMenuRequested.connect(self.create_contact_menu)
959 for i,width in enumerate(self.column_widths['contacts']):
960 l.setColumnWidth(i, width)
962 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
963 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
964 self.contacts_list = l
965 self.contacts_buttons_hbox = hbox
966 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
971 def delete_imported_key(self, addr):
972 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
973 self.wallet.delete_imported_key(addr)
974 self.update_receive_tab()
975 self.update_history_tab()
978 def create_receive_menu(self, position):
979 # fixme: this function apparently has a side effect.
980 # if it is not called the menu pops up several times
981 #self.receive_list.selectedIndexes()
983 item = self.receive_list.itemAt(position)
985 addr = unicode(item.text(0))
986 if not is_valid(addr):
987 item.setExpanded(not item.isExpanded())
990 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
991 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
992 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
993 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
994 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
995 if addr in self.wallet.imported_keys:
996 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
999 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1000 menu.addAction(t, lambda: self.toggle_freeze(addr))
1001 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1002 menu.addAction(t, lambda: self.toggle_priority(addr))
1004 self.run_hook('receive_menu', menu)
1005 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1008 def payto(self, addr):
1010 label = self.wallet.labels.get(addr)
1011 m_addr = label + ' <' + addr + '>' if label else addr
1012 self.tabs.setCurrentIndex(1)
1013 self.payto_e.setText(m_addr)
1014 self.amount_e.setFocus()
1017 def delete_contact(self, x):
1018 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1019 if x in self.wallet.addressbook:
1020 self.wallet.addressbook.remove(x)
1021 self.set_label(x, None)
1022 self.update_history_tab()
1023 self.update_contacts_tab()
1024 self.update_completions()
1027 def create_contact_menu(self, position):
1028 item = self.contacts_list.itemAt(position)
1030 addr = unicode(item.text(0))
1031 label = unicode(item.text(1))
1032 is_editable = item.data(0,32).toBool()
1033 payto_addr = item.data(0,33).toString()
1035 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1036 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1037 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1039 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1040 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1042 self.run_hook('create_contact_menu', menu, item)
1043 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1046 def update_receive_item(self, item):
1047 item.setFont(0, QFont(MONOSPACE_FONT))
1048 address = str(item.data(0,0).toString())
1049 label = self.wallet.labels.get(address,'')
1050 item.setData(1,0,label)
1051 item.setData(0,32, True) # is editable
1053 self.run_hook('update_receive_item', address, item)
1055 c, u = self.wallet.get_addr_balance(address)
1056 balance = self.format_amount(c + u)
1057 item.setData(2,0,balance)
1059 if self.expert_mode:
1060 if address in self.wallet.frozen_addresses:
1061 item.setBackgroundColor(0, QColor('lightblue'))
1062 elif address in self.wallet.prioritized_addresses:
1063 item.setBackgroundColor(0, QColor('lightgreen'))
1066 def update_receive_tab(self):
1067 l = self.receive_list
1070 l.setColumnHidden(2, not self.expert_mode)
1071 l.setColumnHidden(3, not self.expert_mode)
1072 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1073 l.setColumnWidth(i, width)
1075 if self.current_account is None:
1076 account_items = self.wallet.accounts.items()
1077 elif self.current_account != -1:
1078 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1082 for k, account in account_items:
1083 name = account.get('name',str(k))
1084 c,u = self.wallet.get_account_balance(k)
1085 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1086 l.addTopLevelItem(account_item)
1087 account_item.setExpanded(True)
1089 for is_change in ([0,1] if self.expert_mode else [0]):
1090 if self.expert_mode:
1091 name = "Receiving" if not is_change else "Change"
1092 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1093 account_item.addChild(seq_item)
1094 if not is_change: seq_item.setExpanded(True)
1096 seq_item = account_item
1100 for address in account[is_change]:
1101 h = self.wallet.history.get(address,[])
1105 if gap > self.wallet.gap_limit:
1110 num_tx = '*' if h == ['*'] else "%d"%len(h)
1111 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1112 self.update_receive_item(item)
1114 item.setBackgroundColor(1, QColor('red'))
1115 seq_item.addChild(item)
1118 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1119 c,u = self.wallet.get_imported_balance()
1120 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1121 l.addTopLevelItem(account_item)
1122 account_item.setExpanded(True)
1123 for address in self.wallet.imported_keys.keys():
1124 item = QTreeWidgetItem( [ address, '', '', ''] )
1125 self.update_receive_item(item)
1126 account_item.addChild(item)
1129 # we use column 1 because column 0 may be hidden
1130 l.setCurrentItem(l.topLevelItem(0),1)
1133 def update_contacts_tab(self):
1135 l = self.contacts_list
1138 for address in self.wallet.addressbook:
1139 label = self.wallet.labels.get(address,'')
1140 n = self.wallet.get_num_tx(address)
1141 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1142 item.setFont(0, QFont(MONOSPACE_FONT))
1143 # 32 = label can be edited (bool)
1144 item.setData(0,32, True)
1146 item.setData(0,33, address)
1147 l.addTopLevelItem(item)
1149 self.run_hook('update_contacts_tab', l)
1150 l.setCurrentItem(l.topLevelItem(0))
1154 def create_console_tab(self):
1155 from qt_console import Console
1156 self.console = console = Console()
1157 self.console.history = self.config.get("console-history",[])
1158 self.console.history_index = len(self.console.history)
1160 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1161 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1163 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1165 def mkfunc(f, method):
1166 return lambda *args: apply( f, (method, args, self.password_dialog ))
1168 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1169 methods[m] = mkfunc(c._run, m)
1171 console.updateNamespace(methods)
1174 def change_account(self,s):
1175 if s == _("All accounts"):
1176 self.current_account = None
1178 accounts = self.wallet.get_accounts()
1179 for k, v in accounts.items():
1181 self.current_account = k
1182 self.update_history_tab()
1183 self.update_status()
1184 self.update_receive_tab()
1186 def create_status_bar(self):
1187 self.status_text = ""
1189 sb.setFixedHeight(35)
1190 qtVersion = qVersion()
1192 update_notification = UpdateLabel(self.config)
1193 if(update_notification.new_version):
1194 sb.addPermanentWidget(update_notification)
1196 accounts = self.wallet.get_accounts()
1197 if len(accounts) > 1:
1198 from_combo = QComboBox()
1199 from_combo.addItems([_("All accounts")] + accounts.values())
1200 from_combo.setCurrentIndex(0)
1201 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1202 sb.addPermanentWidget(from_combo)
1204 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1205 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1206 if self.wallet.seed:
1207 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1208 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1209 sb.addPermanentWidget( self.password_button )
1210 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1211 if self.wallet.seed:
1212 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1213 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1214 sb.addPermanentWidget( self.status_button )
1216 self.run_hook('create_status_bar', (sb,))
1218 self.setStatusBar(sb)
1222 self.config.set_key('gui', 'lite', True)
1225 self.lite.mini.show()
1227 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1228 self.lite.main(None)
1230 def new_contact_dialog(self):
1231 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1232 address = unicode(text)
1234 if is_valid(address):
1235 self.wallet.add_contact(address)
1236 self.update_contacts_tab()
1237 self.update_history_tab()
1238 self.update_completions()
1240 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1242 def show_master_public_key(self):
1243 dialog = QDialog(self)
1245 dialog.setWindowTitle(_("Master Public Key"))
1247 main_text = QTextEdit()
1248 main_text.setText(self.wallet.get_master_public_key())
1249 main_text.setReadOnly(True)
1250 main_text.setMaximumHeight(170)
1251 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1253 ok_button = QPushButton(_("OK"))
1254 ok_button.setDefault(True)
1255 ok_button.clicked.connect(dialog.accept)
1257 main_layout = QGridLayout()
1258 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1260 main_layout.addWidget(main_text, 1, 0)
1261 main_layout.addWidget(qrw, 1, 1 )
1263 vbox = QVBoxLayout()
1264 vbox.addLayout(main_layout)
1265 hbox = QHBoxLayout()
1267 hbox.addWidget(ok_button)
1268 vbox.addLayout(hbox)
1270 dialog.setLayout(vbox)
1275 def show_seed_dialog(self, password):
1276 if not self.wallet.seed:
1277 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1280 seed = self.wallet.decode_seed(password)
1282 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1284 self.show_seed(seed, self.wallet.imported_keys, self)
1288 def show_seed(self, seed, imported_keys, parent=None):
1289 dialog = QDialog(parent)
1291 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1293 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1295 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1297 seed_text = QTextEdit(brainwallet)
1298 seed_text.setReadOnly(True)
1299 seed_text.setMaximumHeight(130)
1301 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1302 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1303 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1304 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1306 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1307 label2 = QLabel(msg2)
1308 label2.setWordWrap(True)
1311 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1312 logo.setMaximumWidth(60)
1314 qrw = QRCodeWidget(seed)
1316 ok_button = QPushButton(_("OK"))
1317 ok_button.setDefault(True)
1318 ok_button.clicked.connect(dialog.accept)
1320 grid = QGridLayout()
1321 #main_layout.addWidget(logo, 0, 0)
1323 grid.addWidget(logo, 0, 0)
1324 grid.addWidget(label1, 0, 1)
1326 grid.addWidget(seed_text, 1, 0, 1, 2)
1328 grid.addWidget(qrw, 0, 2, 2, 1)
1330 vbox = QVBoxLayout()
1331 vbox.addLayout(grid)
1332 vbox.addWidget(label2)
1334 hbox = QHBoxLayout()
1336 hbox.addWidget(ok_button)
1337 vbox.addLayout(hbox)
1339 dialog.setLayout(vbox)
1342 def show_qrcode(self, data, title = "QR code"):
1346 d.setWindowTitle(title)
1347 d.setMinimumSize(270, 300)
1348 vbox = QVBoxLayout()
1349 qrw = QRCodeWidget(data)
1350 vbox.addWidget(qrw, 1)
1351 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1352 hbox = QHBoxLayout()
1356 filename = "qrcode.bmp"
1357 bmp.save_qrcode(qrw.qr, filename)
1358 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1360 b = QPushButton(_("Save"))
1362 b.clicked.connect(print_qr)
1364 b = QPushButton(_("Close"))
1366 b.clicked.connect(d.accept)
1369 vbox.addLayout(hbox)
1374 def do_protect(self, func, args):
1375 if self.wallet.use_encryption:
1376 password = self.password_dialog()
1382 if args != (False,):
1383 args = (self,) + args + (password,)
1385 args = (self,password)
1390 def show_private_key(self, address, password):
1391 if not address: return
1393 pk = self.wallet.get_private_key(address, password)
1394 except BaseException, e:
1395 self.show_message(str(e))
1397 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1401 def do_sign(self, address, message, signature, password):
1403 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1404 signature.setText(sig)
1405 except BaseException, e:
1406 self.show_message(str(e))
1408 def sign_message(self, address):
1409 if not address: return
1412 d.setWindowTitle(_('Sign Message'))
1413 d.setMinimumSize(410, 290)
1415 tab_widget = QTabWidget()
1417 layout = QGridLayout(tab)
1419 sign_address = QLineEdit()
1421 sign_address.setText(address)
1422 layout.addWidget(QLabel(_('Address')), 1, 0)
1423 layout.addWidget(sign_address, 1, 1)
1425 sign_message = QTextEdit()
1426 layout.addWidget(QLabel(_('Message')), 2, 0)
1427 layout.addWidget(sign_message, 2, 1)
1428 layout.setRowStretch(2,3)
1430 sign_signature = QTextEdit()
1431 layout.addWidget(QLabel(_('Signature')), 3, 0)
1432 layout.addWidget(sign_signature, 3, 1)
1433 layout.setRowStretch(3,1)
1436 hbox = QHBoxLayout()
1437 b = QPushButton(_("Sign"))
1439 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1440 b = QPushButton(_("Close"))
1441 b.clicked.connect(d.accept)
1443 layout.addLayout(hbox, 4, 1)
1444 tab_widget.addTab(tab, _("Sign"))
1448 layout = QGridLayout(tab)
1450 verify_address = QLineEdit()
1451 layout.addWidget(QLabel(_('Address')), 1, 0)
1452 layout.addWidget(verify_address, 1, 1)
1454 verify_message = QTextEdit()
1455 layout.addWidget(QLabel(_('Message')), 2, 0)
1456 layout.addWidget(verify_message, 2, 1)
1457 layout.setRowStretch(2,3)
1459 verify_signature = QTextEdit()
1460 layout.addWidget(QLabel(_('Signature')), 3, 0)
1461 layout.addWidget(verify_signature, 3, 1)
1462 layout.setRowStretch(3,1)
1465 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1466 self.show_message(_("Signature verified"))
1468 self.show_message(_("Error: wrong signature"))
1470 hbox = QHBoxLayout()
1471 b = QPushButton(_("Verify"))
1472 b.clicked.connect(do_verify)
1474 b = QPushButton(_("Close"))
1475 b.clicked.connect(d.accept)
1477 layout.addLayout(hbox, 4, 1)
1478 tab_widget.addTab(tab, _("Verify"))
1480 vbox = QVBoxLayout()
1481 vbox.addWidget(tab_widget)
1488 def question(self, msg):
1489 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1491 def show_message(self, msg):
1492 QMessageBox.information(self, _('Message'), msg, _('OK'))
1494 def password_dialog(self ):
1501 vbox = QVBoxLayout()
1502 msg = _('Please enter your password')
1503 vbox.addWidget(QLabel(msg))
1505 grid = QGridLayout()
1507 grid.addWidget(QLabel(_('Password')), 1, 0)
1508 grid.addWidget(pw, 1, 1)
1509 vbox.addLayout(grid)
1511 vbox.addLayout(ok_cancel_buttons(d))
1514 self.run_hook('password_dialog', pw, grid, 1)
1515 if not d.exec_(): return
1516 return unicode(pw.text())
1523 def change_password_dialog( wallet, parent=None ):
1526 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1534 new_pw = QLineEdit()
1535 new_pw.setEchoMode(2)
1536 conf_pw = QLineEdit()
1537 conf_pw.setEchoMode(2)
1539 vbox = QVBoxLayout()
1541 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1542 +_('To disable wallet encryption, enter an empty new password.')) \
1543 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1545 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1546 +_("Leave these fields empty if you want to disable encryption.")
1547 vbox.addWidget(QLabel(msg))
1549 grid = QGridLayout()
1552 if wallet.use_encryption:
1553 grid.addWidget(QLabel(_('Password')), 1, 0)
1554 grid.addWidget(pw, 1, 1)
1556 grid.addWidget(QLabel(_('New Password')), 2, 0)
1557 grid.addWidget(new_pw, 2, 1)
1559 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1560 grid.addWidget(conf_pw, 3, 1)
1561 vbox.addLayout(grid)
1563 vbox.addLayout(ok_cancel_buttons(d))
1566 if not d.exec_(): return
1568 password = unicode(pw.text()) if wallet.use_encryption else None
1569 new_password = unicode(new_pw.text())
1570 new_password2 = unicode(conf_pw.text())
1573 seed = wallet.decode_seed(password)
1575 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1578 if new_password != new_password2:
1579 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1580 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1583 wallet.update_password(seed, password, new_password)
1585 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1588 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1591 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1592 parent.password_button.setIcon( icon )
1596 def generate_transaction_information_widget(self, tx):
1597 tabs = QTabWidget(self)
1600 grid_ui = QGridLayout(tab1)
1601 grid_ui.setColumnStretch(0,1)
1602 tabs.addTab(tab1, _('Outputs') )
1604 tree_widget = MyTreeWidget(self)
1605 tree_widget.setColumnCount(2)
1606 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1607 tree_widget.setColumnWidth(0, 300)
1608 tree_widget.setColumnWidth(1, 50)
1610 for address, value in tx.outputs:
1611 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1612 tree_widget.addTopLevelItem(item)
1614 tree_widget.setMaximumHeight(100)
1616 grid_ui.addWidget(tree_widget)
1619 grid_ui = QGridLayout(tab2)
1620 grid_ui.setColumnStretch(0,1)
1621 tabs.addTab(tab2, _('Inputs') )
1623 tree_widget = MyTreeWidget(self)
1624 tree_widget.setColumnCount(2)
1625 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1627 for input_line in tx.inputs:
1628 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1629 tree_widget.addTopLevelItem(item)
1631 tree_widget.setMaximumHeight(100)
1633 grid_ui.addWidget(tree_widget)
1637 def tx_dict_from_text(self, txt):
1639 tx_dict = json.loads(str(txt))
1640 assert "hex" in tx_dict.keys()
1641 assert "complete" in tx_dict.keys()
1642 if not tx_dict["complete"]:
1643 assert "input_info" in tx_dict.keys()
1645 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1650 def read_tx_from_file(self):
1651 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1655 with open(fileName, "r") as f:
1656 file_content = f.read()
1657 except (ValueError, IOError, os.error), reason:
1658 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1660 return self.tx_dict_from_text(file_content)
1664 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1666 self.wallet.signrawtransaction(tx, input_info, [], password)
1668 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1670 with open(fileName, "w+") as f:
1671 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1672 self.show_message(_("Transaction saved successfully"))
1675 except BaseException, e:
1676 self.show_message(str(e))
1679 def send_raw_transaction(self, raw_tx, dialog = ""):
1680 result, result_message = self.wallet.sendtx( raw_tx )
1682 self.show_message("Transaction successfully sent: %s" % (result_message))
1686 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1688 def do_process_from_text(self):
1689 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1692 tx_dict = self.tx_dict_from_text(text)
1694 self.create_process_transaction_window(tx_dict)
1696 def do_process_from_file(self):
1697 tx_dict = self.read_tx_from_file()
1699 self.create_process_transaction_window(tx_dict)
1701 def create_process_transaction_window(self, tx_dict):
1702 tx = Transaction(tx_dict["hex"])
1704 dialog = QDialog(self)
1705 dialog.setMinimumWidth(500)
1706 dialog.setWindowTitle(_('Process raw transaction'))
1712 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1713 l.addWidget(QLabel(_("Actions")), 4,0)
1715 if tx_dict["complete"] == False:
1716 l.addWidget(QLabel(_("Unsigned")), 3,1)
1717 if self.wallet.seed :
1718 b = QPushButton("Sign transaction")
1719 input_info = json.loads(tx_dict["input_info"])
1720 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1721 l.addWidget(b, 4, 1)
1723 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1725 l.addWidget(QLabel(_("Signed")), 3,1)
1726 b = QPushButton("Broadcast transaction")
1727 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1730 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1731 cancelButton = QPushButton(_("Cancel"))
1732 cancelButton.clicked.connect(lambda: dialog.done(0))
1733 l.addWidget(cancelButton, 4,2)
1739 def do_export_privkeys(self, password):
1740 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.")))
1743 select_export = _('Select file to export your private keys to')
1744 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1746 with open(fileName, "w+") as csvfile:
1747 transaction = csv.writer(csvfile)
1748 transaction.writerow(["address", "private_key"])
1751 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1752 transaction.writerow(["%34s"%addr,pk])
1754 self.show_message(_("Private keys exported."))
1756 except (IOError, os.error), reason:
1757 export_error_label = _("Electrum was unable to produce a private key-export.")
1758 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1760 except BaseException, e:
1761 self.show_message(str(e))
1765 def do_import_labels(self):
1766 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1767 if not labelsFile: return
1769 f = open(labelsFile, 'r')
1772 for key, value in json.loads(data).items():
1773 self.wallet.labels[key] = value
1775 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1776 except (IOError, os.error), reason:
1777 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1780 def do_export_labels(self):
1781 labels = self.wallet.labels
1783 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1785 with open(fileName, 'w+') as f:
1786 json.dump(labels, f)
1787 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1788 except (IOError, os.error), reason:
1789 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1792 def do_export_history(self):
1793 from gui_lite import csv_transaction
1794 csv_transaction(self.wallet)
1798 def do_import_privkey(self, password):
1799 if not self.wallet.imported_keys:
1800 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1801 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1802 + _('Are you sure you understand what you are doing?'), 3, 4)
1805 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1808 text = str(text).split()
1813 addr = self.wallet.import_key(key, password)
1814 except BaseException as e:
1820 addrlist.append(addr)
1822 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1824 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1825 self.update_receive_tab()
1826 self.update_history_tab()
1829 def settings_dialog(self):
1831 d.setWindowTitle(_('Electrum Settings'))
1833 vbox = QVBoxLayout()
1835 tabs = QTabWidget(self)
1836 self.settings_tab = tabs
1837 vbox.addWidget(tabs)
1840 grid_ui = QGridLayout(tab1)
1841 grid_ui.setColumnStretch(0,1)
1842 tabs.addTab(tab1, _('Display') )
1844 nz_label = QLabel(_('Display zeros'))
1845 grid_ui.addWidget(nz_label, 0, 0)
1846 nz_e = AmountEdit(None,True)
1847 nz_e.setText("%d"% self.wallet.num_zeros)
1848 grid_ui.addWidget(nz_e, 0, 1)
1849 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1850 grid_ui.addWidget(HelpButton(msg), 0, 2)
1851 if not self.config.is_modifiable('num_zeros'):
1852 for w in [nz_e, nz_label]: w.setEnabled(False)
1854 lang_label=QLabel(_('Language') + ':')
1855 grid_ui.addWidget(lang_label, 1, 0)
1856 lang_combo = QComboBox()
1857 from i18n import languages
1858 lang_combo.addItems(languages.values())
1860 index = languages.keys().index(self.config.get("language",''))
1863 lang_combo.setCurrentIndex(index)
1864 grid_ui.addWidget(lang_combo, 1, 1)
1865 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1866 if not self.config.is_modifiable('language'):
1867 for w in [lang_combo, lang_label]: w.setEnabled(False)
1869 currencies = self.exchanger.get_currencies()
1870 currencies.insert(0, "None")
1872 cur_label=QLabel(_('Currency') + ':')
1873 grid_ui.addWidget(cur_label , 2, 0)
1874 cur_combo = QComboBox()
1875 cur_combo.addItems(currencies)
1877 index = currencies.index(self.config.get('currency', "None"))
1880 cur_combo.setCurrentIndex(index)
1881 grid_ui.addWidget(cur_combo, 2, 1)
1882 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1884 expert_cb = QCheckBox(_('Expert mode'))
1885 expert_cb.setChecked(self.expert_mode)
1886 grid_ui.addWidget(expert_cb, 3, 0)
1887 hh = _('In expert mode, your client will:') + '\n' \
1888 + _(' - Show change addresses in the Receive tab') + '\n' \
1889 + _(' - Display the balance of each address') + '\n' \
1890 + _(' - Add freeze/prioritize actions to addresses.')
1891 grid_ui.addWidget(HelpButton(hh), 3, 2)
1892 grid_ui.setRowStretch(4,1)
1896 grid_wallet = QGridLayout(tab2)
1897 grid_wallet.setColumnStretch(0,1)
1898 tabs.addTab(tab2, _('Wallet') )
1900 fee_label = QLabel(_('Transaction fee'))
1901 grid_wallet.addWidget(fee_label, 0, 0)
1902 fee_e = AmountEdit(self.base_unit)
1903 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1904 grid_wallet.addWidget(fee_e, 0, 2)
1905 msg = _('Fee per kilobyte of transaction.') + ' ' \
1906 + _('Recommended value') + ': ' + self.format_amount(20000)
1907 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1908 if not self.config.is_modifiable('fee_per_kb'):
1909 for w in [fee_e, fee_label]: w.setEnabled(False)
1911 usechange_cb = QCheckBox(_('Use change addresses'))
1912 usechange_cb.setChecked(self.wallet.use_change)
1913 grid_wallet.addWidget(usechange_cb, 1, 0)
1914 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1915 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1917 gap_label = QLabel(_('Gap limit'))
1918 grid_wallet.addWidget(gap_label, 2, 0)
1919 gap_e = AmountEdit(None,True)
1920 gap_e.setText("%d"% self.wallet.gap_limit)
1921 grid_wallet.addWidget(gap_e, 2, 2)
1922 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1923 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1924 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1925 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1926 + _('Warning') + ': ' \
1927 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1928 + _('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'
1929 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1930 if not self.config.is_modifiable('gap_limit'):
1931 for w in [gap_e, gap_label]: w.setEnabled(False)
1933 units = ['BTC', 'mBTC']
1934 unit_label = QLabel(_('Base unit'))
1935 grid_wallet.addWidget(unit_label, 3, 0)
1936 unit_combo = QComboBox()
1937 unit_combo.addItems(units)
1938 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1939 grid_wallet.addWidget(unit_combo, 3, 2)
1940 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1941 + '\n1BTC=1000mBTC.\n' \
1942 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1943 grid_wallet.setRowStretch(4,1)
1948 grid_io = QGridLayout(tab3)
1949 grid_io.setColumnStretch(0,1)
1950 tabs.addTab(tab3, _('Import/Export') )
1952 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1953 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1954 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1955 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1957 grid_io.addWidget(QLabel(_('History')), 2, 0)
1958 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1959 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1961 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1963 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1964 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1965 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1967 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1968 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1969 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1970 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1971 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1974 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1975 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1976 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1977 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1979 grid_io.setRowStretch(6,1)
1984 tab5 = QScrollArea()
1985 tab5.setEnabled(True)
1986 tab5.setWidgetResizable(True)
1988 grid_plugins = QGridLayout()
1989 grid_plugins.setColumnStretch(0,1)
1992 w.setLayout(grid_plugins)
1994 tab5.setMaximumSize(tab3.size()) # optional
1996 w.setMinimumHeight(len(self.plugins)*35)
1998 tabs.addTab(tab5, _('Plugins') )
1999 def mk_toggle(cb, p):
2000 return lambda: cb.setChecked(p.toggle())
2001 for i, p in enumerate(self.plugins):
2003 name, description = p.get_info()
2004 cb = QCheckBox(name)
2005 cb.setDisabled(not p.is_available())
2006 cb.setChecked(p.is_enabled())
2007 cb.clicked.connect(mk_toggle(cb,p))
2008 grid_plugins.addWidget(cb, i, 0)
2009 if p.requires_settings():
2010 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2011 grid_plugins.addWidget(HelpButton(description), i, 2)
2013 print_msg("Error: cannot display plugin", p)
2014 traceback.print_exc(file=sys.stdout)
2015 grid_plugins.setRowStretch(i+1,1)
2017 self.run_hook('create_settings_tab', tabs)
2019 vbox.addLayout(ok_cancel_buttons(d))
2023 if not d.exec_(): return
2025 fee = unicode(fee_e.text())
2027 fee = self.read_amount(fee)
2029 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2032 self.wallet.set_fee(fee)
2034 nz = unicode(nz_e.text())
2039 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2042 if self.wallet.num_zeros != nz:
2043 self.wallet.num_zeros = nz
2044 self.config.set_key('num_zeros', nz, True)
2045 self.update_history_tab()
2046 self.update_receive_tab()
2048 usechange_result = usechange_cb.isChecked()
2049 if self.wallet.use_change != usechange_result:
2050 self.wallet.use_change = usechange_result
2051 self.config.set_key('use_change', self.wallet.use_change, True)
2053 unit_result = units[unit_combo.currentIndex()]
2054 if self.base_unit() != unit_result:
2055 self.decimal_point = 8 if unit_result == 'BTC' else 5
2056 self.config.set_key('decimal_point', self.decimal_point, True)
2057 self.update_history_tab()
2058 self.update_status()
2061 n = int(gap_e.text())
2063 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2066 if self.wallet.gap_limit != n:
2067 r = self.wallet.change_gap_limit(n)
2069 self.update_receive_tab()
2070 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2072 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2074 need_restart = False
2076 lang_request = languages.keys()[lang_combo.currentIndex()]
2077 if lang_request != self.config.get('language'):
2078 self.config.set_key("language", lang_request, True)
2081 cur_request = str(currencies[cur_combo.currentIndex()])
2082 if cur_request != self.config.get('currency', "None"):
2083 self.config.set_key('currency', cur_request, True)
2084 self.update_wallet()
2086 self.run_hook('close_settings_dialog')
2089 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2091 self.receive_tab_set_mode(expert_cb.isChecked())
2093 def run_network_dialog(self):
2094 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2096 def closeEvent(self, event):
2098 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2099 self.save_column_widths()
2100 self.config.set_key("console-history",self.console.history[-50:])
2109 def __init__(self, wallet, config, app=None):
2110 self.wallet = wallet
2111 self.config = config
2113 self.app = QApplication(sys.argv)
2116 def restore_or_create(self):
2117 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2118 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2119 if r==2: return None
2120 return 'restore' if r==1 else 'create'
2123 def verify_seed(self):
2124 r = self.seed_dialog(False)
2125 if r != self.wallet.seed:
2126 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2133 def seed_dialog(self, is_restore=True):
2137 vbox = QVBoxLayout()
2139 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2141 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2143 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2146 label.setWordWrap(True)
2147 vbox.addWidget(label)
2149 seed_e = QTextEdit()
2150 seed_e.setMaximumHeight(100)
2151 vbox.addWidget(seed_e)
2154 grid = QGridLayout()
2156 gap_e = AmountEdit(None, True)
2158 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2159 grid.addWidget(gap_e, 2, 1)
2160 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2161 vbox.addLayout(grid)
2163 vbox.addLayout(ok_cancel_buttons(d))
2166 if not d.exec_(): return
2169 seed = str(seed_e.toPlainText())
2173 seed = mnemonic.mn_decode( seed.split() )
2175 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2179 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2186 gap = int(unicode(gap_e.text()))
2188 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2193 def network_dialog(self):
2194 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2197 def show_seed(self):
2198 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2200 def password_dialog(self):
2201 if self.wallet.seed:
2202 ElectrumWindow.change_password_dialog(self.wallet)
2205 def restore_wallet(self):
2206 wallet = self.wallet
2207 # wait until we are connected, because the user might have selected another server
2208 if not wallet.interface.is_connected:
2209 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2210 waiting_dialog(waiting)
2212 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2213 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2215 wallet.set_up_to_date(False)
2216 wallet.interface.poke('synchronizer')
2217 waiting_dialog(waiting)
2218 if wallet.is_found():
2219 print_error( "Recovery successful" )
2221 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2228 w = ElectrumWindow(self.wallet, self.config)
2229 if url: w.set_url(url)