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)
299 if self.wallet.fee < 50000:
300 self.wallet.set_fee(50000)
301 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
303 # set initial message
304 self.console.showMessage(self.wallet.interface.banner)
306 # plugins that need to change the GUI do it here
307 self.run_hook('init_gui')
311 def init_plugins(self):
312 import imp, pkgutil, __builtin__
313 if __builtin__.use_local_modules:
314 fp, pathname, description = imp.find_module('plugins')
315 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
316 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
317 imp.load_module('electrum_plugins', fp, pathname, description)
318 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
320 import electrum_plugins
321 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
322 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
327 self.plugins.append( p.Plugin(self) )
329 print_msg("Error:cannot initialize plugin",p)
330 traceback.print_exc(file=sys.stdout)
333 def run_hook(self, name, *args):
334 for p in self.plugins:
335 if not p.is_enabled():
344 print_error("Plugin error")
345 traceback.print_exc(file=sys.stdout)
350 def set_label(self, name, text = None):
352 old_text = self.wallet.labels.get(name)
355 self.wallet.labels[name] = text
359 self.wallet.labels.pop(name)
361 self.run_hook('set_label', name, text, changed)
365 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
366 def getOpenFileName(self, title, filter = None):
367 directory = self.config.get('io_dir', os.path.expanduser('~'))
368 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
369 if fileName and directory != os.path.dirname(fileName):
370 self.config.set_key('io_dir', os.path.dirname(fileName), True)
373 def getSaveFileName(self, title, filename, filter = None):
374 directory = self.config.get('io_dir', os.path.expanduser('~'))
375 path = os.path.join( directory, filename )
376 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
377 if fileName and directory != os.path.dirname(fileName):
378 self.config.set_key('io_dir', os.path.dirname(fileName), True)
384 QMainWindow.close(self)
385 self.run_hook('close_main_window')
387 def connect_slots(self, sender):
388 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
389 self.previous_payto_e=''
391 def timer_actions(self):
392 if self.need_update.is_set():
394 self.need_update.clear()
395 self.run_hook('timer_actions')
397 def format_amount(self, x, is_diff=False):
398 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
400 def read_amount(self, x):
401 if x in['.', '']: return None
402 p = pow(10, self.decimal_point)
403 return int( p * Decimal(x) )
406 assert self.decimal_point in [5,8]
407 return "BTC" if self.decimal_point == 8 else "mBTC"
409 def update_status(self):
410 if self.wallet.interface and self.wallet.interface.is_connected:
411 if not self.wallet.up_to_date:
412 text = _("Synchronizing...")
413 icon = QIcon(":icons/status_waiting.png")
415 c, u = self.wallet.get_account_balance(self.current_account)
416 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
417 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
418 text += self.create_quote_text(Decimal(c+u)/100000000)
419 icon = QIcon(":icons/status_connected.png")
421 text = _("Not connected")
422 icon = QIcon(":icons/status_disconnected.png")
424 self.balance_label.setText(text)
425 self.status_button.setIcon( icon )
427 def update_wallet(self):
429 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
430 self.update_history_tab()
431 self.update_receive_tab()
432 self.update_contacts_tab()
433 self.update_completions()
436 def create_quote_text(self, btc_balance):
437 quote_currency = self.config.get("currency", "None")
438 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
439 if quote_balance is None:
442 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
445 def create_history_tab(self):
446 self.history_list = l = MyTreeWidget(self)
448 for i,width in enumerate(self.column_widths['history']):
449 l.setColumnWidth(i, width)
450 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
451 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
452 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
454 l.setContextMenuPolicy(Qt.CustomContextMenu)
455 l.customContextMenuRequested.connect(self.create_history_menu)
459 def create_history_menu(self, position):
460 self.history_list.selectedIndexes()
461 item = self.history_list.currentItem()
463 tx_hash = str(item.data(0, Qt.UserRole).toString())
464 if not tx_hash: return
466 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
467 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
468 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
469 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
472 def show_tx_details(self, tx):
473 dialog = QDialog(self)
475 dialog.setWindowTitle(_("Transaction Details"))
477 dialog.setLayout(vbox)
478 dialog.setMinimumSize(600,300)
481 if tx_hash in self.wallet.transactions.keys():
482 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
483 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
485 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
491 vbox.addWidget(QLabel("Transaction ID:"))
492 e = QLineEdit(tx_hash)
496 vbox.addWidget(QLabel("Date: %s"%time_str))
497 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
500 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
501 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
503 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
504 vbox.addWidget(QLabel("Transaction fee: unknown"))
506 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
508 vbox.addWidget( self.generate_transaction_information_widget(tx) )
510 ok_button = QPushButton(_("Close"))
511 ok_button.setDefault(True)
512 ok_button.clicked.connect(dialog.accept)
516 hbox.addWidget(ok_button)
520 def tx_label_clicked(self, item, column):
521 if column==2 and item.isSelected():
523 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
524 self.history_list.editItem( item, column )
525 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
528 def tx_label_changed(self, item, column):
532 tx_hash = str(item.data(0, Qt.UserRole).toString())
533 tx = self.wallet.transactions.get(tx_hash)
534 text = unicode( item.text(2) )
535 self.set_label(tx_hash, text)
537 item.setForeground(2, QBrush(QColor('black')))
539 text = self.wallet.get_default_label(tx_hash)
540 item.setText(2, text)
541 item.setForeground(2, QBrush(QColor('gray')))
545 def edit_label(self, is_recv):
546 l = self.receive_list if is_recv else self.contacts_list
547 item = l.currentItem()
548 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
549 l.editItem( item, 1 )
550 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
554 def address_label_clicked(self, item, column, l, column_addr, column_label):
555 if column == column_label and item.isSelected():
556 is_editable = item.data(0, 32).toBool()
559 addr = unicode( item.text(column_addr) )
560 label = unicode( item.text(column_label) )
561 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
562 l.editItem( item, column )
563 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
566 def address_label_changed(self, item, column, l, column_addr, column_label):
567 if column == column_label:
568 addr = unicode( item.text(column_addr) )
569 text = unicode( item.text(column_label) )
570 is_editable = item.data(0, 32).toBool()
574 changed = self.set_label(addr, text)
576 self.update_history_tab()
577 self.update_completions()
579 self.current_item_changed(item)
581 self.run_hook('item_changed', item, column)
584 def current_item_changed(self, a):
585 self.run_hook('current_item_changed', a)
589 def update_history_tab(self):
591 self.history_list.clear()
592 for item in self.wallet.get_tx_history(self.current_account):
593 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
596 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
601 time_str = 'unverified'
602 icon = QIcon(":icons/unconfirmed.png")
605 icon = QIcon(":icons/unconfirmed.png")
607 icon = QIcon(":icons/clock%d.png"%conf)
609 icon = QIcon(":icons/confirmed.png")
611 if value is not None:
612 v_str = self.format_amount(value, True)
616 balance_str = self.format_amount(balance)
619 label, is_default_label = self.wallet.get_label(tx_hash)
621 label = _('Pruned transaction outputs')
622 is_default_label = False
624 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
625 item.setFont(2, QFont(MONOSPACE_FONT))
626 item.setFont(3, QFont(MONOSPACE_FONT))
627 item.setFont(4, QFont(MONOSPACE_FONT))
629 item.setForeground(3, QBrush(QColor("#BC1E1E")))
631 item.setData(0, Qt.UserRole, tx_hash)
632 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
634 item.setForeground(2, QBrush(QColor('grey')))
636 item.setIcon(0, icon)
637 self.history_list.insertTopLevelItem(0,item)
640 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
643 def create_send_tab(self):
648 grid.setColumnMinimumWidth(3,300)
649 grid.setColumnStretch(5,1)
652 self.payto_e = QLineEdit()
653 grid.addWidget(QLabel(_('Pay to')), 1, 0)
654 grid.addWidget(self.payto_e, 1, 1, 1, 3)
656 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)
658 completer = QCompleter()
659 completer.setCaseSensitivity(False)
660 self.payto_e.setCompleter(completer)
661 completer.setModel(self.completions)
663 self.message_e = QLineEdit()
664 grid.addWidget(QLabel(_('Description')), 2, 0)
665 grid.addWidget(self.message_e, 2, 1, 1, 3)
666 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)
668 self.amount_e = AmountEdit(self.base_unit)
669 grid.addWidget(QLabel(_('Amount')), 3, 0)
670 grid.addWidget(self.amount_e, 3, 1, 1, 2)
671 grid.addWidget(HelpButton(
672 _('Amount to be sent.') + '\n\n' \
673 + _('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.') \
674 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
676 self.fee_e = AmountEdit(self.base_unit)
677 grid.addWidget(QLabel(_('Fee')), 4, 0)
678 grid.addWidget(self.fee_e, 4, 1, 1, 2)
679 grid.addWidget(HelpButton(
680 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
681 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
682 + _('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)
685 b = EnterButton(_("Send"), self.do_send)
687 b = EnterButton(_("Create unsigned transaction"), self.do_send)
688 grid.addWidget(b, 6, 1)
690 b = EnterButton(_("Clear"),self.do_clear)
691 grid.addWidget(b, 6, 2)
693 self.payto_sig = QLabel('')
694 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
696 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
697 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
706 def entry_changed( is_fee ):
707 self.funds_error = False
709 if self.amount_e.is_shortcut:
710 self.amount_e.is_shortcut = False
711 c, u = self.wallet.get_account_balance(self.current_account)
712 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
713 fee = self.wallet.estimated_fee(inputs)
715 self.amount_e.setText( self.format_amount(amount) )
716 self.fee_e.setText( self.format_amount( fee ) )
719 amount = self.read_amount(str(self.amount_e.text()))
720 fee = self.read_amount(str(self.fee_e.text()))
722 if not is_fee: fee = None
725 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
727 self.fee_e.setText( self.format_amount( fee ) )
730 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
734 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
735 self.funds_error = True
736 text = _( "Not enough funds" )
737 c, u = self.wallet.get_frozen_balance()
738 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
740 self.statusBar().showMessage(text)
741 self.amount_e.setPalette(palette)
742 self.fee_e.setPalette(palette)
744 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
745 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
747 self.run_hook('create_send_tab', grid)
751 def update_completions(self):
753 for addr,label in self.wallet.labels.items():
754 if addr in self.wallet.addressbook:
755 l.append( label + ' <' + addr + '>')
757 self.run_hook('update_completions', l)
758 self.completions.setStringList(l)
762 return lambda s, *args: s.do_protect(func, args)
766 def do_send(self, password):
768 label = unicode( self.message_e.text() )
769 r = unicode( self.payto_e.text() )
772 # label or alias, with address in brackets
773 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
774 to_address = m.group(2) if m else r
776 if not is_valid(to_address):
777 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
781 amount = self.read_amount(unicode( self.amount_e.text()))
783 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
786 fee = self.read_amount(unicode( self.fee_e.text()))
788 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
792 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
793 except BaseException, e:
794 self.show_message(str(e))
797 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
798 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
801 self.run_hook('send_tx', tx)
804 self.set_label(tx.hash(), label)
807 h = self.wallet.send_tx(tx)
808 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
809 status, msg = self.wallet.receive_tx( h )
811 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
813 self.update_contacts_tab()
815 QMessageBox.warning(self, _('Error'), msg, _('OK'))
817 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
819 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
820 with open(fileName,'w') as f:
821 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
822 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
824 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
829 def set_url(self, url):
830 address, amount, label, message, signature, identity, url = util.parse_url(url)
831 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
833 if label and self.wallet.labels.get(address) != label:
834 if self.question('Give label "%s" to address %s ?'%(label,address)):
835 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
836 self.wallet.addressbook.append(address)
837 self.set_label(address, label)
839 self.run_hook('set_url', url, self.show_message, self.question)
841 self.tabs.setCurrentIndex(1)
842 label = self.wallet.labels.get(address)
843 m_addr = label + ' <'+ address +'>' if label else address
844 self.payto_e.setText(m_addr)
846 self.message_e.setText(message)
847 self.amount_e.setText(amount)
849 self.set_frozen(self.payto_e,True)
850 self.set_frozen(self.amount_e,True)
851 self.set_frozen(self.message_e,True)
852 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
854 self.payto_sig.setVisible(False)
857 self.payto_sig.setVisible(False)
858 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
860 self.set_frozen(e,False)
863 def set_frozen(self,entry,frozen):
865 entry.setReadOnly(True)
866 entry.setFrame(False)
868 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
869 entry.setPalette(palette)
871 entry.setReadOnly(False)
874 palette.setColor(entry.backgroundRole(), QColor('white'))
875 entry.setPalette(palette)
878 def toggle_freeze(self,addr):
880 if addr in self.wallet.frozen_addresses:
881 self.wallet.unfreeze(addr)
883 self.wallet.freeze(addr)
884 self.update_receive_tab()
886 def toggle_priority(self,addr):
888 if addr in self.wallet.prioritized_addresses:
889 self.wallet.unprioritize(addr)
891 self.wallet.prioritize(addr)
892 self.update_receive_tab()
895 def create_list_tab(self, headers):
896 "generic tab creation method"
897 l = MyTreeWidget(self)
898 l.setColumnCount( len(headers) )
899 l.setHeaderLabels( headers )
909 vbox.addWidget(buttons)
914 buttons.setLayout(hbox)
919 def create_receive_tab(self):
920 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
921 l.setContextMenuPolicy(Qt.CustomContextMenu)
922 l.customContextMenuRequested.connect(self.create_receive_menu)
923 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
924 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
925 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
926 self.receive_list = l
927 self.receive_buttons_hbox = hbox
932 def receive_tab_set_mode(self, i):
933 self.save_column_widths()
934 self.expert_mode = (i == 1)
935 self.config.set_key('classic_expert_mode', self.expert_mode, True)
936 self.update_receive_tab()
939 def save_column_widths(self):
940 if not self.expert_mode:
941 widths = [ self.receive_list.columnWidth(0) ]
944 for i in range(self.receive_list.columnCount() -1):
945 widths.append(self.receive_list.columnWidth(i))
946 self.column_widths["receive"][self.expert_mode] = widths
948 self.column_widths["history"] = []
949 for i in range(self.history_list.columnCount() - 1):
950 self.column_widths["history"].append(self.history_list.columnWidth(i))
952 self.column_widths["contacts"] = []
953 for i in range(self.contacts_list.columnCount() - 1):
954 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
956 self.config.set_key("column_widths", self.column_widths, True)
959 def create_contacts_tab(self):
960 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
961 l.setContextMenuPolicy(Qt.CustomContextMenu)
962 l.customContextMenuRequested.connect(self.create_contact_menu)
963 for i,width in enumerate(self.column_widths['contacts']):
964 l.setColumnWidth(i, width)
966 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
967 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
968 self.contacts_list = l
969 self.contacts_buttons_hbox = hbox
970 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
975 def delete_imported_key(self, addr):
976 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
977 self.wallet.delete_imported_key(addr)
978 self.update_receive_tab()
979 self.update_history_tab()
982 def create_receive_menu(self, position):
983 # fixme: this function apparently has a side effect.
984 # if it is not called the menu pops up several times
985 #self.receive_list.selectedIndexes()
987 item = self.receive_list.itemAt(position)
989 addr = unicode(item.text(0))
990 if not is_valid(addr):
991 item.setExpanded(not item.isExpanded())
994 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
995 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
996 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
997 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
998 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
999 if addr in self.wallet.imported_keys:
1000 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1002 if self.expert_mode:
1003 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1004 menu.addAction(t, lambda: self.toggle_freeze(addr))
1005 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1006 menu.addAction(t, lambda: self.toggle_priority(addr))
1008 self.run_hook('receive_menu', menu)
1009 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1012 def payto(self, addr):
1014 label = self.wallet.labels.get(addr)
1015 m_addr = label + ' <' + addr + '>' if label else addr
1016 self.tabs.setCurrentIndex(1)
1017 self.payto_e.setText(m_addr)
1018 self.amount_e.setFocus()
1021 def delete_contact(self, x):
1022 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1023 self.wallet.delete_contact(x)
1024 self.set_label(x, None)
1025 self.update_history_tab()
1026 self.update_contacts_tab()
1027 self.update_completions()
1030 def create_contact_menu(self, position):
1031 item = self.contacts_list.itemAt(position)
1033 addr = unicode(item.text(0))
1034 label = unicode(item.text(1))
1035 is_editable = item.data(0,32).toBool()
1036 payto_addr = item.data(0,33).toString()
1038 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1039 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1040 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1042 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1043 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1045 self.run_hook('create_contact_menu', menu, item)
1046 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1049 def update_receive_item(self, item):
1050 item.setFont(0, QFont(MONOSPACE_FONT))
1051 address = str(item.data(0,0).toString())
1052 label = self.wallet.labels.get(address,'')
1053 item.setData(1,0,label)
1054 item.setData(0,32, True) # is editable
1056 self.run_hook('update_receive_item', address, item)
1058 c, u = self.wallet.get_addr_balance(address)
1059 balance = self.format_amount(c + u)
1060 item.setData(2,0,balance)
1062 if self.expert_mode:
1063 if address in self.wallet.frozen_addresses:
1064 item.setBackgroundColor(0, QColor('lightblue'))
1065 elif address in self.wallet.prioritized_addresses:
1066 item.setBackgroundColor(0, QColor('lightgreen'))
1069 def update_receive_tab(self):
1070 l = self.receive_list
1073 l.setColumnHidden(2, not self.expert_mode)
1074 l.setColumnHidden(3, not self.expert_mode)
1075 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1076 l.setColumnWidth(i, width)
1078 if self.current_account is None:
1079 account_items = self.wallet.accounts.items()
1080 elif self.current_account != -1:
1081 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1085 for k, account in account_items:
1086 name = account.get('name',str(k))
1087 c,u = self.wallet.get_account_balance(k)
1088 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1089 l.addTopLevelItem(account_item)
1090 account_item.setExpanded(True)
1092 for is_change in ([0,1] if self.expert_mode else [0]):
1093 if self.expert_mode:
1094 name = "Receiving" if not is_change else "Change"
1095 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1096 account_item.addChild(seq_item)
1097 if not is_change: seq_item.setExpanded(True)
1099 seq_item = account_item
1103 for address in account[is_change]:
1104 h = self.wallet.history.get(address,[])
1108 if gap > self.wallet.gap_limit:
1113 num_tx = '*' if h == ['*'] else "%d"%len(h)
1114 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1115 self.update_receive_item(item)
1117 item.setBackgroundColor(1, QColor('red'))
1118 seq_item.addChild(item)
1121 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1122 c,u = self.wallet.get_imported_balance()
1123 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1124 l.addTopLevelItem(account_item)
1125 account_item.setExpanded(True)
1126 for address in self.wallet.imported_keys.keys():
1127 item = QTreeWidgetItem( [ address, '', '', ''] )
1128 self.update_receive_item(item)
1129 account_item.addChild(item)
1132 # we use column 1 because column 0 may be hidden
1133 l.setCurrentItem(l.topLevelItem(0),1)
1136 def update_contacts_tab(self):
1138 l = self.contacts_list
1141 for address in self.wallet.addressbook:
1142 label = self.wallet.labels.get(address,'')
1143 n = self.wallet.get_num_tx(address)
1144 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1145 item.setFont(0, QFont(MONOSPACE_FONT))
1146 # 32 = label can be edited (bool)
1147 item.setData(0,32, True)
1149 item.setData(0,33, address)
1150 l.addTopLevelItem(item)
1152 self.run_hook('update_contacts_tab', l)
1153 l.setCurrentItem(l.topLevelItem(0))
1157 def create_console_tab(self):
1158 from qt_console import Console
1159 self.console = console = Console()
1160 self.console.history = self.config.get("console-history",[])
1161 self.console.history_index = len(self.console.history)
1163 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1164 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1166 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1168 def mkfunc(f, method):
1169 return lambda *args: apply( f, (method, args, self.password_dialog ))
1171 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1172 methods[m] = mkfunc(c._run, m)
1174 console.updateNamespace(methods)
1177 def change_account(self,s):
1178 if s == _("All accounts"):
1179 self.current_account = None
1181 accounts = self.wallet.get_accounts()
1182 for k, v in accounts.items():
1184 self.current_account = k
1185 self.update_history_tab()
1186 self.update_status()
1187 self.update_receive_tab()
1189 def create_status_bar(self):
1192 sb.setFixedHeight(35)
1193 qtVersion = qVersion()
1195 self.balance_label = QLabel("")
1196 sb.addWidget(self.balance_label)
1198 update_notification = UpdateLabel(self.config)
1199 if(update_notification.new_version):
1200 sb.addPermanentWidget(update_notification)
1202 accounts = self.wallet.get_accounts()
1203 if len(accounts) > 1:
1204 from_combo = QComboBox()
1205 from_combo.addItems([_("All accounts")] + accounts.values())
1206 from_combo.setCurrentIndex(0)
1207 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1208 sb.addPermanentWidget(from_combo)
1210 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1211 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1212 if self.wallet.seed:
1213 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1214 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1215 sb.addPermanentWidget( self.password_button )
1216 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1217 if self.wallet.seed:
1218 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1219 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1220 sb.addPermanentWidget( self.status_button )
1222 self.run_hook('create_status_bar', (sb,))
1224 self.setStatusBar(sb)
1228 self.config.set_key('gui', 'lite', True)
1231 self.lite.mini.show()
1233 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1234 self.lite.main(None)
1236 def new_contact_dialog(self):
1237 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1238 address = unicode(text)
1240 if is_valid(address):
1241 self.wallet.add_contact(address)
1242 self.update_contacts_tab()
1243 self.update_history_tab()
1244 self.update_completions()
1246 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1248 def show_master_public_key(self):
1249 dialog = QDialog(self)
1251 dialog.setWindowTitle(_("Master Public Key"))
1253 main_text = QTextEdit()
1254 main_text.setText(self.wallet.get_master_public_key())
1255 main_text.setReadOnly(True)
1256 main_text.setMaximumHeight(170)
1257 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1259 ok_button = QPushButton(_("OK"))
1260 ok_button.setDefault(True)
1261 ok_button.clicked.connect(dialog.accept)
1263 main_layout = QGridLayout()
1264 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1266 main_layout.addWidget(main_text, 1, 0)
1267 main_layout.addWidget(qrw, 1, 1 )
1269 vbox = QVBoxLayout()
1270 vbox.addLayout(main_layout)
1271 hbox = QHBoxLayout()
1273 hbox.addWidget(ok_button)
1274 vbox.addLayout(hbox)
1276 dialog.setLayout(vbox)
1281 def show_seed_dialog(self, password):
1282 if not self.wallet.seed:
1283 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1286 seed = self.wallet.decode_seed(password)
1288 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1290 self.show_seed(seed, self.wallet.imported_keys, self)
1294 def show_seed(self, seed, imported_keys, parent=None):
1295 dialog = QDialog(parent)
1297 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1299 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1301 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1303 seed_text = QTextEdit(brainwallet)
1304 seed_text.setReadOnly(True)
1305 seed_text.setMaximumHeight(130)
1307 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1308 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1309 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1310 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1312 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1313 label2 = QLabel(msg2)
1314 label2.setWordWrap(True)
1317 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1318 logo.setMaximumWidth(60)
1320 qrw = QRCodeWidget(seed)
1322 ok_button = QPushButton(_("OK"))
1323 ok_button.setDefault(True)
1324 ok_button.clicked.connect(dialog.accept)
1326 grid = QGridLayout()
1327 #main_layout.addWidget(logo, 0, 0)
1329 grid.addWidget(logo, 0, 0)
1330 grid.addWidget(label1, 0, 1)
1332 grid.addWidget(seed_text, 1, 0, 1, 2)
1334 grid.addWidget(qrw, 0, 2, 2, 1)
1336 vbox = QVBoxLayout()
1337 vbox.addLayout(grid)
1338 vbox.addWidget(label2)
1340 hbox = QHBoxLayout()
1342 hbox.addWidget(ok_button)
1343 vbox.addLayout(hbox)
1345 dialog.setLayout(vbox)
1348 def show_qrcode(self, data, title = "QR code"):
1352 d.setWindowTitle(title)
1353 d.setMinimumSize(270, 300)
1354 vbox = QVBoxLayout()
1355 qrw = QRCodeWidget(data)
1356 vbox.addWidget(qrw, 1)
1357 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1358 hbox = QHBoxLayout()
1362 filename = "qrcode.bmp"
1363 bmp.save_qrcode(qrw.qr, filename)
1364 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1366 b = QPushButton(_("Save"))
1368 b.clicked.connect(print_qr)
1370 b = QPushButton(_("Close"))
1372 b.clicked.connect(d.accept)
1375 vbox.addLayout(hbox)
1380 def do_protect(self, func, args):
1381 if self.wallet.use_encryption:
1382 password = self.password_dialog()
1388 if args != (False,):
1389 args = (self,) + args + (password,)
1391 args = (self,password)
1396 def show_private_key(self, address, password):
1397 if not address: return
1399 pk = self.wallet.get_private_key(address, password)
1400 except BaseException, e:
1401 self.show_message(str(e))
1403 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1407 def do_sign(self, address, message, signature, password):
1409 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1410 signature.setText(sig)
1411 except BaseException, e:
1412 self.show_message(str(e))
1414 def sign_message(self, address):
1415 if not address: return
1418 d.setWindowTitle(_('Sign Message'))
1419 d.setMinimumSize(410, 290)
1421 tab_widget = QTabWidget()
1423 layout = QGridLayout(tab)
1425 sign_address = QLineEdit()
1427 sign_address.setText(address)
1428 layout.addWidget(QLabel(_('Address')), 1, 0)
1429 layout.addWidget(sign_address, 1, 1)
1431 sign_message = QTextEdit()
1432 layout.addWidget(QLabel(_('Message')), 2, 0)
1433 layout.addWidget(sign_message, 2, 1)
1434 layout.setRowStretch(2,3)
1436 sign_signature = QTextEdit()
1437 layout.addWidget(QLabel(_('Signature')), 3, 0)
1438 layout.addWidget(sign_signature, 3, 1)
1439 layout.setRowStretch(3,1)
1442 hbox = QHBoxLayout()
1443 b = QPushButton(_("Sign"))
1445 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1446 b = QPushButton(_("Close"))
1447 b.clicked.connect(d.accept)
1449 layout.addLayout(hbox, 4, 1)
1450 tab_widget.addTab(tab, _("Sign"))
1454 layout = QGridLayout(tab)
1456 verify_address = QLineEdit()
1457 layout.addWidget(QLabel(_('Address')), 1, 0)
1458 layout.addWidget(verify_address, 1, 1)
1460 verify_message = QTextEdit()
1461 layout.addWidget(QLabel(_('Message')), 2, 0)
1462 layout.addWidget(verify_message, 2, 1)
1463 layout.setRowStretch(2,3)
1465 verify_signature = QTextEdit()
1466 layout.addWidget(QLabel(_('Signature')), 3, 0)
1467 layout.addWidget(verify_signature, 3, 1)
1468 layout.setRowStretch(3,1)
1471 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1472 self.show_message(_("Signature verified"))
1474 self.show_message(_("Error: wrong signature"))
1476 hbox = QHBoxLayout()
1477 b = QPushButton(_("Verify"))
1478 b.clicked.connect(do_verify)
1480 b = QPushButton(_("Close"))
1481 b.clicked.connect(d.accept)
1483 layout.addLayout(hbox, 4, 1)
1484 tab_widget.addTab(tab, _("Verify"))
1486 vbox = QVBoxLayout()
1487 vbox.addWidget(tab_widget)
1494 def question(self, msg):
1495 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1497 def show_message(self, msg):
1498 QMessageBox.information(self, _('Message'), msg, _('OK'))
1500 def password_dialog(self ):
1507 vbox = QVBoxLayout()
1508 msg = _('Please enter your password')
1509 vbox.addWidget(QLabel(msg))
1511 grid = QGridLayout()
1513 grid.addWidget(QLabel(_('Password')), 1, 0)
1514 grid.addWidget(pw, 1, 1)
1515 vbox.addLayout(grid)
1517 vbox.addLayout(ok_cancel_buttons(d))
1520 self.run_hook('password_dialog', pw, grid, 1)
1521 if not d.exec_(): return
1522 return unicode(pw.text())
1529 def change_password_dialog( wallet, parent=None ):
1532 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1540 new_pw = QLineEdit()
1541 new_pw.setEchoMode(2)
1542 conf_pw = QLineEdit()
1543 conf_pw.setEchoMode(2)
1545 vbox = QVBoxLayout()
1547 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1548 +_('To disable wallet encryption, enter an empty new password.')) \
1549 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1551 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1552 +_("Leave these fields empty if you want to disable encryption.")
1553 vbox.addWidget(QLabel(msg))
1555 grid = QGridLayout()
1558 if wallet.use_encryption:
1559 grid.addWidget(QLabel(_('Password')), 1, 0)
1560 grid.addWidget(pw, 1, 1)
1562 grid.addWidget(QLabel(_('New Password')), 2, 0)
1563 grid.addWidget(new_pw, 2, 1)
1565 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1566 grid.addWidget(conf_pw, 3, 1)
1567 vbox.addLayout(grid)
1569 vbox.addLayout(ok_cancel_buttons(d))
1572 if not d.exec_(): return
1574 password = unicode(pw.text()) if wallet.use_encryption else None
1575 new_password = unicode(new_pw.text())
1576 new_password2 = unicode(conf_pw.text())
1579 seed = wallet.decode_seed(password)
1581 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1584 if new_password != new_password2:
1585 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1586 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1589 wallet.update_password(seed, password, new_password)
1591 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1594 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1597 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1598 parent.password_button.setIcon( icon )
1602 def generate_transaction_information_widget(self, tx):
1603 tabs = QTabWidget(self)
1606 grid_ui = QGridLayout(tab1)
1607 grid_ui.setColumnStretch(0,1)
1608 tabs.addTab(tab1, _('Outputs') )
1610 tree_widget = MyTreeWidget(self)
1611 tree_widget.setColumnCount(2)
1612 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1613 tree_widget.setColumnWidth(0, 300)
1614 tree_widget.setColumnWidth(1, 50)
1616 for address, value in tx.outputs:
1617 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1618 tree_widget.addTopLevelItem(item)
1620 tree_widget.setMaximumHeight(100)
1622 grid_ui.addWidget(tree_widget)
1625 grid_ui = QGridLayout(tab2)
1626 grid_ui.setColumnStretch(0,1)
1627 tabs.addTab(tab2, _('Inputs') )
1629 tree_widget = MyTreeWidget(self)
1630 tree_widget.setColumnCount(2)
1631 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1633 for input_line in tx.inputs:
1634 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1635 tree_widget.addTopLevelItem(item)
1637 tree_widget.setMaximumHeight(100)
1639 grid_ui.addWidget(tree_widget)
1643 def tx_dict_from_text(self, txt):
1645 tx_dict = json.loads(str(txt))
1646 assert "hex" in tx_dict.keys()
1647 assert "complete" in tx_dict.keys()
1648 if not tx_dict["complete"]:
1649 assert "input_info" in tx_dict.keys()
1651 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1656 def read_tx_from_file(self):
1657 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1661 with open(fileName, "r") as f:
1662 file_content = f.read()
1663 except (ValueError, IOError, os.error), reason:
1664 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1666 return self.tx_dict_from_text(file_content)
1670 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1672 self.wallet.signrawtransaction(tx, input_info, [], password)
1674 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1676 with open(fileName, "w+") as f:
1677 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1678 self.show_message(_("Transaction saved successfully"))
1681 except BaseException, e:
1682 self.show_message(str(e))
1685 def send_raw_transaction(self, raw_tx, dialog = ""):
1686 result, result_message = self.wallet.sendtx( raw_tx )
1688 self.show_message("Transaction successfully sent: %s" % (result_message))
1692 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1694 def do_process_from_text(self):
1695 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1698 tx_dict = self.tx_dict_from_text(text)
1700 self.create_process_transaction_window(tx_dict)
1702 def do_process_from_file(self):
1703 tx_dict = self.read_tx_from_file()
1705 self.create_process_transaction_window(tx_dict)
1707 def create_process_transaction_window(self, tx_dict):
1708 tx = Transaction(tx_dict["hex"])
1710 dialog = QDialog(self)
1711 dialog.setMinimumWidth(500)
1712 dialog.setWindowTitle(_('Process raw transaction'))
1718 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1719 l.addWidget(QLabel(_("Actions")), 4,0)
1721 if tx_dict["complete"] == False:
1722 l.addWidget(QLabel(_("Unsigned")), 3,1)
1723 if self.wallet.seed :
1724 b = QPushButton("Sign transaction")
1725 input_info = json.loads(tx_dict["input_info"])
1726 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1727 l.addWidget(b, 4, 1)
1729 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1731 l.addWidget(QLabel(_("Signed")), 3,1)
1732 b = QPushButton("Broadcast transaction")
1733 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1736 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1737 cancelButton = QPushButton(_("Cancel"))
1738 cancelButton.clicked.connect(lambda: dialog.done(0))
1739 l.addWidget(cancelButton, 4,2)
1745 def do_export_privkeys(self, password):
1746 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.")))
1749 select_export = _('Select file to export your private keys to')
1750 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1752 with open(fileName, "w+") as csvfile:
1753 transaction = csv.writer(csvfile)
1754 transaction.writerow(["address", "private_key"])
1757 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1758 transaction.writerow(["%34s"%addr,pk])
1760 self.show_message(_("Private keys exported."))
1762 except (IOError, os.error), reason:
1763 export_error_label = _("Electrum was unable to produce a private key-export.")
1764 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1766 except BaseException, e:
1767 self.show_message(str(e))
1771 def do_import_labels(self):
1772 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1773 if not labelsFile: return
1775 f = open(labelsFile, 'r')
1778 for key, value in json.loads(data).items():
1779 self.wallet.labels[key] = value
1781 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1782 except (IOError, os.error), reason:
1783 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1786 def do_export_labels(self):
1787 labels = self.wallet.labels
1789 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1791 with open(fileName, 'w+') as f:
1792 json.dump(labels, f)
1793 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1794 except (IOError, os.error), reason:
1795 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1798 def do_export_history(self):
1799 from gui_lite import csv_transaction
1800 csv_transaction(self.wallet)
1804 def do_import_privkey(self, password):
1805 if not self.wallet.imported_keys:
1806 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1807 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1808 + _('Are you sure you understand what you are doing?'), 3, 4)
1811 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1814 text = str(text).split()
1819 addr = self.wallet.import_key(key, password)
1820 except BaseException as e:
1826 addrlist.append(addr)
1828 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1830 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1831 self.update_receive_tab()
1832 self.update_history_tab()
1835 def settings_dialog(self):
1837 d.setWindowTitle(_('Electrum Settings'))
1839 vbox = QVBoxLayout()
1841 tabs = QTabWidget(self)
1842 self.settings_tab = tabs
1843 vbox.addWidget(tabs)
1846 grid_ui = QGridLayout(tab1)
1847 grid_ui.setColumnStretch(0,1)
1848 tabs.addTab(tab1, _('Display') )
1850 nz_label = QLabel(_('Display zeros'))
1851 grid_ui.addWidget(nz_label, 0, 0)
1852 nz_e = AmountEdit(None,True)
1853 nz_e.setText("%d"% self.wallet.num_zeros)
1854 grid_ui.addWidget(nz_e, 0, 1)
1855 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1856 grid_ui.addWidget(HelpButton(msg), 0, 2)
1857 if not self.config.is_modifiable('num_zeros'):
1858 for w in [nz_e, nz_label]: w.setEnabled(False)
1860 lang_label=QLabel(_('Language') + ':')
1861 grid_ui.addWidget(lang_label, 1, 0)
1862 lang_combo = QComboBox()
1863 from i18n import languages
1864 lang_combo.addItems(languages.values())
1866 index = languages.keys().index(self.config.get("language",''))
1869 lang_combo.setCurrentIndex(index)
1870 grid_ui.addWidget(lang_combo, 1, 1)
1871 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1872 if not self.config.is_modifiable('language'):
1873 for w in [lang_combo, lang_label]: w.setEnabled(False)
1875 currencies = self.exchanger.get_currencies()
1876 currencies.insert(0, "None")
1878 cur_label=QLabel(_('Currency') + ':')
1879 grid_ui.addWidget(cur_label , 2, 0)
1880 cur_combo = QComboBox()
1881 cur_combo.addItems(currencies)
1883 index = currencies.index(self.config.get('currency', "None"))
1886 cur_combo.setCurrentIndex(index)
1887 grid_ui.addWidget(cur_combo, 2, 1)
1888 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1890 expert_cb = QCheckBox(_('Expert mode'))
1891 expert_cb.setChecked(self.expert_mode)
1892 grid_ui.addWidget(expert_cb, 3, 0)
1893 hh = _('In expert mode, your client will:') + '\n' \
1894 + _(' - Show change addresses in the Receive tab') + '\n' \
1895 + _(' - Display the balance of each address') + '\n' \
1896 + _(' - Add freeze/prioritize actions to addresses.')
1897 grid_ui.addWidget(HelpButton(hh), 3, 2)
1898 grid_ui.setRowStretch(4,1)
1902 grid_wallet = QGridLayout(tab2)
1903 grid_wallet.setColumnStretch(0,1)
1904 tabs.addTab(tab2, _('Wallet') )
1906 fee_label = QLabel(_('Transaction fee'))
1907 grid_wallet.addWidget(fee_label, 0, 0)
1908 fee_e = AmountEdit(self.base_unit)
1909 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1910 grid_wallet.addWidget(fee_e, 0, 2)
1911 msg = _('Fee per kilobyte of transaction.') + ' ' \
1912 + _('Recommended value') + ': ' + self.format_amount(20000)
1913 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1914 if not self.config.is_modifiable('fee_per_kb'):
1915 for w in [fee_e, fee_label]: w.setEnabled(False)
1917 usechange_cb = QCheckBox(_('Use change addresses'))
1918 usechange_cb.setChecked(self.wallet.use_change)
1919 grid_wallet.addWidget(usechange_cb, 1, 0)
1920 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1921 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1923 gap_label = QLabel(_('Gap limit'))
1924 grid_wallet.addWidget(gap_label, 2, 0)
1925 gap_e = AmountEdit(None,True)
1926 gap_e.setText("%d"% self.wallet.gap_limit)
1927 grid_wallet.addWidget(gap_e, 2, 2)
1928 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1929 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1930 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1931 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1932 + _('Warning') + ': ' \
1933 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1934 + _('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'
1935 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1936 if not self.config.is_modifiable('gap_limit'):
1937 for w in [gap_e, gap_label]: w.setEnabled(False)
1939 units = ['BTC', 'mBTC']
1940 unit_label = QLabel(_('Base unit'))
1941 grid_wallet.addWidget(unit_label, 3, 0)
1942 unit_combo = QComboBox()
1943 unit_combo.addItems(units)
1944 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1945 grid_wallet.addWidget(unit_combo, 3, 2)
1946 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1947 + '\n1BTC=1000mBTC.\n' \
1948 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1949 grid_wallet.setRowStretch(4,1)
1954 grid_io = QGridLayout(tab3)
1955 grid_io.setColumnStretch(0,1)
1956 tabs.addTab(tab3, _('Import/Export') )
1958 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1959 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1960 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1961 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1963 grid_io.addWidget(QLabel(_('History')), 2, 0)
1964 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1965 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1967 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1969 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1970 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1971 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1973 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1974 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1975 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1976 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1977 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1980 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1981 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1982 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1983 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1985 grid_io.setRowStretch(6,1)
1990 tab5 = QScrollArea()
1991 tab5.setEnabled(True)
1992 tab5.setWidgetResizable(True)
1994 grid_plugins = QGridLayout()
1995 grid_plugins.setColumnStretch(0,1)
1998 w.setLayout(grid_plugins)
2000 tab5.setMaximumSize(tab3.size()) # optional
2002 w.setMinimumHeight(len(self.plugins)*35)
2004 tabs.addTab(tab5, _('Plugins') )
2005 def mk_toggle(cb, p):
2006 return lambda: cb.setChecked(p.toggle())
2007 for i, p in enumerate(self.plugins):
2009 name, description = p.get_info()
2010 cb = QCheckBox(name)
2011 cb.setDisabled(not p.is_available())
2012 cb.setChecked(p.is_enabled())
2013 cb.clicked.connect(mk_toggle(cb,p))
2014 grid_plugins.addWidget(cb, i, 0)
2015 if p.requires_settings():
2016 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2017 grid_plugins.addWidget(HelpButton(description), i, 2)
2019 print_msg("Error: cannot display plugin", p)
2020 traceback.print_exc(file=sys.stdout)
2021 grid_plugins.setRowStretch(i+1,1)
2023 self.run_hook('create_settings_tab', tabs)
2025 vbox.addLayout(ok_cancel_buttons(d))
2029 if not d.exec_(): return
2031 fee = unicode(fee_e.text())
2033 fee = self.read_amount(fee)
2035 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2038 self.wallet.set_fee(fee)
2040 nz = unicode(nz_e.text())
2045 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2048 if self.wallet.num_zeros != nz:
2049 self.wallet.num_zeros = nz
2050 self.config.set_key('num_zeros', nz, True)
2051 self.update_history_tab()
2052 self.update_receive_tab()
2054 usechange_result = usechange_cb.isChecked()
2055 if self.wallet.use_change != usechange_result:
2056 self.wallet.use_change = usechange_result
2057 self.config.set_key('use_change', self.wallet.use_change, True)
2059 unit_result = units[unit_combo.currentIndex()]
2060 if self.base_unit() != unit_result:
2061 self.decimal_point = 8 if unit_result == 'BTC' else 5
2062 self.config.set_key('decimal_point', self.decimal_point, True)
2063 self.update_history_tab()
2064 self.update_status()
2067 n = int(gap_e.text())
2069 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2072 if self.wallet.gap_limit != n:
2073 r = self.wallet.change_gap_limit(n)
2075 self.update_receive_tab()
2076 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2078 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2080 need_restart = False
2082 lang_request = languages.keys()[lang_combo.currentIndex()]
2083 if lang_request != self.config.get('language'):
2084 self.config.set_key("language", lang_request, True)
2087 cur_request = str(currencies[cur_combo.currentIndex()])
2088 if cur_request != self.config.get('currency', "None"):
2089 self.config.set_key('currency', cur_request, True)
2090 self.update_wallet()
2092 self.run_hook('close_settings_dialog')
2095 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2097 self.receive_tab_set_mode(expert_cb.isChecked())
2099 def run_network_dialog(self):
2100 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2102 def closeEvent(self, event):
2104 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2105 self.save_column_widths()
2106 self.config.set_key("console-history",self.console.history[-50:])
2115 def __init__(self, wallet, config, app=None):
2116 self.wallet = wallet
2117 self.config = config
2119 self.app = QApplication(sys.argv)
2122 def restore_or_create(self):
2123 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2124 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2125 if r==2: return None
2126 return 'restore' if r==1 else 'create'
2129 def verify_seed(self):
2130 r = self.seed_dialog(False)
2131 if r != self.wallet.seed:
2132 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2139 def seed_dialog(self, is_restore=True):
2143 vbox = QVBoxLayout()
2145 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2147 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2149 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2152 label.setWordWrap(True)
2153 vbox.addWidget(label)
2155 seed_e = QTextEdit()
2156 seed_e.setMaximumHeight(100)
2157 vbox.addWidget(seed_e)
2160 grid = QGridLayout()
2162 gap_e = AmountEdit(None, True)
2164 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2165 grid.addWidget(gap_e, 2, 1)
2166 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2167 vbox.addLayout(grid)
2169 vbox.addLayout(ok_cancel_buttons(d))
2172 if not d.exec_(): return
2175 seed = str(seed_e.toPlainText())
2179 seed = mnemonic.mn_decode( seed.split() )
2181 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2185 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2192 gap = int(unicode(gap_e.text()))
2194 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2199 def network_dialog(self):
2200 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2203 def show_seed(self):
2204 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2206 def password_dialog(self):
2207 if self.wallet.seed:
2208 ElectrumWindow.change_password_dialog(self.wallet)
2211 def restore_wallet(self):
2212 wallet = self.wallet
2213 # wait until we are connected, because the user might have selected another server
2214 if not wallet.interface.is_connected:
2215 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2216 waiting_dialog(waiting)
2218 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2219 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2221 wallet.set_up_to_date(False)
2222 wallet.interface.poke('synchronizer')
2223 waiting_dialog(waiting)
2224 if wallet.is_found():
2225 print_error( "Recovery successful" )
2227 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2234 w = ElectrumWindow(self.wallet, self.config)
2235 if url: w.set_url(url)