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.imported_keys.pop(addr)
975 self.update_receive_tab()
976 self.update_history_tab()
980 def create_receive_menu(self, position):
981 # fixme: this function apparently has a side effect.
982 # if it is not called the menu pops up several times
983 #self.receive_list.selectedIndexes()
985 item = self.receive_list.itemAt(position)
987 addr = unicode(item.text(0))
988 if not is_valid(addr):
989 item.setExpanded(not item.isExpanded())
992 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
993 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
994 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
995 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
996 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
997 if addr in self.wallet.imported_keys:
998 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1000 if self.expert_mode:
1001 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1002 menu.addAction(t, lambda: self.toggle_freeze(addr))
1003 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1004 menu.addAction(t, lambda: self.toggle_priority(addr))
1006 self.run_hook('receive_menu', menu)
1007 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1010 def payto(self, addr):
1012 label = self.wallet.labels.get(addr)
1013 m_addr = label + ' <' + addr + '>' if label else addr
1014 self.tabs.setCurrentIndex(1)
1015 self.payto_e.setText(m_addr)
1016 self.amount_e.setFocus()
1019 def delete_contact(self, x):
1020 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1021 if x in self.wallet.addressbook:
1022 self.wallet.addressbook.remove(x)
1023 self.set_label(x, None)
1024 self.update_history_tab()
1025 self.update_contacts_tab()
1026 self.update_completions()
1029 def create_contact_menu(self, position):
1030 item = self.contacts_list.itemAt(position)
1032 addr = unicode(item.text(0))
1033 label = unicode(item.text(1))
1034 is_editable = item.data(0,32).toBool()
1035 payto_addr = item.data(0,33).toString()
1037 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1038 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1039 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1041 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1042 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1044 self.run_hook('create_contact_menu', menu, item)
1045 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1048 def update_receive_item(self, item):
1049 item.setFont(0, QFont(MONOSPACE_FONT))
1050 address = str(item.data(0,0).toString())
1051 label = self.wallet.labels.get(address,'')
1052 item.setData(1,0,label)
1053 item.setData(0,32, True) # is editable
1055 self.run_hook('update_receive_item', address, item)
1057 c, u = self.wallet.get_addr_balance(address)
1058 balance = self.format_amount(c + u)
1059 item.setData(2,0,balance)
1061 if self.expert_mode:
1062 if address in self.wallet.frozen_addresses:
1063 item.setBackgroundColor(0, QColor('lightblue'))
1064 elif address in self.wallet.prioritized_addresses:
1065 item.setBackgroundColor(0, QColor('lightgreen'))
1068 def update_receive_tab(self):
1069 l = self.receive_list
1072 l.setColumnHidden(2, not self.expert_mode)
1073 l.setColumnHidden(3, not self.expert_mode)
1074 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1075 l.setColumnWidth(i, width)
1077 if self.current_account is None:
1078 account_items = self.wallet.accounts.items()
1079 elif self.current_account != -1:
1080 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1084 for k, account in account_items:
1085 name = account.get('name',str(k))
1086 c,u = self.wallet.get_account_balance(k)
1087 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1088 l.addTopLevelItem(account_item)
1089 account_item.setExpanded(True)
1091 for is_change in ([0,1] if self.expert_mode else [0]):
1092 if self.expert_mode:
1093 name = "Receiving" if not is_change else "Change"
1094 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1095 account_item.addChild(seq_item)
1096 if not is_change: seq_item.setExpanded(True)
1098 seq_item = account_item
1102 for address in account[is_change]:
1103 h = self.wallet.history.get(address,[])
1107 if gap > self.wallet.gap_limit:
1112 num_tx = '*' if h == ['*'] else "%d"%len(h)
1113 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1114 self.update_receive_item(item)
1116 item.setBackgroundColor(1, QColor('red'))
1117 seq_item.addChild(item)
1120 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1121 c,u = self.wallet.get_imported_balance()
1122 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1123 l.addTopLevelItem(account_item)
1124 account_item.setExpanded(True)
1125 for address in self.wallet.imported_keys.keys():
1126 item = QTreeWidgetItem( [ address, '', '', ''] )
1127 self.update_receive_item(item)
1128 account_item.addChild(item)
1131 # we use column 1 because column 0 may be hidden
1132 l.setCurrentItem(l.topLevelItem(0),1)
1135 def update_contacts_tab(self):
1137 l = self.contacts_list
1140 for address in self.wallet.addressbook:
1141 label = self.wallet.labels.get(address,'')
1142 n = self.wallet.get_num_tx(address)
1143 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1144 item.setFont(0, QFont(MONOSPACE_FONT))
1145 # 32 = label can be edited (bool)
1146 item.setData(0,32, True)
1148 item.setData(0,33, address)
1149 l.addTopLevelItem(item)
1151 self.run_hook('update_contacts_tab', l)
1152 l.setCurrentItem(l.topLevelItem(0))
1156 def create_console_tab(self):
1157 from qt_console import Console
1158 self.console = console = Console()
1159 self.console.history = self.config.get("console-history",[])
1160 self.console.history_index = len(self.console.history)
1162 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1163 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1165 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1167 def mkfunc(f, method):
1168 return lambda *args: apply( f, (method, args, self.password_dialog ))
1170 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1171 methods[m] = mkfunc(c._run, m)
1173 console.updateNamespace(methods)
1176 def change_account(self,s):
1177 if s == _("All accounts"):
1178 self.current_account = None
1180 accounts = self.wallet.get_accounts()
1181 for k, v in accounts.items():
1183 self.current_account = k
1184 self.update_history_tab()
1185 self.update_status()
1186 self.update_receive_tab()
1188 def create_status_bar(self):
1189 self.status_text = ""
1191 sb.setFixedHeight(35)
1192 qtVersion = qVersion()
1194 update_notification = UpdateLabel(self.config)
1195 if(update_notification.new_version):
1196 sb.addPermanentWidget(update_notification)
1198 accounts = self.wallet.get_accounts()
1199 if len(accounts) > 1:
1200 from_combo = QComboBox()
1201 from_combo.addItems([_("All accounts")] + accounts.values())
1202 from_combo.setCurrentIndex(0)
1203 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1204 sb.addPermanentWidget(from_combo)
1206 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1207 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1208 if self.wallet.seed:
1209 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1210 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1211 sb.addPermanentWidget( self.password_button )
1212 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1213 if self.wallet.seed:
1214 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1215 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1216 sb.addPermanentWidget( self.status_button )
1218 self.run_hook('create_status_bar', (sb,))
1220 self.setStatusBar(sb)
1224 self.config.set_key('gui', 'lite', True)
1227 self.lite.mini.show()
1229 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1230 self.lite.main(None)
1232 def new_contact_dialog(self):
1233 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1234 address = unicode(text)
1236 if is_valid(address):
1237 self.wallet.addressbook.append(address)
1239 self.update_contacts_tab()
1240 self.update_history_tab()
1241 self.update_completions()
1243 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1245 def show_master_public_key(self):
1246 dialog = QDialog(self)
1248 dialog.setWindowTitle(_("Master Public Key"))
1250 main_text = QTextEdit()
1251 main_text.setText(self.wallet.get_master_public_key())
1252 main_text.setReadOnly(True)
1253 main_text.setMaximumHeight(170)
1254 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1256 ok_button = QPushButton(_("OK"))
1257 ok_button.setDefault(True)
1258 ok_button.clicked.connect(dialog.accept)
1260 main_layout = QGridLayout()
1261 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1263 main_layout.addWidget(main_text, 1, 0)
1264 main_layout.addWidget(qrw, 1, 1 )
1266 vbox = QVBoxLayout()
1267 vbox.addLayout(main_layout)
1268 hbox = QHBoxLayout()
1270 hbox.addWidget(ok_button)
1271 vbox.addLayout(hbox)
1273 dialog.setLayout(vbox)
1278 def show_seed_dialog(self, password):
1279 if not self.wallet.seed:
1280 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1283 seed = self.wallet.decode_seed(password)
1285 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1287 self.show_seed(seed, self.wallet.imported_keys, self)
1291 def show_seed(self, seed, imported_keys, parent=None):
1292 dialog = QDialog(parent)
1294 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1296 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1298 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1300 seed_text = QTextEdit(brainwallet)
1301 seed_text.setReadOnly(True)
1302 seed_text.setMaximumHeight(130)
1304 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1305 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1306 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1307 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1309 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1310 label2 = QLabel(msg2)
1311 label2.setWordWrap(True)
1314 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1315 logo.setMaximumWidth(60)
1317 qrw = QRCodeWidget(seed)
1319 ok_button = QPushButton(_("OK"))
1320 ok_button.setDefault(True)
1321 ok_button.clicked.connect(dialog.accept)
1323 grid = QGridLayout()
1324 #main_layout.addWidget(logo, 0, 0)
1326 grid.addWidget(logo, 0, 0)
1327 grid.addWidget(label1, 0, 1)
1329 grid.addWidget(seed_text, 1, 0, 1, 2)
1331 grid.addWidget(qrw, 0, 2, 2, 1)
1333 vbox = QVBoxLayout()
1334 vbox.addLayout(grid)
1335 vbox.addWidget(label2)
1337 hbox = QHBoxLayout()
1339 hbox.addWidget(ok_button)
1340 vbox.addLayout(hbox)
1342 dialog.setLayout(vbox)
1345 def show_qrcode(self, data, title = "QR code"):
1349 d.setWindowTitle(title)
1350 d.setMinimumSize(270, 300)
1351 vbox = QVBoxLayout()
1352 qrw = QRCodeWidget(data)
1353 vbox.addWidget(qrw, 1)
1354 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1355 hbox = QHBoxLayout()
1359 filename = "qrcode.bmp"
1360 bmp.save_qrcode(qrw.qr, filename)
1361 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1363 b = QPushButton(_("Save"))
1365 b.clicked.connect(print_qr)
1367 b = QPushButton(_("Close"))
1369 b.clicked.connect(d.accept)
1372 vbox.addLayout(hbox)
1377 def do_protect(self, func, args):
1378 if self.wallet.use_encryption:
1379 password = self.password_dialog()
1385 if args != (False,):
1386 args = (self,) + args + (password,)
1388 args = (self,password)
1393 def show_private_key(self, address, password):
1394 if not address: return
1396 pk = self.wallet.get_private_key(address, password)
1397 except BaseException, e:
1398 self.show_message(str(e))
1400 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1404 def do_sign(self, address, message, signature, password):
1406 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1407 signature.setText(sig)
1408 except BaseException, e:
1409 self.show_message(str(e))
1411 def sign_message(self, address):
1412 if not address: return
1415 d.setWindowTitle(_('Sign Message'))
1416 d.setMinimumSize(410, 290)
1418 tab_widget = QTabWidget()
1420 layout = QGridLayout(tab)
1422 sign_address = QLineEdit()
1424 sign_address.setText(address)
1425 layout.addWidget(QLabel(_('Address')), 1, 0)
1426 layout.addWidget(sign_address, 1, 1)
1428 sign_message = QTextEdit()
1429 layout.addWidget(QLabel(_('Message')), 2, 0)
1430 layout.addWidget(sign_message, 2, 1)
1431 layout.setRowStretch(2,3)
1433 sign_signature = QTextEdit()
1434 layout.addWidget(QLabel(_('Signature')), 3, 0)
1435 layout.addWidget(sign_signature, 3, 1)
1436 layout.setRowStretch(3,1)
1439 hbox = QHBoxLayout()
1440 b = QPushButton(_("Sign"))
1442 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1443 b = QPushButton(_("Close"))
1444 b.clicked.connect(d.accept)
1446 layout.addLayout(hbox, 4, 1)
1447 tab_widget.addTab(tab, _("Sign"))
1451 layout = QGridLayout(tab)
1453 verify_address = QLineEdit()
1454 layout.addWidget(QLabel(_('Address')), 1, 0)
1455 layout.addWidget(verify_address, 1, 1)
1457 verify_message = QTextEdit()
1458 layout.addWidget(QLabel(_('Message')), 2, 0)
1459 layout.addWidget(verify_message, 2, 1)
1460 layout.setRowStretch(2,3)
1462 verify_signature = QTextEdit()
1463 layout.addWidget(QLabel(_('Signature')), 3, 0)
1464 layout.addWidget(verify_signature, 3, 1)
1465 layout.setRowStretch(3,1)
1468 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1469 self.show_message(_("Signature verified"))
1471 self.show_message(_("Error: wrong signature"))
1473 hbox = QHBoxLayout()
1474 b = QPushButton(_("Verify"))
1475 b.clicked.connect(do_verify)
1477 b = QPushButton(_("Close"))
1478 b.clicked.connect(d.accept)
1480 layout.addLayout(hbox, 4, 1)
1481 tab_widget.addTab(tab, _("Verify"))
1483 vbox = QVBoxLayout()
1484 vbox.addWidget(tab_widget)
1491 def question(self, msg):
1492 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1494 def show_message(self, msg):
1495 QMessageBox.information(self, _('Message'), msg, _('OK'))
1497 def password_dialog(self ):
1504 vbox = QVBoxLayout()
1505 msg = _('Please enter your password')
1506 vbox.addWidget(QLabel(msg))
1508 grid = QGridLayout()
1510 grid.addWidget(QLabel(_('Password')), 1, 0)
1511 grid.addWidget(pw, 1, 1)
1512 vbox.addLayout(grid)
1514 vbox.addLayout(ok_cancel_buttons(d))
1517 self.run_hook('password_dialog', pw, grid, 1)
1518 if not d.exec_(): return
1519 return unicode(pw.text())
1526 def change_password_dialog( wallet, parent=None ):
1529 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1537 new_pw = QLineEdit()
1538 new_pw.setEchoMode(2)
1539 conf_pw = QLineEdit()
1540 conf_pw.setEchoMode(2)
1542 vbox = QVBoxLayout()
1544 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1545 +_('To disable wallet encryption, enter an empty new password.')) \
1546 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1548 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1549 +_("Leave these fields empty if you want to disable encryption.")
1550 vbox.addWidget(QLabel(msg))
1552 grid = QGridLayout()
1555 if wallet.use_encryption:
1556 grid.addWidget(QLabel(_('Password')), 1, 0)
1557 grid.addWidget(pw, 1, 1)
1559 grid.addWidget(QLabel(_('New Password')), 2, 0)
1560 grid.addWidget(new_pw, 2, 1)
1562 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1563 grid.addWidget(conf_pw, 3, 1)
1564 vbox.addLayout(grid)
1566 vbox.addLayout(ok_cancel_buttons(d))
1569 if not d.exec_(): return
1571 password = unicode(pw.text()) if wallet.use_encryption else None
1572 new_password = unicode(new_pw.text())
1573 new_password2 = unicode(conf_pw.text())
1576 seed = wallet.decode_seed(password)
1578 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1581 if new_password != new_password2:
1582 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1583 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1586 wallet.update_password(seed, password, new_password)
1588 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1591 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1594 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1595 parent.password_button.setIcon( icon )
1599 def generate_transaction_information_widget(self, tx):
1600 tabs = QTabWidget(self)
1603 grid_ui = QGridLayout(tab1)
1604 grid_ui.setColumnStretch(0,1)
1605 tabs.addTab(tab1, _('Outputs') )
1607 tree_widget = MyTreeWidget(self)
1608 tree_widget.setColumnCount(2)
1609 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1610 tree_widget.setColumnWidth(0, 300)
1611 tree_widget.setColumnWidth(1, 50)
1613 for address, value in tx.outputs:
1614 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1615 tree_widget.addTopLevelItem(item)
1617 tree_widget.setMaximumHeight(100)
1619 grid_ui.addWidget(tree_widget)
1622 grid_ui = QGridLayout(tab2)
1623 grid_ui.setColumnStretch(0,1)
1624 tabs.addTab(tab2, _('Inputs') )
1626 tree_widget = MyTreeWidget(self)
1627 tree_widget.setColumnCount(2)
1628 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1630 for input_line in tx.inputs:
1631 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1632 tree_widget.addTopLevelItem(item)
1634 tree_widget.setMaximumHeight(100)
1636 grid_ui.addWidget(tree_widget)
1640 def tx_dict_from_text(self, txt):
1642 tx_dict = json.loads(str(txt))
1643 assert "hex" in tx_dict.keys()
1644 assert "complete" in tx_dict.keys()
1645 if not tx_dict["complete"]:
1646 assert "input_info" in tx_dict.keys()
1648 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1653 def read_tx_from_file(self):
1654 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1658 with open(fileName, "r") as f:
1659 file_content = f.read()
1660 except (ValueError, IOError, os.error), reason:
1661 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1663 return self.tx_dict_from_text(file_content)
1667 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1669 self.wallet.signrawtransaction(tx, input_info, [], password)
1671 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1673 with open(fileName, "w+") as f:
1674 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1675 self.show_message(_("Transaction saved successfully"))
1678 except BaseException, e:
1679 self.show_message(str(e))
1682 def send_raw_transaction(self, raw_tx, dialog = ""):
1683 result, result_message = self.wallet.sendtx( raw_tx )
1685 self.show_message("Transaction successfully sent: %s" % (result_message))
1689 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1691 def do_process_from_text(self):
1692 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1695 tx_dict = self.tx_dict_from_text(text)
1697 self.create_process_transaction_window(tx_dict)
1699 def do_process_from_file(self):
1700 tx_dict = self.read_tx_from_file()
1702 self.create_process_transaction_window(tx_dict)
1704 def create_process_transaction_window(self, tx_dict):
1705 tx = Transaction(tx_dict["hex"])
1707 dialog = QDialog(self)
1708 dialog.setMinimumWidth(500)
1709 dialog.setWindowTitle(_('Process raw transaction'))
1715 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1716 l.addWidget(QLabel(_("Actions")), 4,0)
1718 if tx_dict["complete"] == False:
1719 l.addWidget(QLabel(_("Unsigned")), 3,1)
1720 if self.wallet.seed :
1721 b = QPushButton("Sign transaction")
1722 input_info = json.loads(tx_dict["input_info"])
1723 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1724 l.addWidget(b, 4, 1)
1726 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1728 l.addWidget(QLabel(_("Signed")), 3,1)
1729 b = QPushButton("Broadcast transaction")
1730 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1733 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1734 cancelButton = QPushButton(_("Cancel"))
1735 cancelButton.clicked.connect(lambda: dialog.done(0))
1736 l.addWidget(cancelButton, 4,2)
1742 def do_export_privkeys(self, password):
1743 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.")))
1746 select_export = _('Select file to export your private keys to')
1747 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1749 with open(fileName, "w+") as csvfile:
1750 transaction = csv.writer(csvfile)
1751 transaction.writerow(["address", "private_key"])
1754 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1755 transaction.writerow(["%34s"%addr,pk])
1757 self.show_message(_("Private keys exported."))
1759 except (IOError, os.error), reason:
1760 export_error_label = _("Electrum was unable to produce a private key-export.")
1761 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1763 except BaseException, e:
1764 self.show_message(str(e))
1768 def do_import_labels(self):
1769 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1770 if not labelsFile: return
1772 f = open(labelsFile, 'r')
1775 for key, value in json.loads(data).items():
1776 self.wallet.labels[key] = value
1778 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1779 except (IOError, os.error), reason:
1780 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1783 def do_export_labels(self):
1784 labels = self.wallet.labels
1786 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1788 with open(fileName, 'w+') as f:
1789 json.dump(labels, f)
1790 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1791 except (IOError, os.error), reason:
1792 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1795 def do_export_history(self):
1796 from gui_lite import csv_transaction
1797 csv_transaction(self.wallet)
1801 def do_import_privkey(self, password):
1802 if not self.wallet.imported_keys:
1803 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1804 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1805 + _('Are you sure you understand what you are doing?'), 3, 4)
1808 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1811 text = str(text).split()
1816 addr = self.wallet.import_key(key, password)
1817 except BaseException as e:
1823 addrlist.append(addr)
1825 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1827 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1828 self.update_receive_tab()
1829 self.update_history_tab()
1832 def settings_dialog(self):
1834 d.setWindowTitle(_('Electrum Settings'))
1836 vbox = QVBoxLayout()
1838 tabs = QTabWidget(self)
1839 self.settings_tab = tabs
1840 vbox.addWidget(tabs)
1843 grid_ui = QGridLayout(tab1)
1844 grid_ui.setColumnStretch(0,1)
1845 tabs.addTab(tab1, _('Display') )
1847 nz_label = QLabel(_('Display zeros'))
1848 grid_ui.addWidget(nz_label, 0, 0)
1849 nz_e = AmountEdit(None,True)
1850 nz_e.setText("%d"% self.wallet.num_zeros)
1851 grid_ui.addWidget(nz_e, 0, 1)
1852 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1853 grid_ui.addWidget(HelpButton(msg), 0, 2)
1854 if not self.config.is_modifiable('num_zeros'):
1855 for w in [nz_e, nz_label]: w.setEnabled(False)
1857 lang_label=QLabel(_('Language') + ':')
1858 grid_ui.addWidget(lang_label, 1, 0)
1859 lang_combo = QComboBox()
1860 from i18n import languages
1861 lang_combo.addItems(languages.values())
1863 index = languages.keys().index(self.config.get("language",''))
1866 lang_combo.setCurrentIndex(index)
1867 grid_ui.addWidget(lang_combo, 1, 1)
1868 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1869 if not self.config.is_modifiable('language'):
1870 for w in [lang_combo, lang_label]: w.setEnabled(False)
1872 currencies = self.exchanger.get_currencies()
1873 currencies.insert(0, "None")
1875 cur_label=QLabel(_('Currency') + ':')
1876 grid_ui.addWidget(cur_label , 2, 0)
1877 cur_combo = QComboBox()
1878 cur_combo.addItems(currencies)
1880 index = currencies.index(self.config.get('currency', "None"))
1883 cur_combo.setCurrentIndex(index)
1884 grid_ui.addWidget(cur_combo, 2, 1)
1885 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1887 expert_cb = QCheckBox(_('Expert mode'))
1888 expert_cb.setChecked(self.expert_mode)
1889 grid_ui.addWidget(expert_cb, 3, 0)
1890 hh = _('In expert mode, your client will:') + '\n' \
1891 + _(' - Show change addresses in the Receive tab') + '\n' \
1892 + _(' - Display the balance of each address') + '\n' \
1893 + _(' - Add freeze/prioritize actions to addresses.')
1894 grid_ui.addWidget(HelpButton(hh), 3, 2)
1895 grid_ui.setRowStretch(4,1)
1899 grid_wallet = QGridLayout(tab2)
1900 grid_wallet.setColumnStretch(0,1)
1901 tabs.addTab(tab2, _('Wallet') )
1903 fee_label = QLabel(_('Transaction fee'))
1904 grid_wallet.addWidget(fee_label, 0, 0)
1905 fee_e = AmountEdit(self.base_unit)
1906 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1907 grid_wallet.addWidget(fee_e, 0, 2)
1908 msg = _('Fee per kilobyte of transaction.') + ' ' \
1909 + _('Recommended value') + ': ' + self.format_amount(20000)
1910 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1911 if not self.config.is_modifiable('fee_per_kb'):
1912 for w in [fee_e, fee_label]: w.setEnabled(False)
1914 usechange_cb = QCheckBox(_('Use change addresses'))
1915 usechange_cb.setChecked(self.wallet.use_change)
1916 grid_wallet.addWidget(usechange_cb, 1, 0)
1917 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1918 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1920 gap_label = QLabel(_('Gap limit'))
1921 grid_wallet.addWidget(gap_label, 2, 0)
1922 gap_e = AmountEdit(None,True)
1923 gap_e.setText("%d"% self.wallet.gap_limit)
1924 grid_wallet.addWidget(gap_e, 2, 2)
1925 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1926 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1927 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1928 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1929 + _('Warning') + ': ' \
1930 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1931 + _('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'
1932 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1933 if not self.config.is_modifiable('gap_limit'):
1934 for w in [gap_e, gap_label]: w.setEnabled(False)
1936 units = ['BTC', 'mBTC']
1937 unit_label = QLabel(_('Base unit'))
1938 grid_wallet.addWidget(unit_label, 3, 0)
1939 unit_combo = QComboBox()
1940 unit_combo.addItems(units)
1941 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1942 grid_wallet.addWidget(unit_combo, 3, 2)
1943 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1944 + '\n1BTC=1000mBTC.\n' \
1945 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1946 grid_wallet.setRowStretch(4,1)
1951 grid_io = QGridLayout(tab3)
1952 grid_io.setColumnStretch(0,1)
1953 tabs.addTab(tab3, _('Import/Export') )
1955 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1956 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1957 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1958 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1960 grid_io.addWidget(QLabel(_('History')), 2, 0)
1961 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1962 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1964 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1966 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1967 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1968 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1970 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1971 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1972 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1973 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1974 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1977 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1978 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1979 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1980 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1982 grid_io.setRowStretch(6,1)
1987 tab5 = QScrollArea()
1988 tab5.setEnabled(True)
1989 tab5.setWidgetResizable(True)
1991 grid_plugins = QGridLayout()
1992 grid_plugins.setColumnStretch(0,1)
1995 w.setLayout(grid_plugins)
1997 tab5.setMaximumSize(tab3.size()) # optional
1999 w.setMinimumHeight(len(self.plugins)*35)
2001 tabs.addTab(tab5, _('Plugins') )
2002 def mk_toggle(cb, p):
2003 return lambda: cb.setChecked(p.toggle())
2004 for i, p in enumerate(self.plugins):
2006 name, description = p.get_info()
2007 cb = QCheckBox(name)
2008 cb.setDisabled(not p.is_available())
2009 cb.setChecked(p.is_enabled())
2010 cb.clicked.connect(mk_toggle(cb,p))
2011 grid_plugins.addWidget(cb, i, 0)
2012 if p.requires_settings():
2013 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2014 grid_plugins.addWidget(HelpButton(description), i, 2)
2016 print_msg("Error: cannot display plugin", p)
2017 traceback.print_exc(file=sys.stdout)
2018 grid_plugins.setRowStretch(i+1,1)
2020 self.run_hook('create_settings_tab', tabs)
2022 vbox.addLayout(ok_cancel_buttons(d))
2026 if not d.exec_(): return
2028 fee = unicode(fee_e.text())
2030 fee = self.read_amount(fee)
2032 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2035 if self.wallet.fee != fee:
2036 self.wallet.fee = fee
2039 nz = unicode(nz_e.text())
2044 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2047 if self.wallet.num_zeros != nz:
2048 self.wallet.num_zeros = nz
2049 self.config.set_key('num_zeros', nz, True)
2050 self.update_history_tab()
2051 self.update_receive_tab()
2053 usechange_result = usechange_cb.isChecked()
2054 if self.wallet.use_change != usechange_result:
2055 self.wallet.use_change = usechange_result
2056 self.config.set_key('use_change', self.wallet.use_change, True)
2058 unit_result = units[unit_combo.currentIndex()]
2059 if self.base_unit() != unit_result:
2060 self.decimal_point = 8 if unit_result == 'BTC' else 5
2061 self.config.set_key('decimal_point', self.decimal_point, True)
2062 self.update_history_tab()
2063 self.update_status()
2066 n = int(gap_e.text())
2068 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2071 if self.wallet.gap_limit != n:
2072 r = self.wallet.change_gap_limit(n)
2074 self.update_receive_tab()
2075 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2077 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2079 need_restart = False
2081 lang_request = languages.keys()[lang_combo.currentIndex()]
2082 if lang_request != self.config.get('language'):
2083 self.config.set_key("language", lang_request, True)
2086 cur_request = str(currencies[cur_combo.currentIndex()])
2087 if cur_request != self.config.get('currency', "None"):
2088 self.config.set_key('currency', cur_request, True)
2089 self.update_wallet()
2091 self.run_hook('close_settings_dialog')
2094 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2096 self.receive_tab_set_mode(expert_cb.isChecked())
2098 def run_network_dialog(self):
2099 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2101 def closeEvent(self, event):
2103 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2104 self.save_column_widths()
2105 self.config.set_key("console-history",self.console.history[-50:])
2114 def __init__(self, wallet, config, app=None):
2115 self.wallet = wallet
2116 self.config = config
2118 self.app = QApplication(sys.argv)
2121 def restore_or_create(self):
2122 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2123 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2124 if r==2: return None
2125 return 'restore' if r==1 else 'create'
2128 def verify_seed(self):
2129 r = self.seed_dialog(False)
2130 if r != self.wallet.seed:
2131 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2138 def seed_dialog(self, is_restore=True):
2142 vbox = QVBoxLayout()
2144 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2146 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2148 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2151 label.setWordWrap(True)
2152 vbox.addWidget(label)
2154 seed_e = QTextEdit()
2155 seed_e.setMaximumHeight(100)
2156 vbox.addWidget(seed_e)
2159 grid = QGridLayout()
2161 gap_e = AmountEdit(None, True)
2163 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2164 grid.addWidget(gap_e, 2, 1)
2165 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2166 vbox.addLayout(grid)
2168 vbox.addLayout(ok_cancel_buttons(d))
2171 if not d.exec_(): return
2174 seed = str(seed_e.toPlainText())
2178 seed = mnemonic.mn_decode( seed.split() )
2180 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2184 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2191 gap = int(unicode(gap_e.text()))
2193 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2198 def network_dialog(self):
2199 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2202 def show_seed(self):
2203 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2205 def password_dialog(self):
2206 if self.wallet.seed:
2207 ElectrumWindow.change_password_dialog(self.wallet)
2210 def restore_wallet(self):
2211 wallet = self.wallet
2212 # wait until we are connected, because the user might have selected another server
2213 if not wallet.interface.is_connected:
2214 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2215 waiting_dialog(waiting)
2217 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2218 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2220 wallet.set_up_to_date(False)
2221 wallet.interface.poke('synchronizer')
2222 waiting_dialog(waiting)
2223 if wallet.is_found():
2224 print_error( "Recovery successful" )
2226 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2233 w = ElectrumWindow(self.wallet, self.config)
2234 if url: w.set_url(url)