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 self.wallet.delete_contact(x)
1020 self.set_label(x, None)
1021 self.update_history_tab()
1022 self.update_contacts_tab()
1023 self.update_completions()
1026 def create_contact_menu(self, position):
1027 item = self.contacts_list.itemAt(position)
1029 addr = unicode(item.text(0))
1030 label = unicode(item.text(1))
1031 is_editable = item.data(0,32).toBool()
1032 payto_addr = item.data(0,33).toString()
1034 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1035 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1036 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1038 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1039 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1041 self.run_hook('create_contact_menu', menu, item)
1042 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1045 def update_receive_item(self, item):
1046 item.setFont(0, QFont(MONOSPACE_FONT))
1047 address = str(item.data(0,0).toString())
1048 label = self.wallet.labels.get(address,'')
1049 item.setData(1,0,label)
1050 item.setData(0,32, True) # is editable
1052 self.run_hook('update_receive_item', address, item)
1054 c, u = self.wallet.get_addr_balance(address)
1055 balance = self.format_amount(c + u)
1056 item.setData(2,0,balance)
1058 if self.expert_mode:
1059 if address in self.wallet.frozen_addresses:
1060 item.setBackgroundColor(0, QColor('lightblue'))
1061 elif address in self.wallet.prioritized_addresses:
1062 item.setBackgroundColor(0, QColor('lightgreen'))
1065 def update_receive_tab(self):
1066 l = self.receive_list
1069 l.setColumnHidden(2, not self.expert_mode)
1070 l.setColumnHidden(3, not self.expert_mode)
1071 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1072 l.setColumnWidth(i, width)
1074 if self.current_account is None:
1075 account_items = self.wallet.accounts.items()
1076 elif self.current_account != -1:
1077 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1081 for k, account in account_items:
1082 name = account.get('name',str(k))
1083 c,u = self.wallet.get_account_balance(k)
1084 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1085 l.addTopLevelItem(account_item)
1086 account_item.setExpanded(True)
1088 for is_change in ([0,1] if self.expert_mode else [0]):
1089 if self.expert_mode:
1090 name = "Receiving" if not is_change else "Change"
1091 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1092 account_item.addChild(seq_item)
1093 if not is_change: seq_item.setExpanded(True)
1095 seq_item = account_item
1099 for address in account[is_change]:
1100 h = self.wallet.history.get(address,[])
1104 if gap > self.wallet.gap_limit:
1109 num_tx = '*' if h == ['*'] else "%d"%len(h)
1110 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1111 self.update_receive_item(item)
1113 item.setBackgroundColor(1, QColor('red'))
1114 seq_item.addChild(item)
1117 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1118 c,u = self.wallet.get_imported_balance()
1119 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1120 l.addTopLevelItem(account_item)
1121 account_item.setExpanded(True)
1122 for address in self.wallet.imported_keys.keys():
1123 item = QTreeWidgetItem( [ address, '', '', ''] )
1124 self.update_receive_item(item)
1125 account_item.addChild(item)
1128 # we use column 1 because column 0 may be hidden
1129 l.setCurrentItem(l.topLevelItem(0),1)
1132 def update_contacts_tab(self):
1134 l = self.contacts_list
1137 for address in self.wallet.addressbook:
1138 label = self.wallet.labels.get(address,'')
1139 n = self.wallet.get_num_tx(address)
1140 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1141 item.setFont(0, QFont(MONOSPACE_FONT))
1142 # 32 = label can be edited (bool)
1143 item.setData(0,32, True)
1145 item.setData(0,33, address)
1146 l.addTopLevelItem(item)
1148 self.run_hook('update_contacts_tab', l)
1149 l.setCurrentItem(l.topLevelItem(0))
1153 def create_console_tab(self):
1154 from qt_console import Console
1155 self.console = console = Console()
1156 self.console.history = self.config.get("console-history",[])
1157 self.console.history_index = len(self.console.history)
1159 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1160 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1162 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1164 def mkfunc(f, method):
1165 return lambda *args: apply( f, (method, args, self.password_dialog ))
1167 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1168 methods[m] = mkfunc(c._run, m)
1170 console.updateNamespace(methods)
1173 def change_account(self,s):
1174 if s == _("All accounts"):
1175 self.current_account = None
1177 accounts = self.wallet.get_accounts()
1178 for k, v in accounts.items():
1180 self.current_account = k
1181 self.update_history_tab()
1182 self.update_status()
1183 self.update_receive_tab()
1185 def create_status_bar(self):
1186 self.status_text = ""
1188 sb.setFixedHeight(35)
1189 qtVersion = qVersion()
1191 update_notification = UpdateLabel(self.config)
1192 if(update_notification.new_version):
1193 sb.addPermanentWidget(update_notification)
1195 accounts = self.wallet.get_accounts()
1196 if len(accounts) > 1:
1197 from_combo = QComboBox()
1198 from_combo.addItems([_("All accounts")] + accounts.values())
1199 from_combo.setCurrentIndex(0)
1200 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1201 sb.addPermanentWidget(from_combo)
1203 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1204 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1205 if self.wallet.seed:
1206 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1207 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1208 sb.addPermanentWidget( self.password_button )
1209 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1210 if self.wallet.seed:
1211 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1212 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1213 sb.addPermanentWidget( self.status_button )
1215 self.run_hook('create_status_bar', (sb,))
1217 self.setStatusBar(sb)
1221 self.config.set_key('gui', 'lite', True)
1224 self.lite.mini.show()
1226 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1227 self.lite.main(None)
1229 def new_contact_dialog(self):
1230 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1231 address = unicode(text)
1233 if is_valid(address):
1234 self.wallet.add_contact(address)
1235 self.update_contacts_tab()
1236 self.update_history_tab()
1237 self.update_completions()
1239 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1241 def show_master_public_key(self):
1242 dialog = QDialog(self)
1244 dialog.setWindowTitle(_("Master Public Key"))
1246 main_text = QTextEdit()
1247 main_text.setText(self.wallet.get_master_public_key())
1248 main_text.setReadOnly(True)
1249 main_text.setMaximumHeight(170)
1250 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1252 ok_button = QPushButton(_("OK"))
1253 ok_button.setDefault(True)
1254 ok_button.clicked.connect(dialog.accept)
1256 main_layout = QGridLayout()
1257 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1259 main_layout.addWidget(main_text, 1, 0)
1260 main_layout.addWidget(qrw, 1, 1 )
1262 vbox = QVBoxLayout()
1263 vbox.addLayout(main_layout)
1264 hbox = QHBoxLayout()
1266 hbox.addWidget(ok_button)
1267 vbox.addLayout(hbox)
1269 dialog.setLayout(vbox)
1274 def show_seed_dialog(self, password):
1275 if not self.wallet.seed:
1276 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1279 seed = self.wallet.decode_seed(password)
1281 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1283 self.show_seed(seed, self.wallet.imported_keys, self)
1287 def show_seed(self, seed, imported_keys, parent=None):
1288 dialog = QDialog(parent)
1290 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1292 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1294 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1296 seed_text = QTextEdit(brainwallet)
1297 seed_text.setReadOnly(True)
1298 seed_text.setMaximumHeight(130)
1300 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1301 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1302 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1303 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1305 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1306 label2 = QLabel(msg2)
1307 label2.setWordWrap(True)
1310 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1311 logo.setMaximumWidth(60)
1313 qrw = QRCodeWidget(seed)
1315 ok_button = QPushButton(_("OK"))
1316 ok_button.setDefault(True)
1317 ok_button.clicked.connect(dialog.accept)
1319 grid = QGridLayout()
1320 #main_layout.addWidget(logo, 0, 0)
1322 grid.addWidget(logo, 0, 0)
1323 grid.addWidget(label1, 0, 1)
1325 grid.addWidget(seed_text, 1, 0, 1, 2)
1327 grid.addWidget(qrw, 0, 2, 2, 1)
1329 vbox = QVBoxLayout()
1330 vbox.addLayout(grid)
1331 vbox.addWidget(label2)
1333 hbox = QHBoxLayout()
1335 hbox.addWidget(ok_button)
1336 vbox.addLayout(hbox)
1338 dialog.setLayout(vbox)
1341 def show_qrcode(self, data, title = "QR code"):
1345 d.setWindowTitle(title)
1346 d.setMinimumSize(270, 300)
1347 vbox = QVBoxLayout()
1348 qrw = QRCodeWidget(data)
1349 vbox.addWidget(qrw, 1)
1350 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1351 hbox = QHBoxLayout()
1355 filename = "qrcode.bmp"
1356 bmp.save_qrcode(qrw.qr, filename)
1357 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1359 b = QPushButton(_("Save"))
1361 b.clicked.connect(print_qr)
1363 b = QPushButton(_("Close"))
1365 b.clicked.connect(d.accept)
1368 vbox.addLayout(hbox)
1373 def do_protect(self, func, args):
1374 if self.wallet.use_encryption:
1375 password = self.password_dialog()
1381 if args != (False,):
1382 args = (self,) + args + (password,)
1384 args = (self,password)
1389 def show_private_key(self, address, password):
1390 if not address: return
1392 pk = self.wallet.get_private_key(address, password)
1393 except BaseException, e:
1394 self.show_message(str(e))
1396 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1400 def do_sign(self, address, message, signature, password):
1402 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1403 signature.setText(sig)
1404 except BaseException, e:
1405 self.show_message(str(e))
1407 def sign_message(self, address):
1408 if not address: return
1411 d.setWindowTitle(_('Sign Message'))
1412 d.setMinimumSize(410, 290)
1414 tab_widget = QTabWidget()
1416 layout = QGridLayout(tab)
1418 sign_address = QLineEdit()
1420 sign_address.setText(address)
1421 layout.addWidget(QLabel(_('Address')), 1, 0)
1422 layout.addWidget(sign_address, 1, 1)
1424 sign_message = QTextEdit()
1425 layout.addWidget(QLabel(_('Message')), 2, 0)
1426 layout.addWidget(sign_message, 2, 1)
1427 layout.setRowStretch(2,3)
1429 sign_signature = QTextEdit()
1430 layout.addWidget(QLabel(_('Signature')), 3, 0)
1431 layout.addWidget(sign_signature, 3, 1)
1432 layout.setRowStretch(3,1)
1435 hbox = QHBoxLayout()
1436 b = QPushButton(_("Sign"))
1438 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1439 b = QPushButton(_("Close"))
1440 b.clicked.connect(d.accept)
1442 layout.addLayout(hbox, 4, 1)
1443 tab_widget.addTab(tab, _("Sign"))
1447 layout = QGridLayout(tab)
1449 verify_address = QLineEdit()
1450 layout.addWidget(QLabel(_('Address')), 1, 0)
1451 layout.addWidget(verify_address, 1, 1)
1453 verify_message = QTextEdit()
1454 layout.addWidget(QLabel(_('Message')), 2, 0)
1455 layout.addWidget(verify_message, 2, 1)
1456 layout.setRowStretch(2,3)
1458 verify_signature = QTextEdit()
1459 layout.addWidget(QLabel(_('Signature')), 3, 0)
1460 layout.addWidget(verify_signature, 3, 1)
1461 layout.setRowStretch(3,1)
1464 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1465 self.show_message(_("Signature verified"))
1467 self.show_message(_("Error: wrong signature"))
1469 hbox = QHBoxLayout()
1470 b = QPushButton(_("Verify"))
1471 b.clicked.connect(do_verify)
1473 b = QPushButton(_("Close"))
1474 b.clicked.connect(d.accept)
1476 layout.addLayout(hbox, 4, 1)
1477 tab_widget.addTab(tab, _("Verify"))
1479 vbox = QVBoxLayout()
1480 vbox.addWidget(tab_widget)
1487 def question(self, msg):
1488 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1490 def show_message(self, msg):
1491 QMessageBox.information(self, _('Message'), msg, _('OK'))
1493 def password_dialog(self ):
1500 vbox = QVBoxLayout()
1501 msg = _('Please enter your password')
1502 vbox.addWidget(QLabel(msg))
1504 grid = QGridLayout()
1506 grid.addWidget(QLabel(_('Password')), 1, 0)
1507 grid.addWidget(pw, 1, 1)
1508 vbox.addLayout(grid)
1510 vbox.addLayout(ok_cancel_buttons(d))
1513 self.run_hook('password_dialog', pw, grid, 1)
1514 if not d.exec_(): return
1515 return unicode(pw.text())
1522 def change_password_dialog( wallet, parent=None ):
1525 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1533 new_pw = QLineEdit()
1534 new_pw.setEchoMode(2)
1535 conf_pw = QLineEdit()
1536 conf_pw.setEchoMode(2)
1538 vbox = QVBoxLayout()
1540 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1541 +_('To disable wallet encryption, enter an empty new password.')) \
1542 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1544 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1545 +_("Leave these fields empty if you want to disable encryption.")
1546 vbox.addWidget(QLabel(msg))
1548 grid = QGridLayout()
1551 if wallet.use_encryption:
1552 grid.addWidget(QLabel(_('Password')), 1, 0)
1553 grid.addWidget(pw, 1, 1)
1555 grid.addWidget(QLabel(_('New Password')), 2, 0)
1556 grid.addWidget(new_pw, 2, 1)
1558 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1559 grid.addWidget(conf_pw, 3, 1)
1560 vbox.addLayout(grid)
1562 vbox.addLayout(ok_cancel_buttons(d))
1565 if not d.exec_(): return
1567 password = unicode(pw.text()) if wallet.use_encryption else None
1568 new_password = unicode(new_pw.text())
1569 new_password2 = unicode(conf_pw.text())
1572 seed = wallet.decode_seed(password)
1574 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1577 if new_password != new_password2:
1578 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1579 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1582 wallet.update_password(seed, password, new_password)
1584 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1587 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1590 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1591 parent.password_button.setIcon( icon )
1595 def generate_transaction_information_widget(self, tx):
1596 tabs = QTabWidget(self)
1599 grid_ui = QGridLayout(tab1)
1600 grid_ui.setColumnStretch(0,1)
1601 tabs.addTab(tab1, _('Outputs') )
1603 tree_widget = MyTreeWidget(self)
1604 tree_widget.setColumnCount(2)
1605 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1606 tree_widget.setColumnWidth(0, 300)
1607 tree_widget.setColumnWidth(1, 50)
1609 for address, value in tx.outputs:
1610 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1611 tree_widget.addTopLevelItem(item)
1613 tree_widget.setMaximumHeight(100)
1615 grid_ui.addWidget(tree_widget)
1618 grid_ui = QGridLayout(tab2)
1619 grid_ui.setColumnStretch(0,1)
1620 tabs.addTab(tab2, _('Inputs') )
1622 tree_widget = MyTreeWidget(self)
1623 tree_widget.setColumnCount(2)
1624 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1626 for input_line in tx.inputs:
1627 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1628 tree_widget.addTopLevelItem(item)
1630 tree_widget.setMaximumHeight(100)
1632 grid_ui.addWidget(tree_widget)
1636 def tx_dict_from_text(self, txt):
1638 tx_dict = json.loads(str(txt))
1639 assert "hex" in tx_dict.keys()
1640 assert "complete" in tx_dict.keys()
1641 if not tx_dict["complete"]:
1642 assert "input_info" in tx_dict.keys()
1644 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1649 def read_tx_from_file(self):
1650 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1654 with open(fileName, "r") as f:
1655 file_content = f.read()
1656 except (ValueError, IOError, os.error), reason:
1657 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1659 return self.tx_dict_from_text(file_content)
1663 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1665 self.wallet.signrawtransaction(tx, input_info, [], password)
1667 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1669 with open(fileName, "w+") as f:
1670 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1671 self.show_message(_("Transaction saved successfully"))
1674 except BaseException, e:
1675 self.show_message(str(e))
1678 def send_raw_transaction(self, raw_tx, dialog = ""):
1679 result, result_message = self.wallet.sendtx( raw_tx )
1681 self.show_message("Transaction successfully sent: %s" % (result_message))
1685 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1687 def do_process_from_text(self):
1688 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1691 tx_dict = self.tx_dict_from_text(text)
1693 self.create_process_transaction_window(tx_dict)
1695 def do_process_from_file(self):
1696 tx_dict = self.read_tx_from_file()
1698 self.create_process_transaction_window(tx_dict)
1700 def create_process_transaction_window(self, tx_dict):
1701 tx = Transaction(tx_dict["hex"])
1703 dialog = QDialog(self)
1704 dialog.setMinimumWidth(500)
1705 dialog.setWindowTitle(_('Process raw transaction'))
1711 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1712 l.addWidget(QLabel(_("Actions")), 4,0)
1714 if tx_dict["complete"] == False:
1715 l.addWidget(QLabel(_("Unsigned")), 3,1)
1716 if self.wallet.seed :
1717 b = QPushButton("Sign transaction")
1718 input_info = json.loads(tx_dict["input_info"])
1719 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1720 l.addWidget(b, 4, 1)
1722 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1724 l.addWidget(QLabel(_("Signed")), 3,1)
1725 b = QPushButton("Broadcast transaction")
1726 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1729 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1730 cancelButton = QPushButton(_("Cancel"))
1731 cancelButton.clicked.connect(lambda: dialog.done(0))
1732 l.addWidget(cancelButton, 4,2)
1738 def do_export_privkeys(self, password):
1739 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.")))
1742 select_export = _('Select file to export your private keys to')
1743 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1745 with open(fileName, "w+") as csvfile:
1746 transaction = csv.writer(csvfile)
1747 transaction.writerow(["address", "private_key"])
1750 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1751 transaction.writerow(["%34s"%addr,pk])
1753 self.show_message(_("Private keys exported."))
1755 except (IOError, os.error), reason:
1756 export_error_label = _("Electrum was unable to produce a private key-export.")
1757 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1759 except BaseException, e:
1760 self.show_message(str(e))
1764 def do_import_labels(self):
1765 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1766 if not labelsFile: return
1768 f = open(labelsFile, 'r')
1771 for key, value in json.loads(data).items():
1772 self.wallet.labels[key] = value
1774 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1775 except (IOError, os.error), reason:
1776 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1779 def do_export_labels(self):
1780 labels = self.wallet.labels
1782 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1784 with open(fileName, 'w+') as f:
1785 json.dump(labels, f)
1786 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1787 except (IOError, os.error), reason:
1788 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1791 def do_export_history(self):
1792 from gui_lite import csv_transaction
1793 csv_transaction(self.wallet)
1797 def do_import_privkey(self, password):
1798 if not self.wallet.imported_keys:
1799 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1800 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1801 + _('Are you sure you understand what you are doing?'), 3, 4)
1804 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1807 text = str(text).split()
1812 addr = self.wallet.import_key(key, password)
1813 except BaseException as e:
1819 addrlist.append(addr)
1821 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1823 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1824 self.update_receive_tab()
1825 self.update_history_tab()
1828 def settings_dialog(self):
1830 d.setWindowTitle(_('Electrum Settings'))
1832 vbox = QVBoxLayout()
1834 tabs = QTabWidget(self)
1835 self.settings_tab = tabs
1836 vbox.addWidget(tabs)
1839 grid_ui = QGridLayout(tab1)
1840 grid_ui.setColumnStretch(0,1)
1841 tabs.addTab(tab1, _('Display') )
1843 nz_label = QLabel(_('Display zeros'))
1844 grid_ui.addWidget(nz_label, 0, 0)
1845 nz_e = AmountEdit(None,True)
1846 nz_e.setText("%d"% self.wallet.num_zeros)
1847 grid_ui.addWidget(nz_e, 0, 1)
1848 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1849 grid_ui.addWidget(HelpButton(msg), 0, 2)
1850 if not self.config.is_modifiable('num_zeros'):
1851 for w in [nz_e, nz_label]: w.setEnabled(False)
1853 lang_label=QLabel(_('Language') + ':')
1854 grid_ui.addWidget(lang_label, 1, 0)
1855 lang_combo = QComboBox()
1856 from i18n import languages
1857 lang_combo.addItems(languages.values())
1859 index = languages.keys().index(self.config.get("language",''))
1862 lang_combo.setCurrentIndex(index)
1863 grid_ui.addWidget(lang_combo, 1, 1)
1864 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1865 if not self.config.is_modifiable('language'):
1866 for w in [lang_combo, lang_label]: w.setEnabled(False)
1868 currencies = self.exchanger.get_currencies()
1869 currencies.insert(0, "None")
1871 cur_label=QLabel(_('Currency') + ':')
1872 grid_ui.addWidget(cur_label , 2, 0)
1873 cur_combo = QComboBox()
1874 cur_combo.addItems(currencies)
1876 index = currencies.index(self.config.get('currency', "None"))
1879 cur_combo.setCurrentIndex(index)
1880 grid_ui.addWidget(cur_combo, 2, 1)
1881 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1883 expert_cb = QCheckBox(_('Expert mode'))
1884 expert_cb.setChecked(self.expert_mode)
1885 grid_ui.addWidget(expert_cb, 3, 0)
1886 hh = _('In expert mode, your client will:') + '\n' \
1887 + _(' - Show change addresses in the Receive tab') + '\n' \
1888 + _(' - Display the balance of each address') + '\n' \
1889 + _(' - Add freeze/prioritize actions to addresses.')
1890 grid_ui.addWidget(HelpButton(hh), 3, 2)
1891 grid_ui.setRowStretch(4,1)
1895 grid_wallet = QGridLayout(tab2)
1896 grid_wallet.setColumnStretch(0,1)
1897 tabs.addTab(tab2, _('Wallet') )
1899 fee_label = QLabel(_('Transaction fee'))
1900 grid_wallet.addWidget(fee_label, 0, 0)
1901 fee_e = AmountEdit(self.base_unit)
1902 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1903 grid_wallet.addWidget(fee_e, 0, 2)
1904 msg = _('Fee per kilobyte of transaction.') + ' ' \
1905 + _('Recommended value') + ': ' + self.format_amount(20000)
1906 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1907 if not self.config.is_modifiable('fee_per_kb'):
1908 for w in [fee_e, fee_label]: w.setEnabled(False)
1910 usechange_cb = QCheckBox(_('Use change addresses'))
1911 usechange_cb.setChecked(self.wallet.use_change)
1912 grid_wallet.addWidget(usechange_cb, 1, 0)
1913 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1914 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1916 gap_label = QLabel(_('Gap limit'))
1917 grid_wallet.addWidget(gap_label, 2, 0)
1918 gap_e = AmountEdit(None,True)
1919 gap_e.setText("%d"% self.wallet.gap_limit)
1920 grid_wallet.addWidget(gap_e, 2, 2)
1921 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1922 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1923 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1924 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1925 + _('Warning') + ': ' \
1926 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1927 + _('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'
1928 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1929 if not self.config.is_modifiable('gap_limit'):
1930 for w in [gap_e, gap_label]: w.setEnabled(False)
1932 units = ['BTC', 'mBTC']
1933 unit_label = QLabel(_('Base unit'))
1934 grid_wallet.addWidget(unit_label, 3, 0)
1935 unit_combo = QComboBox()
1936 unit_combo.addItems(units)
1937 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1938 grid_wallet.addWidget(unit_combo, 3, 2)
1939 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1940 + '\n1BTC=1000mBTC.\n' \
1941 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1942 grid_wallet.setRowStretch(4,1)
1947 grid_io = QGridLayout(tab3)
1948 grid_io.setColumnStretch(0,1)
1949 tabs.addTab(tab3, _('Import/Export') )
1951 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1952 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1953 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1954 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1956 grid_io.addWidget(QLabel(_('History')), 2, 0)
1957 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1958 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1960 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1962 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1963 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1964 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1966 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1967 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1968 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1969 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1970 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1973 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1974 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1975 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1976 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1978 grid_io.setRowStretch(6,1)
1983 tab5 = QScrollArea()
1984 tab5.setEnabled(True)
1985 tab5.setWidgetResizable(True)
1987 grid_plugins = QGridLayout()
1988 grid_plugins.setColumnStretch(0,1)
1991 w.setLayout(grid_plugins)
1993 tab5.setMaximumSize(tab3.size()) # optional
1995 w.setMinimumHeight(len(self.plugins)*35)
1997 tabs.addTab(tab5, _('Plugins') )
1998 def mk_toggle(cb, p):
1999 return lambda: cb.setChecked(p.toggle())
2000 for i, p in enumerate(self.plugins):
2002 name, description = p.get_info()
2003 cb = QCheckBox(name)
2004 cb.setDisabled(not p.is_available())
2005 cb.setChecked(p.is_enabled())
2006 cb.clicked.connect(mk_toggle(cb,p))
2007 grid_plugins.addWidget(cb, i, 0)
2008 if p.requires_settings():
2009 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2010 grid_plugins.addWidget(HelpButton(description), i, 2)
2012 print_msg("Error: cannot display plugin", p)
2013 traceback.print_exc(file=sys.stdout)
2014 grid_plugins.setRowStretch(i+1,1)
2016 self.run_hook('create_settings_tab', tabs)
2018 vbox.addLayout(ok_cancel_buttons(d))
2022 if not d.exec_(): return
2024 fee = unicode(fee_e.text())
2026 fee = self.read_amount(fee)
2028 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2031 self.wallet.set_fee(fee)
2033 nz = unicode(nz_e.text())
2038 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2041 if self.wallet.num_zeros != nz:
2042 self.wallet.num_zeros = nz
2043 self.config.set_key('num_zeros', nz, True)
2044 self.update_history_tab()
2045 self.update_receive_tab()
2047 usechange_result = usechange_cb.isChecked()
2048 if self.wallet.use_change != usechange_result:
2049 self.wallet.use_change = usechange_result
2050 self.config.set_key('use_change', self.wallet.use_change, True)
2052 unit_result = units[unit_combo.currentIndex()]
2053 if self.base_unit() != unit_result:
2054 self.decimal_point = 8 if unit_result == 'BTC' else 5
2055 self.config.set_key('decimal_point', self.decimal_point, True)
2056 self.update_history_tab()
2057 self.update_status()
2060 n = int(gap_e.text())
2062 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2065 if self.wallet.gap_limit != n:
2066 r = self.wallet.change_gap_limit(n)
2068 self.update_receive_tab()
2069 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2071 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2073 need_restart = False
2075 lang_request = languages.keys()[lang_combo.currentIndex()]
2076 if lang_request != self.config.get('language'):
2077 self.config.set_key("language", lang_request, True)
2080 cur_request = str(currencies[cur_combo.currentIndex()])
2081 if cur_request != self.config.get('currency', "None"):
2082 self.config.set_key('currency', cur_request, True)
2083 self.update_wallet()
2085 self.run_hook('close_settings_dialog')
2088 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2090 self.receive_tab_set_mode(expert_cb.isChecked())
2092 def run_network_dialog(self):
2093 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2095 def closeEvent(self, event):
2097 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2098 self.save_column_widths()
2099 self.config.set_key("console-history",self.console.history[-50:])
2108 def __init__(self, wallet, config, app=None):
2109 self.wallet = wallet
2110 self.config = config
2112 self.app = QApplication(sys.argv)
2115 def restore_or_create(self):
2116 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2117 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2118 if r==2: return None
2119 return 'restore' if r==1 else 'create'
2122 def verify_seed(self):
2123 r = self.seed_dialog(False)
2124 if r != self.wallet.seed:
2125 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2132 def seed_dialog(self, is_restore=True):
2136 vbox = QVBoxLayout()
2138 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2140 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2142 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2145 label.setWordWrap(True)
2146 vbox.addWidget(label)
2148 seed_e = QTextEdit()
2149 seed_e.setMaximumHeight(100)
2150 vbox.addWidget(seed_e)
2153 grid = QGridLayout()
2155 gap_e = AmountEdit(None, True)
2157 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2158 grid.addWidget(gap_e, 2, 1)
2159 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2160 vbox.addLayout(grid)
2162 vbox.addLayout(ok_cancel_buttons(d))
2165 if not d.exec_(): return
2168 seed = str(seed_e.toPlainText())
2172 seed = mnemonic.mn_decode( seed.split() )
2174 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2178 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2185 gap = int(unicode(gap_e.text()))
2187 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2192 def network_dialog(self):
2193 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2196 def show_seed(self):
2197 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2199 def password_dialog(self):
2200 if self.wallet.seed:
2201 ElectrumWindow.change_password_dialog(self.wallet)
2204 def restore_wallet(self):
2205 wallet = self.wallet
2206 # wait until we are connected, because the user might have selected another server
2207 if not wallet.interface.is_connected:
2208 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2209 waiting_dialog(waiting)
2211 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2212 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2214 wallet.set_up_to_date(False)
2215 wallet.interface.poke('synchronizer')
2216 waiting_dialog(waiting)
2217 if wallet.is_found():
2218 print_error( "Recovery successful" )
2220 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2227 w = ElectrumWindow(self.wallet, self.config)
2228 if url: w.set_url(url)