3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
28 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
30 from PyQt4.QtGui import *
31 from PyQt4.QtCore import *
32 import PyQt4.QtCore as QtCore
34 from electrum.bitcoin import MIN_RELAY_TX_FEE
39 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41 from electrum.wallet import format_satoshis
42 from electrum.bitcoin import Transaction, is_valid
43 from electrum import mnemonic
44 from electrum import util, bitcoin, commands
46 import bmp, pyqrnative
49 from amountedit import AmountEdit
50 from network_dialog import NetworkDialog
51 from qrcodewidget import QRCodeWidget
53 from decimal import Decimal
61 if platform.system() == 'Windows':
62 MONOSPACE_FONT = 'Lucida Console'
63 elif platform.system() == 'Darwin':
64 MONOSPACE_FONT = 'Monaco'
66 MONOSPACE_FONT = 'monospace'
68 from electrum import ELECTRUM_VERSION
73 class UpdateLabel(QLabel):
74 def __init__(self, config, parent=None):
75 QLabel.__init__(self, parent)
76 self.new_version = False
79 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
80 con.request("GET", "/version")
81 res = con.getresponse()
82 except socket.error as msg:
83 print_error("Could not retrieve version information")
87 self.latest_version = res.read()
88 self.latest_version = self.latest_version.replace("\n","")
89 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
91 self.current_version = ELECTRUM_VERSION
92 if(self.compare_versions(self.latest_version, self.current_version) == 1):
93 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
94 if(self.compare_versions(self.latest_version, latest_seen) == 1):
95 self.new_version = True
96 self.setText(_("New version available") + ": " + self.latest_version)
99 def compare_versions(self, version1, version2):
101 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
102 return cmp(normalize(version1), normalize(version2))
104 def ignore_this_version(self):
106 self.config.set_key("last_seen_version", self.latest_version, True)
107 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
110 def ignore_all_version(self):
112 self.config.set_key("last_seen_version", "9.9.9", True)
113 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
116 def open_website(self):
117 webbrowser.open("http://electrum.org/download.html")
120 def mouseReleaseEvent(self, event):
121 dialog = QDialog(self)
122 dialog.setWindowTitle(_('Electrum update'))
125 main_layout = QGridLayout()
126 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
128 ignore_version = QPushButton(_("Ignore this version"))
129 ignore_version.clicked.connect(self.ignore_this_version)
131 ignore_all_versions = QPushButton(_("Ignore all versions"))
132 ignore_all_versions.clicked.connect(self.ignore_all_version)
134 open_website = QPushButton(_("Goto download page"))
135 open_website.clicked.connect(self.open_website)
137 main_layout.addWidget(ignore_version, 1, 0)
138 main_layout.addWidget(ignore_all_versions, 1, 1)
139 main_layout.addWidget(open_website, 1, 2)
141 dialog.setLayout(main_layout)
145 if not dialog.exec_(): return
149 class Timer(QtCore.QThread):
152 self.emit(QtCore.SIGNAL('timersignal'))
155 class HelpButton(QPushButton):
156 def __init__(self, text):
157 QPushButton.__init__(self, '?')
158 self.setFocusPolicy(Qt.NoFocus)
159 self.setFixedWidth(20)
160 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
163 class EnterButton(QPushButton):
164 def __init__(self, text, func):
165 QPushButton.__init__(self, text)
167 self.clicked.connect(func)
169 def keyPressEvent(self, e):
170 if e.key() == QtCore.Qt.Key_Return:
173 class MyTreeWidget(QTreeWidget):
174 def __init__(self, parent):
175 QTreeWidget.__init__(self, parent)
178 for i in range(0,self.viewport().height()/5):
179 if self.itemAt(QPoint(0,i*5)) == item:
183 for j in range(0,30):
184 if self.itemAt(QPoint(0,i*5 + j)) != item:
186 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
188 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
193 class StatusBarButton(QPushButton):
194 def __init__(self, icon, tooltip, func):
195 QPushButton.__init__(self, icon, '')
196 self.setToolTip(tooltip)
198 self.setMaximumWidth(25)
199 self.clicked.connect(func)
202 def keyPressEvent(self, e):
203 if e.key() == QtCore.Qt.Key_Return:
210 def waiting_dialog(f):
216 w.setWindowTitle('Electrum')
226 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
235 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
237 class ElectrumWindow(QMainWindow):
239 def __init__(self, wallet, config):
240 QMainWindow.__init__(self)
244 self.current_account = self.config.get("current_account", None)
247 self.create_status_bar()
249 self.need_update = threading.Event()
250 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
251 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
252 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
253 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
255 self.expert_mode = config.get('classic_expert_mode', False)
256 self.decimal_point = config.get('decimal_point', 8)
258 set_language(config.get('language'))
260 self.funds_error = False
261 self.completions = QStringListModel()
263 self.tabs = tabs = QTabWidget(self)
264 self.column_widths = self.config.get("column_widths", default_column_widths )
265 tabs.addTab(self.create_history_tab(), _('History') )
266 tabs.addTab(self.create_send_tab(), _('Send') )
267 tabs.addTab(self.create_receive_tab(), _('Receive') )
268 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
269 tabs.addTab(self.create_console_tab(), _('Console') )
270 tabs.setMinimumSize(600, 400)
271 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
272 self.setCentralWidget(tabs)
274 g = self.config.get("winpos-qt",[100, 100, 840, 400])
275 self.setGeometry(g[0], g[1], g[2], g[3])
276 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
277 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
278 self.setWindowTitle( title )
280 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
281 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
282 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
283 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
285 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
286 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
287 self.history_list.setFocus(True)
289 self.exchanger = exchange_rate.Exchanger(self)
290 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
292 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
293 if platform.system() == 'Windows':
294 n = 3 if self.wallet.seed else 2
295 tabs.setCurrentIndex (n)
296 tabs.setCurrentIndex (0)
298 # set initial message
299 self.console.showMessage(self.wallet.interface.banner)
301 # plugins that need to change the GUI do it here
302 self.run_hook('init_gui')
306 def init_plugins(self):
307 import imp, pkgutil, __builtin__
308 if __builtin__.use_local_modules:
309 fp, pathname, description = imp.find_module('plugins')
310 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
311 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
312 imp.load_module('electrum_plugins', fp, pathname, description)
313 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
315 import electrum_plugins
316 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
317 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
322 self.plugins.append( p.Plugin(self) )
324 print_msg("Error:cannot initialize plugin",p)
325 traceback.print_exc(file=sys.stdout)
328 def run_hook(self, name, *args):
329 for p in self.plugins:
330 if not p.is_enabled():
339 print_error("Plugin error")
340 traceback.print_exc(file=sys.stdout)
345 def set_label(self, name, text = None):
347 old_text = self.wallet.labels.get(name)
350 self.wallet.labels[name] = text
354 self.wallet.labels.pop(name)
356 self.run_hook('set_label', name, text, changed)
360 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
361 def getOpenFileName(self, title, filter = None):
362 directory = self.config.get('io_dir', os.path.expanduser('~'))
363 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
364 if fileName and directory != os.path.dirname(fileName):
365 self.config.set_key('io_dir', os.path.dirname(fileName), True)
368 def getSaveFileName(self, title, filename, filter = None):
369 directory = self.config.get('io_dir', os.path.expanduser('~'))
370 path = os.path.join( directory, filename )
371 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
372 if fileName and directory != os.path.dirname(fileName):
373 self.config.set_key('io_dir', os.path.dirname(fileName), True)
379 QMainWindow.close(self)
380 self.run_hook('close_main_window')
382 def connect_slots(self, sender):
383 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
384 self.previous_payto_e=''
386 def timer_actions(self):
387 if self.need_update.is_set():
389 self.need_update.clear()
390 self.run_hook('timer_actions')
392 def format_amount(self, x, is_diff=False):
393 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
395 def read_amount(self, x):
396 if x in['.', '']: return None
397 p = pow(10, self.decimal_point)
398 return int( p * Decimal(x) )
401 assert self.decimal_point in [5,8]
402 return "BTC" if self.decimal_point == 8 else "mBTC"
404 def update_status(self):
405 if self.wallet.interface and self.wallet.interface.is_connected:
406 if not self.wallet.up_to_date:
407 text = _("Synchronizing...")
408 icon = QIcon(":icons/status_waiting.png")
410 c, u = self.wallet.get_account_balance(self.current_account)
411 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
412 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
413 text += self.create_quote_text(Decimal(c+u)/100000000)
414 icon = QIcon(":icons/status_connected.png")
416 text = _("Not connected")
417 icon = QIcon(":icons/status_disconnected.png")
419 self.status_text = text
420 self.statusBar().showMessage(text)
421 self.status_button.setIcon( icon )
423 def update_wallet(self):
425 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
426 self.update_history_tab()
427 self.update_receive_tab()
428 self.update_contacts_tab()
429 self.update_completions()
432 def create_quote_text(self, btc_balance):
433 quote_currency = self.config.get("currency", "None")
434 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
435 if quote_balance is None:
438 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
441 def create_history_tab(self):
442 self.history_list = l = MyTreeWidget(self)
444 for i,width in enumerate(self.column_widths['history']):
445 l.setColumnWidth(i, width)
446 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
447 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
448 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
450 l.setContextMenuPolicy(Qt.CustomContextMenu)
451 l.customContextMenuRequested.connect(self.create_history_menu)
455 def create_history_menu(self, position):
456 self.history_list.selectedIndexes()
457 item = self.history_list.currentItem()
459 tx_hash = str(item.data(0, Qt.UserRole).toString())
460 if not tx_hash: return
462 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
463 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
464 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
465 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
468 def show_tx_details(self, tx):
469 dialog = QDialog(self)
471 dialog.setWindowTitle(_("Transaction Details"))
473 dialog.setLayout(vbox)
474 dialog.setMinimumSize(600,300)
477 if tx_hash in self.wallet.transactions.keys():
478 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
479 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
481 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
487 vbox.addWidget(QLabel("Transaction ID:"))
488 e = QLineEdit(tx_hash)
492 vbox.addWidget(QLabel("Date: %s"%time_str))
493 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
496 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
497 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
499 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
500 vbox.addWidget(QLabel("Transaction fee: unknown"))
502 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
504 vbox.addWidget( self.generate_transaction_information_widget(tx) )
506 ok_button = QPushButton(_("Close"))
507 ok_button.setDefault(True)
508 ok_button.clicked.connect(dialog.accept)
512 hbox.addWidget(ok_button)
516 def tx_label_clicked(self, item, column):
517 if column==2 and item.isSelected():
519 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
520 self.history_list.editItem( item, column )
521 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
524 def tx_label_changed(self, item, column):
528 tx_hash = str(item.data(0, Qt.UserRole).toString())
529 tx = self.wallet.transactions.get(tx_hash)
530 text = unicode( item.text(2) )
531 self.set_label(tx_hash, text)
533 item.setForeground(2, QBrush(QColor('black')))
535 text = self.wallet.get_default_label(tx_hash)
536 item.setText(2, text)
537 item.setForeground(2, QBrush(QColor('gray')))
541 def edit_label(self, is_recv):
542 l = self.receive_list if is_recv else self.contacts_list
543 item = l.currentItem()
544 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
545 l.editItem( item, 1 )
546 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
550 def address_label_clicked(self, item, column, l, column_addr, column_label):
551 if column == column_label and item.isSelected():
552 is_editable = item.data(0, 32).toBool()
555 addr = unicode( item.text(column_addr) )
556 label = unicode( item.text(column_label) )
557 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 l.editItem( item, column )
559 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
562 def address_label_changed(self, item, column, l, column_addr, column_label):
563 if column == column_label:
564 addr = unicode( item.text(column_addr) )
565 text = unicode( item.text(column_label) )
566 is_editable = item.data(0, 32).toBool()
570 changed = self.set_label(addr, text)
572 self.update_history_tab()
573 self.update_completions()
575 self.current_item_changed(item)
577 self.run_hook('item_changed', item, column)
580 def current_item_changed(self, a):
581 self.run_hook('current_item_changed', a)
585 def update_history_tab(self):
587 self.history_list.clear()
588 for item in self.wallet.get_tx_history(self.current_account):
589 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
592 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
597 time_str = 'unverified'
598 icon = QIcon(":icons/unconfirmed.png")
601 icon = QIcon(":icons/unconfirmed.png")
603 icon = QIcon(":icons/clock%d.png"%conf)
605 icon = QIcon(":icons/confirmed.png")
607 if value is not None:
608 v_str = self.format_amount(value, True)
612 balance_str = self.format_amount(balance)
615 label, is_default_label = self.wallet.get_label(tx_hash)
617 label = _('Pruned transaction outputs')
618 is_default_label = False
620 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
621 item.setFont(2, QFont(MONOSPACE_FONT))
622 item.setFont(3, QFont(MONOSPACE_FONT))
623 item.setFont(4, QFont(MONOSPACE_FONT))
625 item.setForeground(3, QBrush(QColor("#BC1E1E")))
627 item.setData(0, Qt.UserRole, tx_hash)
628 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
630 item.setForeground(2, QBrush(QColor('grey')))
632 item.setIcon(0, icon)
633 self.history_list.insertTopLevelItem(0,item)
636 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
639 def create_send_tab(self):
644 grid.setColumnMinimumWidth(3,300)
645 grid.setColumnStretch(5,1)
648 self.payto_e = QLineEdit()
649 grid.addWidget(QLabel(_('Pay to')), 1, 0)
650 grid.addWidget(self.payto_e, 1, 1, 1, 3)
652 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
654 completer = QCompleter()
655 completer.setCaseSensitivity(False)
656 self.payto_e.setCompleter(completer)
657 completer.setModel(self.completions)
659 self.message_e = QLineEdit()
660 grid.addWidget(QLabel(_('Description')), 2, 0)
661 grid.addWidget(self.message_e, 2, 1, 1, 3)
662 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
664 self.amount_e = AmountEdit(self.base_unit)
665 grid.addWidget(QLabel(_('Amount')), 3, 0)
666 grid.addWidget(self.amount_e, 3, 1, 1, 2)
667 grid.addWidget(HelpButton(
668 _('Amount to be sent.') + '\n\n' \
669 + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') \
670 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
672 self.fee_e = AmountEdit(self.base_unit)
673 grid.addWidget(QLabel(_('Fee')), 4, 0)
674 grid.addWidget(self.fee_e, 4, 1, 1, 2)
675 grid.addWidget(HelpButton(
676 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
677 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
678 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
681 b = EnterButton(_("Send"), self.do_send)
683 b = EnterButton(_("Create unsigned transaction"), self.do_send)
684 grid.addWidget(b, 6, 1)
686 b = EnterButton(_("Clear"),self.do_clear)
687 grid.addWidget(b, 6, 2)
689 self.payto_sig = QLabel('')
690 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
692 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
693 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
702 def entry_changed( is_fee ):
703 self.funds_error = False
705 if self.amount_e.is_shortcut:
706 self.amount_e.is_shortcut = False
707 c, u = self.wallet.get_account_balance(self.current_account)
708 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
709 fee = self.wallet.estimated_fee(inputs)
711 self.amount_e.setText( self.format_amount(amount) )
712 self.fee_e.setText( self.format_amount( fee ) )
715 amount = self.read_amount(str(self.amount_e.text()))
716 fee = self.read_amount(str(self.fee_e.text()))
718 if not is_fee: fee = None
721 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
723 self.fee_e.setText( self.format_amount( fee ) )
726 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
727 text = self.status_text
730 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
731 self.funds_error = True
732 text = _( "Not enough funds" )
733 c, u = self.wallet.get_frozen_balance()
734 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
736 self.statusBar().showMessage(text)
737 self.amount_e.setPalette(palette)
738 self.fee_e.setPalette(palette)
740 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
741 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
743 self.run_hook('create_send_tab', grid)
747 def update_completions(self):
749 for addr,label in self.wallet.labels.items():
750 if addr in self.wallet.addressbook:
751 l.append( label + ' <' + addr + '>')
753 self.run_hook('update_completions', l)
754 self.completions.setStringList(l)
758 return lambda s, *args: s.do_protect(func, args)
762 def do_send(self, password):
764 label = unicode( self.message_e.text() )
765 r = unicode( self.payto_e.text() )
768 # label or alias, with address in brackets
769 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
770 to_address = m.group(2) if m else r
772 if not is_valid(to_address):
773 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
777 amount = self.read_amount(unicode( self.amount_e.text()))
779 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
782 fee = self.read_amount(unicode( self.fee_e.text()))
784 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
788 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
789 except BaseException, e:
790 self.show_message(str(e))
793 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
794 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
797 self.run_hook('send_tx', tx)
800 self.set_label(tx.hash(), label)
803 h = self.wallet.send_tx(tx)
804 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
805 status, msg = self.wallet.receive_tx( h )
807 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
809 self.update_contacts_tab()
811 QMessageBox.warning(self, _('Error'), msg, _('OK'))
813 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
815 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
816 with open(fileName,'w') as f:
817 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
818 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
820 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
825 def set_url(self, url):
826 address, amount, label, message, signature, identity, url = util.parse_url(url)
827 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
829 if label and self.wallet.labels.get(address) != label:
830 if self.question('Give label "%s" to address %s ?'%(label,address)):
831 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
832 self.wallet.addressbook.append(address)
833 self.set_label(address, label)
835 self.run_hook('set_url', url, self.show_message, self.question)
837 self.tabs.setCurrentIndex(1)
838 label = self.wallet.labels.get(address)
839 m_addr = label + ' <'+ address +'>' if label else address
840 self.payto_e.setText(m_addr)
842 self.message_e.setText(message)
843 self.amount_e.setText(amount)
845 self.set_frozen(self.payto_e,True)
846 self.set_frozen(self.amount_e,True)
847 self.set_frozen(self.message_e,True)
848 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
850 self.payto_sig.setVisible(False)
853 self.payto_sig.setVisible(False)
854 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
856 self.set_frozen(e,False)
859 def set_frozen(self,entry,frozen):
861 entry.setReadOnly(True)
862 entry.setFrame(False)
864 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
865 entry.setPalette(palette)
867 entry.setReadOnly(False)
870 palette.setColor(entry.backgroundRole(), QColor('white'))
871 entry.setPalette(palette)
874 def toggle_freeze(self,addr):
876 if addr in self.wallet.frozen_addresses:
877 self.wallet.unfreeze(addr)
879 self.wallet.freeze(addr)
880 self.update_receive_tab()
882 def toggle_priority(self,addr):
884 if addr in self.wallet.prioritized_addresses:
885 self.wallet.unprioritize(addr)
887 self.wallet.prioritize(addr)
888 self.update_receive_tab()
891 def create_list_tab(self, headers):
892 "generic tab creation method"
893 l = MyTreeWidget(self)
894 l.setColumnCount( len(headers) )
895 l.setHeaderLabels( headers )
905 vbox.addWidget(buttons)
910 buttons.setLayout(hbox)
915 def create_receive_tab(self):
916 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
917 l.setContextMenuPolicy(Qt.CustomContextMenu)
918 l.customContextMenuRequested.connect(self.create_receive_menu)
919 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
920 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
921 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
922 self.receive_list = l
923 self.receive_buttons_hbox = hbox
928 def receive_tab_set_mode(self, i):
929 self.save_column_widths()
930 self.expert_mode = (i == 1)
931 self.config.set_key('classic_expert_mode', self.expert_mode, True)
933 self.update_receive_tab()
936 def save_column_widths(self):
937 if not self.expert_mode:
938 widths = [ self.receive_list.columnWidth(0) ]
941 for i in range(self.receive_list.columnCount() -1):
942 widths.append(self.receive_list.columnWidth(i))
943 self.column_widths["receive"][self.expert_mode] = widths
945 self.column_widths["history"] = []
946 for i in range(self.history_list.columnCount() - 1):
947 self.column_widths["history"].append(self.history_list.columnWidth(i))
949 self.column_widths["contacts"] = []
950 for i in range(self.contacts_list.columnCount() - 1):
951 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
953 self.config.set_key("column_widths", self.column_widths, True)
956 def create_contacts_tab(self):
957 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
958 l.setContextMenuPolicy(Qt.CustomContextMenu)
959 l.customContextMenuRequested.connect(self.create_contact_menu)
960 for i,width in enumerate(self.column_widths['contacts']):
961 l.setColumnWidth(i, width)
963 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
964 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
965 self.contacts_list = l
966 self.contacts_buttons_hbox = hbox
967 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
972 def delete_imported_key(self, addr):
973 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
974 self.wallet.imported_keys.pop(addr)
975 self.update_receive_tab()
976 self.update_history_tab()
980 def create_receive_menu(self, position):
981 # fixme: this function apparently has a side effect.
982 # if it is not called the menu pops up several times
983 #self.receive_list.selectedIndexes()
985 item = self.receive_list.itemAt(position)
987 addr = unicode(item.text(0))
988 if not is_valid(addr):
989 item.setExpanded(not item.isExpanded())
992 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
993 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
994 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
995 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
996 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
997 if addr in self.wallet.imported_keys:
998 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1000 if self.expert_mode:
1001 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1002 menu.addAction(t, lambda: self.toggle_freeze(addr))
1003 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1004 menu.addAction(t, lambda: self.toggle_priority(addr))
1006 self.run_hook('receive_menu', menu)
1007 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1010 def payto(self, addr):
1012 label = self.wallet.labels.get(addr)
1013 m_addr = label + ' <' + addr + '>' if label else addr
1014 self.tabs.setCurrentIndex(1)
1015 self.payto_e.setText(m_addr)
1016 self.amount_e.setFocus()
1019 def delete_contact(self, x):
1020 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1021 if x in self.wallet.addressbook:
1022 self.wallet.addressbook.remove(x)
1023 self.set_label(x, None)
1024 self.update_history_tab()
1025 self.update_contacts_tab()
1026 self.update_completions()
1029 def create_contact_menu(self, position):
1030 item = self.contacts_list.itemAt(position)
1032 addr = unicode(item.text(0))
1033 label = unicode(item.text(1))
1034 is_editable = item.data(0,32).toBool()
1035 payto_addr = item.data(0,33).toString()
1037 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1038 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1039 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1041 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1042 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1044 self.run_hook('create_contact_menu', menu, item)
1045 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1048 def update_receive_item(self, item):
1049 item.setFont(0, QFont(MONOSPACE_FONT))
1050 address = str(item.data(0,0).toString())
1051 label = self.wallet.labels.get(address,'')
1052 item.setData(1,0,label)
1053 item.setData(0,32, True) # is editable
1055 self.run_hook('update_receive_item', address, item)
1057 c, u = self.wallet.get_addr_balance(address)
1058 balance = self.format_amount(c + u)
1059 item.setData(2,0,balance)
1061 if self.expert_mode:
1062 if address in self.wallet.frozen_addresses:
1063 item.setBackgroundColor(0, QColor('lightblue'))
1064 elif address in self.wallet.prioritized_addresses:
1065 item.setBackgroundColor(0, QColor('lightgreen'))
1068 def update_receive_tab(self):
1069 l = self.receive_list
1072 l.setColumnHidden(2, not self.expert_mode)
1073 l.setColumnHidden(3, not self.expert_mode)
1074 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1075 l.setColumnWidth(i, width)
1077 if self.current_account is None:
1078 account_items = self.wallet.accounts.items()
1079 elif self.current_account != -1:
1080 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1084 for k, account in account_items:
1085 name = account.get('name',str(k))
1086 c,u = self.wallet.get_account_balance(k)
1087 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1088 l.addTopLevelItem(account_item)
1089 account_item.setExpanded(True)
1091 for is_change in ([0,1] if self.expert_mode else [0]):
1092 if self.expert_mode:
1093 name = "Receiving" if not is_change else "Change"
1094 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1095 account_item.addChild(seq_item)
1096 if not is_change: seq_item.setExpanded(True)
1098 seq_item = account_item
1102 for address in account[is_change]:
1103 h = self.wallet.history.get(address,[])
1107 if gap > self.wallet.gap_limit:
1112 num_tx = '*' if h == ['*'] else "%d"%len(h)
1113 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1114 self.update_receive_item(item)
1116 item.setBackgroundColor(1, QColor('red'))
1117 seq_item.addChild(item)
1120 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1121 c,u = self.wallet.get_imported_balance()
1122 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1123 l.addTopLevelItem(account_item)
1124 account_item.setExpanded(True)
1125 for address in self.wallet.imported_keys.keys():
1126 item = QTreeWidgetItem( [ address, '', '', ''] )
1127 self.update_receive_item(item)
1128 account_item.addChild(item)
1131 # we use column 1 because column 0 may be hidden
1132 l.setCurrentItem(l.topLevelItem(0),1)
1135 def update_contacts_tab(self):
1137 l = self.contacts_list
1140 for address in self.wallet.addressbook:
1141 label = self.wallet.labels.get(address,'')
1142 n = self.wallet.get_num_tx(address)
1143 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1144 item.setFont(0, QFont(MONOSPACE_FONT))
1145 # 32 = label can be edited (bool)
1146 item.setData(0,32, True)
1148 item.setData(0,33, address)
1149 l.addTopLevelItem(item)
1151 self.run_hook('update_contacts_tab', l)
1152 l.setCurrentItem(l.topLevelItem(0))
1156 def create_console_tab(self):
1157 from qt_console import Console
1158 self.console = console = Console()
1159 self.console.history = self.config.get("console-history",[])
1160 self.console.history_index = len(self.console.history)
1162 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1163 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1165 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1167 def mkfunc(f, method):
1168 return lambda *args: apply( f, (method, args, self.password_dialog ))
1170 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1171 methods[m] = mkfunc(c._run, m)
1173 console.updateNamespace(methods)
1176 def change_account(self,s):
1177 if s == _("All accounts"):
1178 self.current_account = None
1180 accounts = self.wallet.get_accounts()
1181 for k, v in accounts.items():
1183 self.current_account = k
1184 self.update_history_tab()
1185 self.update_status()
1186 self.update_receive_tab()
1188 def create_status_bar(self):
1189 self.status_text = ""
1191 sb.setFixedHeight(35)
1192 qtVersion = qVersion()
1194 update_notification = UpdateLabel(self.config)
1195 if(update_notification.new_version):
1196 sb.addPermanentWidget(update_notification)
1198 accounts = self.wallet.get_accounts()
1199 if len(accounts) > 1:
1200 from_combo = QComboBox()
1201 from_combo.addItems([_("All accounts")] + accounts.values())
1202 from_combo.setCurrentIndex(0)
1203 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1204 sb.addPermanentWidget(from_combo)
1206 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1207 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1208 if self.wallet.seed:
1209 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1210 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1211 sb.addPermanentWidget( self.password_button )
1212 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1213 if self.wallet.seed:
1214 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1215 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1216 sb.addPermanentWidget( self.status_button )
1218 self.run_hook('create_status_bar', (sb,))
1220 self.setStatusBar(sb)
1224 self.config.set_key('gui', 'lite', True)
1227 self.lite.mini.show()
1229 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1230 self.lite.main(None)
1232 def new_contact_dialog(self):
1233 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1234 address = unicode(text)
1236 if is_valid(address):
1237 self.wallet.addressbook.append(address)
1239 self.update_contacts_tab()
1240 self.update_history_tab()
1241 self.update_completions()
1243 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1245 def show_master_public_key(self):
1246 dialog = QDialog(self)
1248 dialog.setWindowTitle(_("Master Public Key"))
1250 main_text = QTextEdit()
1251 main_text.setText(self.wallet.get_master_public_key())
1252 main_text.setReadOnly(True)
1253 main_text.setMaximumHeight(170)
1254 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1256 ok_button = QPushButton(_("OK"))
1257 ok_button.setDefault(True)
1258 ok_button.clicked.connect(dialog.accept)
1260 main_layout = QGridLayout()
1261 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1263 main_layout.addWidget(main_text, 1, 0)
1264 main_layout.addWidget(qrw, 1, 1 )
1266 vbox = QVBoxLayout()
1267 vbox.addLayout(main_layout)
1268 hbox = QHBoxLayout()
1270 hbox.addWidget(ok_button)
1271 vbox.addLayout(hbox)
1273 dialog.setLayout(vbox)
1278 def show_seed_dialog(self, password):
1279 if not self.wallet.seed:
1280 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1283 seed = self.wallet.decode_seed(password)
1285 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1287 self.show_seed(seed, self.wallet.imported_keys, self)
1291 def show_seed(self, seed, imported_keys, parent=None):
1292 dialog = QDialog(parent)
1294 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1296 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1298 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1300 seed_text = QTextEdit(brainwallet)
1301 seed_text.setReadOnly(True)
1302 seed_text.setMaximumHeight(130)
1304 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1305 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1306 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1307 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1309 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1310 label2 = QLabel(msg2)
1311 label2.setWordWrap(True)
1314 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1315 logo.setMaximumWidth(60)
1317 qrw = QRCodeWidget(seed)
1319 ok_button = QPushButton(_("OK"))
1320 ok_button.setDefault(True)
1321 ok_button.clicked.connect(dialog.accept)
1323 grid = QGridLayout()
1324 #main_layout.addWidget(logo, 0, 0)
1326 grid.addWidget(logo, 0, 0)
1327 grid.addWidget(label1, 0, 1)
1329 grid.addWidget(seed_text, 1, 0, 1, 2)
1331 grid.addWidget(qrw, 0, 2, 2, 1)
1333 vbox = QVBoxLayout()
1334 vbox.addLayout(grid)
1335 vbox.addWidget(label2)
1337 hbox = QHBoxLayout()
1339 hbox.addWidget(ok_button)
1340 vbox.addLayout(hbox)
1342 dialog.setLayout(vbox)
1345 def show_qrcode(self, data, title = "QR code"):
1349 d.setWindowTitle(title)
1350 d.setMinimumSize(270, 300)
1351 vbox = QVBoxLayout()
1352 qrw = QRCodeWidget(data)
1353 vbox.addWidget(qrw, 1)
1354 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1355 hbox = QHBoxLayout()
1359 filename = "qrcode.bmp"
1360 bmp.save_qrcode(qrw.qr, filename)
1361 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1363 b = QPushButton(_("Save"))
1365 b.clicked.connect(print_qr)
1367 b = QPushButton(_("Close"))
1369 b.clicked.connect(d.accept)
1372 vbox.addLayout(hbox)
1377 def do_protect(self, func, args):
1378 if self.wallet.use_encryption:
1379 password = self.password_dialog()
1385 if args != (False,):
1386 args = (self,) + args + (password,)
1388 args = (self,password)
1393 def show_private_key(self, address, password):
1394 if not address: return
1396 pk = self.wallet.get_private_key(address, password)
1397 except BaseException, e:
1398 self.show_message(str(e))
1400 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1404 def do_sign(self, address, message, signature, password):
1406 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1407 signature.setText(sig)
1408 except BaseException, e:
1409 self.show_message(str(e))
1411 def sign_message(self, address):
1412 if not address: return
1415 d.setWindowTitle(_('Sign Message'))
1416 d.setMinimumSize(410, 290)
1418 tab_widget = QTabWidget()
1420 layout = QGridLayout(tab)
1422 sign_address = QLineEdit()
1424 sign_address.setText(address)
1425 layout.addWidget(QLabel(_('Address')), 1, 0)
1426 layout.addWidget(sign_address, 1, 1)
1428 sign_message = QTextEdit()
1429 layout.addWidget(QLabel(_('Message')), 2, 0)
1430 layout.addWidget(sign_message, 2, 1)
1431 layout.setRowStretch(2,3)
1433 sign_signature = QTextEdit()
1434 layout.addWidget(QLabel(_('Signature')), 3, 0)
1435 layout.addWidget(sign_signature, 3, 1)
1436 layout.setRowStretch(3,1)
1439 hbox = QHBoxLayout()
1440 b = QPushButton(_("Sign"))
1442 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1443 b = QPushButton(_("Close"))
1444 b.clicked.connect(d.accept)
1446 layout.addLayout(hbox, 4, 1)
1447 tab_widget.addTab(tab, _("Sign"))
1451 layout = QGridLayout(tab)
1453 verify_address = QLineEdit()
1454 layout.addWidget(QLabel(_('Address')), 1, 0)
1455 layout.addWidget(verify_address, 1, 1)
1457 verify_message = QTextEdit()
1458 layout.addWidget(QLabel(_('Message')), 2, 0)
1459 layout.addWidget(verify_message, 2, 1)
1460 layout.setRowStretch(2,3)
1462 verify_signature = QTextEdit()
1463 layout.addWidget(QLabel(_('Signature')), 3, 0)
1464 layout.addWidget(verify_signature, 3, 1)
1465 layout.setRowStretch(3,1)
1469 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1470 self.show_message(_("Signature verified"))
1471 except BaseException, e:
1472 self.show_message(str(e))
1475 hbox = QHBoxLayout()
1476 b = QPushButton(_("Verify"))
1477 b.clicked.connect(do_verify)
1479 b = QPushButton(_("Close"))
1480 b.clicked.connect(d.accept)
1482 layout.addLayout(hbox, 4, 1)
1483 tab_widget.addTab(tab, _("Verify"))
1485 vbox = QVBoxLayout()
1486 vbox.addWidget(tab_widget)
1493 def question(self, msg):
1494 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1496 def show_message(self, msg):
1497 QMessageBox.information(self, _('Message'), msg, _('OK'))
1499 def password_dialog(self ):
1506 vbox = QVBoxLayout()
1507 msg = _('Please enter your password')
1508 vbox.addWidget(QLabel(msg))
1510 grid = QGridLayout()
1512 grid.addWidget(QLabel(_('Password')), 1, 0)
1513 grid.addWidget(pw, 1, 1)
1514 vbox.addLayout(grid)
1516 vbox.addLayout(ok_cancel_buttons(d))
1519 self.run_hook('password_dialog', pw, grid, 1)
1520 if not d.exec_(): return
1521 return unicode(pw.text())
1528 def change_password_dialog( wallet, parent=None ):
1531 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1539 new_pw = QLineEdit()
1540 new_pw.setEchoMode(2)
1541 conf_pw = QLineEdit()
1542 conf_pw.setEchoMode(2)
1544 vbox = QVBoxLayout()
1546 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1547 +_('To disable wallet encryption, enter an empty new password.')) \
1548 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1550 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1551 +_("Leave these fields empty if you want to disable encryption.")
1552 vbox.addWidget(QLabel(msg))
1554 grid = QGridLayout()
1557 if wallet.use_encryption:
1558 grid.addWidget(QLabel(_('Password')), 1, 0)
1559 grid.addWidget(pw, 1, 1)
1561 grid.addWidget(QLabel(_('New Password')), 2, 0)
1562 grid.addWidget(new_pw, 2, 1)
1564 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1565 grid.addWidget(conf_pw, 3, 1)
1566 vbox.addLayout(grid)
1568 vbox.addLayout(ok_cancel_buttons(d))
1571 if not d.exec_(): return
1573 password = unicode(pw.text()) if wallet.use_encryption else None
1574 new_password = unicode(new_pw.text())
1575 new_password2 = unicode(conf_pw.text())
1578 seed = wallet.decode_seed(password)
1580 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1583 if new_password != new_password2:
1584 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1585 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1587 wallet.update_password(seed, password, new_password)
1589 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1590 parent.password_button.setIcon( icon )
1594 def generate_transaction_information_widget(self, tx):
1595 tabs = QTabWidget(self)
1598 grid_ui = QGridLayout(tab1)
1599 grid_ui.setColumnStretch(0,1)
1600 tabs.addTab(tab1, _('Outputs') )
1602 tree_widget = MyTreeWidget(self)
1603 tree_widget.setColumnCount(2)
1604 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1605 tree_widget.setColumnWidth(0, 300)
1606 tree_widget.setColumnWidth(1, 50)
1608 for address, value in tx.outputs:
1609 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1610 tree_widget.addTopLevelItem(item)
1612 tree_widget.setMaximumHeight(100)
1614 grid_ui.addWidget(tree_widget)
1617 grid_ui = QGridLayout(tab2)
1618 grid_ui.setColumnStretch(0,1)
1619 tabs.addTab(tab2, _('Inputs') )
1621 tree_widget = MyTreeWidget(self)
1622 tree_widget.setColumnCount(2)
1623 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1625 for input_line in tx.inputs:
1626 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1627 tree_widget.addTopLevelItem(item)
1629 tree_widget.setMaximumHeight(100)
1631 grid_ui.addWidget(tree_widget)
1635 def tx_dict_from_text(self, txt):
1637 tx_dict = json.loads(str(txt))
1638 assert "hex" in tx_dict.keys()
1639 assert "complete" in tx_dict.keys()
1640 if not tx_dict["complete"]:
1641 assert "input_info" in tx_dict.keys()
1643 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1648 def read_tx_from_file(self):
1649 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1653 with open(fileName, "r") as f:
1654 file_content = f.read()
1655 except (ValueError, IOError, os.error), reason:
1656 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1658 return self.tx_dict_from_text(file_content)
1662 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1664 self.wallet.signrawtransaction(tx, input_info, [], password)
1666 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1668 with open(fileName, "w+") as f:
1669 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1670 self.show_message(_("Transaction saved successfully"))
1673 except BaseException, e:
1674 self.show_message(str(e))
1677 def send_raw_transaction(self, raw_tx, dialog = ""):
1678 result, result_message = self.wallet.sendtx( raw_tx )
1680 self.show_message("Transaction successfully sent: %s" % (result_message))
1684 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1686 def do_process_from_text(self):
1687 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1690 tx_dict = self.tx_dict_from_text(text)
1692 self.create_process_transaction_window(tx_dict)
1694 def do_process_from_file(self):
1695 tx_dict = self.read_tx_from_file()
1697 self.create_process_transaction_window(tx_dict)
1699 def create_process_transaction_window(self, tx_dict):
1700 tx = Transaction(tx_dict["hex"])
1702 dialog = QDialog(self)
1703 dialog.setMinimumWidth(500)
1704 dialog.setWindowTitle(_('Process raw transaction'))
1710 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1711 l.addWidget(QLabel(_("Actions")), 4,0)
1713 if tx_dict["complete"] == False:
1714 l.addWidget(QLabel(_("Unsigned")), 3,1)
1715 if self.wallet.seed :
1716 b = QPushButton("Sign transaction")
1717 input_info = json.loads(tx_dict["input_info"])
1718 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1719 l.addWidget(b, 4, 1)
1721 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1723 l.addWidget(QLabel(_("Signed")), 3,1)
1724 b = QPushButton("Broadcast transaction")
1725 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1728 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1729 cancelButton = QPushButton(_("Cancel"))
1730 cancelButton.clicked.connect(lambda: dialog.done(0))
1731 l.addWidget(cancelButton, 4,2)
1737 def do_export_privkeys(self, password):
1738 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.")))
1741 select_export = _('Select file to export your private keys to')
1742 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1744 with open(fileName, "w+") as csvfile:
1745 transaction = csv.writer(csvfile)
1746 transaction.writerow(["address", "private_key"])
1749 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1750 transaction.writerow(["%34s"%addr,pk])
1752 self.show_message(_("Private keys exported."))
1754 except (IOError, os.error), reason:
1755 export_error_label = _("Electrum was unable to produce a private key-export.")
1756 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1758 except BaseException, e:
1759 self.show_message(str(e))
1763 def do_import_labels(self):
1764 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1765 if not labelsFile: return
1767 f = open(labelsFile, 'r')
1770 for key, value in json.loads(data).items():
1771 self.wallet.labels[key] = value
1773 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1774 except (IOError, os.error), reason:
1775 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1778 def do_export_labels(self):
1779 labels = self.wallet.labels
1781 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1783 with open(fileName, 'w+') as f:
1784 json.dump(labels, f)
1785 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1786 except (IOError, os.error), reason:
1787 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1790 def do_export_history(self):
1791 from gui_lite import csv_transaction
1792 csv_transaction(self.wallet)
1796 def do_import_privkey(self, password):
1797 if not self.wallet.imported_keys:
1798 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1799 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1800 + _('Are you sure you understand what you are doing?'), 3, 4)
1803 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1806 text = str(text).split()
1811 addr = self.wallet.import_key(key, password)
1812 except BaseException as e:
1818 addrlist.append(addr)
1820 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1822 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1823 self.update_receive_tab()
1824 self.update_history_tab()
1827 def settings_dialog(self):
1829 d.setWindowTitle(_('Electrum Settings'))
1831 vbox = QVBoxLayout()
1833 tabs = QTabWidget(self)
1834 self.settings_tab = tabs
1835 vbox.addWidget(tabs)
1838 grid_ui = QGridLayout(tab1)
1839 grid_ui.setColumnStretch(0,1)
1840 tabs.addTab(tab1, _('Display') )
1842 nz_label = QLabel(_('Display zeros'))
1843 grid_ui.addWidget(nz_label, 0, 0)
1844 nz_e = AmountEdit(None,True)
1845 nz_e.setText("%d"% self.wallet.num_zeros)
1846 grid_ui.addWidget(nz_e, 0, 1)
1847 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1848 grid_ui.addWidget(HelpButton(msg), 0, 2)
1849 if not self.config.is_modifiable('num_zeros'):
1850 for w in [nz_e, nz_label]: w.setEnabled(False)
1852 lang_label=QLabel(_('Language') + ':')
1853 grid_ui.addWidget(lang_label, 1, 0)
1854 lang_combo = QComboBox()
1855 from i18n import languages
1856 lang_combo.addItems(languages.values())
1858 index = languages.keys().index(self.config.get("language",''))
1861 lang_combo.setCurrentIndex(index)
1862 grid_ui.addWidget(lang_combo, 1, 1)
1863 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1864 if not self.config.is_modifiable('language'):
1865 for w in [lang_combo, lang_label]: w.setEnabled(False)
1867 currencies = self.exchanger.get_currencies()
1868 currencies.insert(0, "None")
1870 cur_label=QLabel(_('Currency') + ':')
1871 grid_ui.addWidget(cur_label , 2, 0)
1872 cur_combo = QComboBox()
1873 cur_combo.addItems(currencies)
1875 index = currencies.index(self.config.get('currency', "None"))
1878 cur_combo.setCurrentIndex(index)
1879 grid_ui.addWidget(cur_combo, 2, 1)
1880 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1882 expert_cb = QCheckBox(_('Expert mode'))
1883 expert_cb.setChecked(self.expert_mode)
1884 grid_ui.addWidget(expert_cb, 3, 0)
1885 hh = _('In expert mode, your client will:') + '\n' \
1886 + _(' - Show change addresses in the Receive tab') + '\n' \
1887 + _(' - Display the balance of each address') + '\n' \
1888 + _(' - Add freeze/prioritize actions to addresses.')
1889 grid_ui.addWidget(HelpButton(hh), 3, 2)
1890 grid_ui.setRowStretch(4,1)
1894 grid_wallet = QGridLayout(tab2)
1895 grid_wallet.setColumnStretch(0,1)
1896 tabs.addTab(tab2, _('Wallet') )
1898 fee_label = QLabel(_('Transaction fee'))
1899 grid_wallet.addWidget(fee_label, 0, 0)
1900 fee_e = AmountEdit(self.base_unit)
1901 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1902 grid_wallet.addWidget(fee_e, 0, 2)
1903 msg = _('Fee per kilobyte of transaction.') + ' ' \
1904 + _('Recommended value') + ': ' + self.format_amount(20000)
1905 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1906 if not self.config.is_modifiable('fee_per_kb'):
1907 for w in [fee_e, fee_label]: w.setEnabled(False)
1909 usechange_cb = QCheckBox(_('Use change addresses'))
1910 usechange_cb.setChecked(self.wallet.use_change)
1911 grid_wallet.addWidget(usechange_cb, 1, 0)
1912 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1913 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1915 gap_label = QLabel(_('Gap limit'))
1916 grid_wallet.addWidget(gap_label, 2, 0)
1917 gap_e = AmountEdit(None,True)
1918 gap_e.setText("%d"% self.wallet.gap_limit)
1919 grid_wallet.addWidget(gap_e, 2, 2)
1920 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1921 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1922 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1923 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1924 + _('Warning') + ': ' \
1925 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1926 + _('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'
1927 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1928 if not self.config.is_modifiable('gap_limit'):
1929 for w in [gap_e, gap_label]: w.setEnabled(False)
1931 units = ['BTC', 'mBTC']
1932 unit_label = QLabel(_('Base unit'))
1933 grid_wallet.addWidget(unit_label, 3, 0)
1934 unit_combo = QComboBox()
1935 unit_combo.addItems(units)
1936 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1937 grid_wallet.addWidget(unit_combo, 3, 2)
1938 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1939 + '\n1BTC=1000mBTC.\n' \
1940 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1941 grid_wallet.setRowStretch(4,1)
1946 grid_io = QGridLayout(tab3)
1947 grid_io.setColumnStretch(0,1)
1948 tabs.addTab(tab3, _('Import/Export') )
1950 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1951 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1952 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1953 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1955 grid_io.addWidget(QLabel(_('History')), 2, 0)
1956 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1957 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1959 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1961 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1962 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1963 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1965 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1966 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1967 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1968 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1969 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1972 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1973 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1974 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1975 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1977 grid_io.setRowStretch(6,1)
1982 tab5 = QScrollArea()
1983 tab5.setEnabled(True)
1984 tab5.setWidgetResizable(True)
1986 grid_plugins = QGridLayout()
1987 grid_plugins.setColumnStretch(0,1)
1990 w.setLayout(grid_plugins)
1992 tab5.setMaximumSize(tab3.size()) # optional
1994 w.setMinimumHeight(len(self.plugins)*35)
1996 tabs.addTab(tab5, _('Plugins') )
1997 def mk_toggle(cb, p):
1998 return lambda: cb.setChecked(p.toggle())
1999 for i, p in enumerate(self.plugins):
2001 name, description = p.get_info()
2002 cb = QCheckBox(name)
2003 cb.setDisabled(not p.is_available())
2004 cb.setChecked(p.is_enabled())
2005 cb.clicked.connect(mk_toggle(cb,p))
2006 grid_plugins.addWidget(cb, i, 0)
2007 if p.requires_settings():
2008 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2009 grid_plugins.addWidget(HelpButton(description), i, 2)
2011 print_msg("Error: cannot display plugin", p)
2012 traceback.print_exc(file=sys.stdout)
2013 grid_plugins.setRowStretch(i+1,1)
2015 self.run_hook('create_settings_tab', tabs)
2017 vbox.addLayout(ok_cancel_buttons(d))
2021 if not d.exec_(): return
2023 fee = unicode(fee_e.text())
2025 fee = self.read_amount(fee)
2027 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2030 if self.wallet.fee != fee:
2031 self.wallet.fee = fee
2034 nz = unicode(nz_e.text())
2039 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2042 if self.wallet.num_zeros != nz:
2043 self.wallet.num_zeros = nz
2044 self.config.set_key('num_zeros', nz, True)
2045 self.update_history_tab()
2046 self.update_receive_tab()
2048 usechange_result = usechange_cb.isChecked()
2049 if self.wallet.use_change != usechange_result:
2050 self.wallet.use_change = usechange_result
2051 self.config.set_key('use_change', self.wallet.use_change, True)
2053 unit_result = units[unit_combo.currentIndex()]
2054 if self.base_unit() != unit_result:
2055 self.decimal_point = 8 if unit_result == 'BTC' else 5
2056 self.config.set_key('decimal_point', self.decimal_point, True)
2057 self.update_history_tab()
2058 self.update_status()
2061 n = int(gap_e.text())
2063 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2066 if self.wallet.gap_limit != n:
2067 r = self.wallet.change_gap_limit(n)
2069 self.update_receive_tab()
2070 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2072 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2074 need_restart = False
2076 lang_request = languages.keys()[lang_combo.currentIndex()]
2077 if lang_request != self.config.get('language'):
2078 self.config.set_key("language", lang_request, True)
2081 cur_request = str(currencies[cur_combo.currentIndex()])
2082 if cur_request != self.config.get('currency', "None"):
2083 self.config.set_key('currency', cur_request, True)
2084 self.update_wallet()
2086 self.run_hook('close_settings_dialog')
2089 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2091 self.receive_tab_set_mode(expert_cb.isChecked())
2093 def run_network_dialog(self):
2094 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2096 def closeEvent(self, event):
2098 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2099 self.save_column_widths()
2100 self.config.set_key("console-history",self.console.history[-50:])
2109 def __init__(self, wallet, config, app=None):
2110 self.wallet = wallet
2111 self.config = config
2113 self.app = QApplication(sys.argv)
2116 def restore_or_create(self):
2117 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2118 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2119 if r==2: return None
2120 return 'restore' if r==1 else 'create'
2123 def verify_seed(self):
2124 r = self.seed_dialog(False)
2125 if r != self.wallet.seed:
2126 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2133 def seed_dialog(self, is_restore=True):
2137 vbox = QVBoxLayout()
2139 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2141 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2143 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2146 label.setWordWrap(True)
2147 vbox.addWidget(label)
2149 seed_e = QTextEdit()
2150 seed_e.setMaximumHeight(100)
2151 vbox.addWidget(seed_e)
2154 grid = QGridLayout()
2156 gap_e = AmountEdit(None, True)
2158 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2159 grid.addWidget(gap_e, 2, 1)
2160 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2161 vbox.addLayout(grid)
2163 vbox.addLayout(ok_cancel_buttons(d))
2166 if not d.exec_(): return
2169 seed = str(seed_e.toPlainText())
2173 seed = mnemonic.mn_decode( seed.split() )
2175 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2179 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2186 gap = int(unicode(gap_e.text()))
2188 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2193 def network_dialog(self):
2194 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2197 def show_seed(self):
2198 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2200 def password_dialog(self):
2201 if self.wallet.seed:
2202 ElectrumWindow.change_password_dialog(self.wallet)
2205 def restore_wallet(self):
2206 wallet = self.wallet
2207 # wait until we are connected, because the user might have selected another server
2208 if not wallet.interface.is_connected:
2209 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2210 waiting_dialog(waiting)
2212 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2213 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2215 wallet.set_up_to_date(False)
2216 wallet.interface.poke('synchronizer')
2217 waiting_dialog(waiting)
2218 if wallet.is_found():
2219 print_error( "Recovery successful" )
2221 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2228 w = ElectrumWindow(self.wallet, self.config)
2229 if url: w.set_url(url)