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 )
282 electrum_menu = menubar.addMenu(_("&File"))
283 preferences_name = _("Preferences")
284 if sys.platform == 'darwin':
285 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
287 preferences_menu = electrum_menu.addAction(preferences_name)
288 electrum_menu.addSeparator()
290 accounts_menu = electrum_menu.addMenu(_("&Accounts"))
291 accounts_menu.addAction(_("All accounts"))
292 accounts_menu.addAction(_("Main account"))
293 accounts_menu.addAction(_("Imported keys"))
295 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
296 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
297 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
299 electrum_menu.addSeparator()
300 quit_item = electrum_menu.addAction(_("&Close"))
302 wallet_menu = menubar.addMenu(_("&Wallet"))
303 wallet_backup = wallet_menu.addAction(_("&Create backup"))
304 show_seed = wallet_menu.addAction(_("&Show seed"))
305 chnage_password = wallet_menu.addAction(_("&Password"))
306 wallet_menu.addSeparator()
307 new_contact = wallet_menu.addAction(_("&New contact"))
309 import_menu = menubar.addMenu(_("&Import"))
310 in_labels = import_menu.addAction(_("&Labels"))
311 in_private_keys = import_menu.addAction(_("&Private key"))
313 export_menu = menubar.addMenu(_("&Export"))
314 ex_private_keys = export_menu.addAction(_("&Private keys"))
315 ex_history = export_menu.addAction(_("&History"))
316 ex_labels = export_menu.addAction(_("&Labels"))
318 help_menu = menubar.addMenu(_("&Help"))
319 help_menu.addAction(_("&About"))
320 help_menu.addAction(_("&Documentation")) # http://electrum.org/documentation.html
321 help_menu.addAction(_("&Official website"))
323 self.setMenuBar(menubar)
326 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
327 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
328 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
329 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
331 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
332 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
333 self.history_list.setFocus(True)
335 self.exchanger = exchange_rate.Exchanger(self)
336 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
338 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
339 if platform.system() == 'Windows':
340 n = 3 if self.wallet.seed else 2
341 tabs.setCurrentIndex (n)
342 tabs.setCurrentIndex (0)
345 if self.wallet.fee < 50000:
346 self.wallet.set_fee(50000)
347 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
349 # set initial message
350 self.console.showMessage(self.wallet.interface.banner)
352 # plugins that need to change the GUI do it here
353 self.run_hook('init_gui')
357 def init_plugins(self):
358 import imp, pkgutil, __builtin__
359 if __builtin__.use_local_modules:
360 fp, pathname, description = imp.find_module('plugins')
361 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
362 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
363 imp.load_module('electrum_plugins', fp, pathname, description)
364 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
366 import electrum_plugins
367 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
368 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
373 self.plugins.append( p.Plugin(self) )
375 print_msg("Error:cannot initialize plugin",p)
376 traceback.print_exc(file=sys.stdout)
379 def run_hook(self, name, *args):
380 for p in self.plugins:
381 if not p.is_enabled():
390 print_error("Plugin error")
391 traceback.print_exc(file=sys.stdout)
396 def set_label(self, name, text = None):
398 old_text = self.wallet.labels.get(name)
401 self.wallet.labels[name] = text
405 self.wallet.labels.pop(name)
407 self.run_hook('set_label', name, text, changed)
411 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
412 def getOpenFileName(self, title, filter = None):
413 directory = self.config.get('io_dir', os.path.expanduser('~'))
414 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
415 if fileName and directory != os.path.dirname(fileName):
416 self.config.set_key('io_dir', os.path.dirname(fileName), True)
419 def getSaveFileName(self, title, filename, filter = None):
420 directory = self.config.get('io_dir', os.path.expanduser('~'))
421 path = os.path.join( directory, filename )
422 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
423 if fileName and directory != os.path.dirname(fileName):
424 self.config.set_key('io_dir', os.path.dirname(fileName), True)
430 QMainWindow.close(self)
431 self.run_hook('close_main_window')
433 def connect_slots(self, sender):
434 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
435 self.previous_payto_e=''
437 def timer_actions(self):
438 if self.need_update.is_set():
440 self.need_update.clear()
441 self.run_hook('timer_actions')
443 def format_amount(self, x, is_diff=False):
444 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
446 def read_amount(self, x):
447 if x in['.', '']: return None
448 p = pow(10, self.decimal_point)
449 return int( p * Decimal(x) )
452 assert self.decimal_point in [5,8]
453 return "BTC" if self.decimal_point == 8 else "mBTC"
455 def update_status(self):
456 if self.wallet.interface and self.wallet.interface.is_connected:
457 if not self.wallet.up_to_date:
458 text = _("Synchronizing...")
459 icon = QIcon(":icons/status_waiting.png")
461 c, u = self.wallet.get_account_balance(self.current_account)
462 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
463 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
464 text += self.create_quote_text(Decimal(c+u)/100000000)
465 icon = QIcon(":icons/status_connected.png")
467 text = _("Not connected")
468 icon = QIcon(":icons/status_disconnected.png")
470 self.balance_label.setText(text)
471 self.status_button.setIcon( icon )
473 def update_wallet(self):
475 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
476 self.update_history_tab()
477 self.update_receive_tab()
478 self.update_contacts_tab()
479 self.update_completions()
482 def create_quote_text(self, btc_balance):
483 quote_currency = self.config.get("currency", "None")
484 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
485 if quote_balance is None:
488 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
491 def create_history_tab(self):
492 self.history_list = l = MyTreeWidget(self)
494 for i,width in enumerate(self.column_widths['history']):
495 l.setColumnWidth(i, width)
496 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
497 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
498 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
500 l.setContextMenuPolicy(Qt.CustomContextMenu)
501 l.customContextMenuRequested.connect(self.create_history_menu)
505 def create_history_menu(self, position):
506 self.history_list.selectedIndexes()
507 item = self.history_list.currentItem()
509 tx_hash = str(item.data(0, Qt.UserRole).toString())
510 if not tx_hash: return
512 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
513 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
514 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
515 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
518 def show_tx_details(self, tx):
519 dialog = QDialog(self)
521 dialog.setWindowTitle(_("Transaction Details"))
523 dialog.setLayout(vbox)
524 dialog.setMinimumSize(600,300)
527 if tx_hash in self.wallet.transactions.keys():
528 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
529 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
531 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
537 vbox.addWidget(QLabel("Transaction ID:"))
538 e = QLineEdit(tx_hash)
542 vbox.addWidget(QLabel("Date: %s"%time_str))
543 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
546 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
547 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
549 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
550 vbox.addWidget(QLabel("Transaction fee: unknown"))
552 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
554 vbox.addWidget( self.generate_transaction_information_widget(tx) )
556 ok_button = QPushButton(_("Close"))
557 ok_button.setDefault(True)
558 ok_button.clicked.connect(dialog.accept)
562 hbox.addWidget(ok_button)
566 def tx_label_clicked(self, item, column):
567 if column==2 and item.isSelected():
569 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
570 self.history_list.editItem( item, column )
571 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
574 def tx_label_changed(self, item, column):
578 tx_hash = str(item.data(0, Qt.UserRole).toString())
579 tx = self.wallet.transactions.get(tx_hash)
580 text = unicode( item.text(2) )
581 self.set_label(tx_hash, text)
583 item.setForeground(2, QBrush(QColor('black')))
585 text = self.wallet.get_default_label(tx_hash)
586 item.setText(2, text)
587 item.setForeground(2, QBrush(QColor('gray')))
591 def edit_label(self, is_recv):
592 l = self.receive_list if is_recv else self.contacts_list
593 item = l.currentItem()
594 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
595 l.editItem( item, 1 )
596 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
600 def address_label_clicked(self, item, column, l, column_addr, column_label):
601 if column == column_label and item.isSelected():
602 is_editable = item.data(0, 32).toBool()
605 addr = unicode( item.text(column_addr) )
606 label = unicode( item.text(column_label) )
607 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
608 l.editItem( item, column )
609 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
612 def address_label_changed(self, item, column, l, column_addr, column_label):
613 if column == column_label:
614 addr = unicode( item.text(column_addr) )
615 text = unicode( item.text(column_label) )
616 is_editable = item.data(0, 32).toBool()
620 changed = self.set_label(addr, text)
622 self.update_history_tab()
623 self.update_completions()
625 self.current_item_changed(item)
627 self.run_hook('item_changed', item, column)
630 def current_item_changed(self, a):
631 self.run_hook('current_item_changed', a)
635 def update_history_tab(self):
637 self.history_list.clear()
638 for item in self.wallet.get_tx_history(self.current_account):
639 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
642 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
647 time_str = 'unverified'
648 icon = QIcon(":icons/unconfirmed.png")
651 icon = QIcon(":icons/unconfirmed.png")
653 icon = QIcon(":icons/clock%d.png"%conf)
655 icon = QIcon(":icons/confirmed.png")
657 if value is not None:
658 v_str = self.format_amount(value, True)
662 balance_str = self.format_amount(balance)
665 label, is_default_label = self.wallet.get_label(tx_hash)
667 label = _('Pruned transaction outputs')
668 is_default_label = False
670 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
671 item.setFont(2, QFont(MONOSPACE_FONT))
672 item.setFont(3, QFont(MONOSPACE_FONT))
673 item.setFont(4, QFont(MONOSPACE_FONT))
675 item.setForeground(3, QBrush(QColor("#BC1E1E")))
677 item.setData(0, Qt.UserRole, tx_hash)
678 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
680 item.setForeground(2, QBrush(QColor('grey')))
682 item.setIcon(0, icon)
683 self.history_list.insertTopLevelItem(0,item)
686 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
689 def create_send_tab(self):
694 grid.setColumnMinimumWidth(3,300)
695 grid.setColumnStretch(5,1)
698 self.payto_e = QLineEdit()
699 grid.addWidget(QLabel(_('Pay to')), 1, 0)
700 grid.addWidget(self.payto_e, 1, 1, 1, 3)
702 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)
704 completer = QCompleter()
705 completer.setCaseSensitivity(False)
706 self.payto_e.setCompleter(completer)
707 completer.setModel(self.completions)
709 self.message_e = QLineEdit()
710 grid.addWidget(QLabel(_('Description')), 2, 0)
711 grid.addWidget(self.message_e, 2, 1, 1, 3)
712 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)
714 self.amount_e = AmountEdit(self.base_unit)
715 grid.addWidget(QLabel(_('Amount')), 3, 0)
716 grid.addWidget(self.amount_e, 3, 1, 1, 2)
717 grid.addWidget(HelpButton(
718 _('Amount to be sent.') + '\n\n' \
719 + _('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.') \
720 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
722 self.fee_e = AmountEdit(self.base_unit)
723 grid.addWidget(QLabel(_('Fee')), 4, 0)
724 grid.addWidget(self.fee_e, 4, 1, 1, 2)
725 grid.addWidget(HelpButton(
726 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
727 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
728 + _('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)
731 b = EnterButton(_("Send"), self.do_send)
733 b = EnterButton(_("Create unsigned transaction"), self.do_send)
734 grid.addWidget(b, 6, 1)
736 b = EnterButton(_("Clear"),self.do_clear)
737 grid.addWidget(b, 6, 2)
739 self.payto_sig = QLabel('')
740 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
742 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
743 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
752 def entry_changed( is_fee ):
753 self.funds_error = False
755 if self.amount_e.is_shortcut:
756 self.amount_e.is_shortcut = False
757 c, u = self.wallet.get_account_balance(self.current_account)
758 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
759 fee = self.wallet.estimated_fee(inputs)
761 self.amount_e.setText( self.format_amount(amount) )
762 self.fee_e.setText( self.format_amount( fee ) )
765 amount = self.read_amount(str(self.amount_e.text()))
766 fee = self.read_amount(str(self.fee_e.text()))
768 if not is_fee: fee = None
771 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
773 self.fee_e.setText( self.format_amount( fee ) )
776 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
780 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
781 self.funds_error = True
782 text = _( "Not enough funds" )
783 c, u = self.wallet.get_frozen_balance()
784 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
786 self.statusBar().showMessage(text)
787 self.amount_e.setPalette(palette)
788 self.fee_e.setPalette(palette)
790 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
791 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
793 self.run_hook('create_send_tab', grid)
797 def update_completions(self):
799 for addr,label in self.wallet.labels.items():
800 if addr in self.wallet.addressbook:
801 l.append( label + ' <' + addr + '>')
803 self.run_hook('update_completions', l)
804 self.completions.setStringList(l)
808 return lambda s, *args: s.do_protect(func, args)
812 def do_send(self, password):
814 label = unicode( self.message_e.text() )
815 r = unicode( self.payto_e.text() )
818 # label or alias, with address in brackets
819 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
820 to_address = m.group(2) if m else r
822 if not is_valid(to_address):
823 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
827 amount = self.read_amount(unicode( self.amount_e.text()))
829 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
832 fee = self.read_amount(unicode( self.fee_e.text()))
834 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
838 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
839 except BaseException, e:
840 self.show_message(str(e))
843 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
844 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
847 self.run_hook('send_tx', tx)
850 self.set_label(tx.hash(), label)
853 h = self.wallet.send_tx(tx)
854 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
855 status, msg = self.wallet.receive_tx( h )
857 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
859 self.update_contacts_tab()
861 QMessageBox.warning(self, _('Error'), msg, _('OK'))
863 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
865 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
866 with open(fileName,'w') as f:
867 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
868 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
870 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
875 def set_url(self, url):
876 address, amount, label, message, signature, identity, url = util.parse_url(url)
877 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
879 if label and self.wallet.labels.get(address) != label:
880 if self.question('Give label "%s" to address %s ?'%(label,address)):
881 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
882 self.wallet.addressbook.append(address)
883 self.set_label(address, label)
885 self.run_hook('set_url', url, self.show_message, self.question)
887 self.tabs.setCurrentIndex(1)
888 label = self.wallet.labels.get(address)
889 m_addr = label + ' <'+ address +'>' if label else address
890 self.payto_e.setText(m_addr)
892 self.message_e.setText(message)
893 self.amount_e.setText(amount)
895 self.set_frozen(self.payto_e,True)
896 self.set_frozen(self.amount_e,True)
897 self.set_frozen(self.message_e,True)
898 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
900 self.payto_sig.setVisible(False)
903 self.payto_sig.setVisible(False)
904 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
906 self.set_frozen(e,False)
909 def set_frozen(self,entry,frozen):
911 entry.setReadOnly(True)
912 entry.setFrame(False)
914 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
915 entry.setPalette(palette)
917 entry.setReadOnly(False)
920 palette.setColor(entry.backgroundRole(), QColor('white'))
921 entry.setPalette(palette)
924 def toggle_freeze(self,addr):
926 if addr in self.wallet.frozen_addresses:
927 self.wallet.unfreeze(addr)
929 self.wallet.freeze(addr)
930 self.update_receive_tab()
932 def toggle_priority(self,addr):
934 if addr in self.wallet.prioritized_addresses:
935 self.wallet.unprioritize(addr)
937 self.wallet.prioritize(addr)
938 self.update_receive_tab()
941 def create_list_tab(self, headers):
942 "generic tab creation method"
943 l = MyTreeWidget(self)
944 l.setColumnCount( len(headers) )
945 l.setHeaderLabels( headers )
955 vbox.addWidget(buttons)
960 buttons.setLayout(hbox)
965 def create_receive_tab(self):
966 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
967 l.setContextMenuPolicy(Qt.CustomContextMenu)
968 l.customContextMenuRequested.connect(self.create_receive_menu)
969 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
970 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
971 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
972 self.receive_list = l
973 self.receive_buttons_hbox = hbox
978 def receive_tab_set_mode(self, i):
979 self.save_column_widths()
980 self.expert_mode = (i == 1)
981 self.config.set_key('classic_expert_mode', self.expert_mode, True)
982 self.update_receive_tab()
985 def save_column_widths(self):
986 if not self.expert_mode:
987 widths = [ self.receive_list.columnWidth(0) ]
990 for i in range(self.receive_list.columnCount() -1):
991 widths.append(self.receive_list.columnWidth(i))
992 self.column_widths["receive"][self.expert_mode] = widths
994 self.column_widths["history"] = []
995 for i in range(self.history_list.columnCount() - 1):
996 self.column_widths["history"].append(self.history_list.columnWidth(i))
998 self.column_widths["contacts"] = []
999 for i in range(self.contacts_list.columnCount() - 1):
1000 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1002 self.config.set_key("column_widths", self.column_widths, True)
1005 def create_contacts_tab(self):
1006 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1007 l.setContextMenuPolicy(Qt.CustomContextMenu)
1008 l.customContextMenuRequested.connect(self.create_contact_menu)
1009 for i,width in enumerate(self.column_widths['contacts']):
1010 l.setColumnWidth(i, width)
1012 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1013 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1014 self.contacts_list = l
1015 self.contacts_buttons_hbox = hbox
1016 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1021 def delete_imported_key(self, addr):
1022 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1023 self.wallet.delete_imported_key(addr)
1024 self.update_receive_tab()
1025 self.update_history_tab()
1028 def create_receive_menu(self, position):
1029 # fixme: this function apparently has a side effect.
1030 # if it is not called the menu pops up several times
1031 #self.receive_list.selectedIndexes()
1033 item = self.receive_list.itemAt(position)
1035 addr = unicode(item.text(0))
1036 if not is_valid(addr):
1037 item.setExpanded(not item.isExpanded())
1040 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1041 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1042 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1043 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1044 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1045 if addr in self.wallet.imported_keys:
1046 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1048 if self.expert_mode:
1049 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1050 menu.addAction(t, lambda: self.toggle_freeze(addr))
1051 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1052 menu.addAction(t, lambda: self.toggle_priority(addr))
1054 self.run_hook('receive_menu', menu)
1055 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1058 def payto(self, addr):
1060 label = self.wallet.labels.get(addr)
1061 m_addr = label + ' <' + addr + '>' if label else addr
1062 self.tabs.setCurrentIndex(1)
1063 self.payto_e.setText(m_addr)
1064 self.amount_e.setFocus()
1067 def delete_contact(self, x):
1068 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1069 self.wallet.delete_contact(x)
1070 self.set_label(x, None)
1071 self.update_history_tab()
1072 self.update_contacts_tab()
1073 self.update_completions()
1076 def create_contact_menu(self, position):
1077 item = self.contacts_list.itemAt(position)
1079 addr = unicode(item.text(0))
1080 label = unicode(item.text(1))
1081 is_editable = item.data(0,32).toBool()
1082 payto_addr = item.data(0,33).toString()
1084 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1085 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1086 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1088 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1089 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1091 self.run_hook('create_contact_menu', menu, item)
1092 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1095 def update_receive_item(self, item):
1096 item.setFont(0, QFont(MONOSPACE_FONT))
1097 address = str(item.data(0,0).toString())
1098 label = self.wallet.labels.get(address,'')
1099 item.setData(1,0,label)
1100 item.setData(0,32, True) # is editable
1102 self.run_hook('update_receive_item', address, item)
1104 c, u = self.wallet.get_addr_balance(address)
1105 balance = self.format_amount(c + u)
1106 item.setData(2,0,balance)
1108 if self.expert_mode:
1109 if address in self.wallet.frozen_addresses:
1110 item.setBackgroundColor(0, QColor('lightblue'))
1111 elif address in self.wallet.prioritized_addresses:
1112 item.setBackgroundColor(0, QColor('lightgreen'))
1115 def update_receive_tab(self):
1116 l = self.receive_list
1119 l.setColumnHidden(2, not self.expert_mode)
1120 l.setColumnHidden(3, not self.expert_mode)
1121 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1122 l.setColumnWidth(i, width)
1124 if self.current_account is None:
1125 account_items = self.wallet.accounts.items()
1126 elif self.current_account != -1:
1127 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1131 for k, account in account_items:
1132 name = account.get('name',str(k))
1133 c,u = self.wallet.get_account_balance(k)
1134 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1135 l.addTopLevelItem(account_item)
1136 account_item.setExpanded(True)
1138 for is_change in ([0,1] if self.expert_mode else [0]):
1139 if self.expert_mode:
1140 name = "Receiving" if not is_change else "Change"
1141 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1142 account_item.addChild(seq_item)
1143 if not is_change: seq_item.setExpanded(True)
1145 seq_item = account_item
1149 for address in account[is_change]:
1150 h = self.wallet.history.get(address,[])
1154 if gap > self.wallet.gap_limit:
1159 num_tx = '*' if h == ['*'] else "%d"%len(h)
1160 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1161 self.update_receive_item(item)
1163 item.setBackgroundColor(1, QColor('red'))
1164 seq_item.addChild(item)
1167 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1168 c,u = self.wallet.get_imported_balance()
1169 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1170 l.addTopLevelItem(account_item)
1171 account_item.setExpanded(True)
1172 for address in self.wallet.imported_keys.keys():
1173 item = QTreeWidgetItem( [ address, '', '', ''] )
1174 self.update_receive_item(item)
1175 account_item.addChild(item)
1178 # we use column 1 because column 0 may be hidden
1179 l.setCurrentItem(l.topLevelItem(0),1)
1182 def update_contacts_tab(self):
1184 l = self.contacts_list
1187 for address in self.wallet.addressbook:
1188 label = self.wallet.labels.get(address,'')
1189 n = self.wallet.get_num_tx(address)
1190 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1191 item.setFont(0, QFont(MONOSPACE_FONT))
1192 # 32 = label can be edited (bool)
1193 item.setData(0,32, True)
1195 item.setData(0,33, address)
1196 l.addTopLevelItem(item)
1198 self.run_hook('update_contacts_tab', l)
1199 l.setCurrentItem(l.topLevelItem(0))
1203 def create_console_tab(self):
1204 from qt_console import Console
1205 self.console = console = Console()
1206 self.console.history = self.config.get("console-history",[])
1207 self.console.history_index = len(self.console.history)
1209 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1210 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1212 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1214 def mkfunc(f, method):
1215 return lambda *args: apply( f, (method, args, self.password_dialog ))
1217 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1218 methods[m] = mkfunc(c._run, m)
1220 console.updateNamespace(methods)
1223 def change_account(self,s):
1224 if s == _("All accounts"):
1225 self.current_account = None
1227 accounts = self.wallet.get_accounts()
1228 for k, v in accounts.items():
1230 self.current_account = k
1231 self.update_history_tab()
1232 self.update_status()
1233 self.update_receive_tab()
1235 def create_status_bar(self):
1238 sb.setFixedHeight(35)
1239 qtVersion = qVersion()
1241 self.balance_label = QLabel("")
1242 sb.addWidget(self.balance_label)
1244 update_notification = UpdateLabel(self.config)
1245 if(update_notification.new_version):
1246 sb.addPermanentWidget(update_notification)
1248 accounts = self.wallet.get_accounts()
1249 if len(accounts) > 1:
1250 from_combo = QComboBox()
1251 from_combo.addItems([_("All accounts")] + accounts.values())
1252 from_combo.setCurrentIndex(0)
1253 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1254 sb.addPermanentWidget(from_combo)
1256 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1257 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1258 if self.wallet.seed:
1259 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1260 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1261 sb.addPermanentWidget( self.password_button )
1262 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1263 if self.wallet.seed:
1264 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1265 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1266 sb.addPermanentWidget( self.status_button )
1268 self.run_hook('create_status_bar', (sb,))
1270 self.setStatusBar(sb)
1274 self.config.set_key('gui', 'lite', True)
1277 self.lite.mini.show()
1279 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1280 self.lite.main(None)
1282 def new_contact_dialog(self):
1283 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1284 address = unicode(text)
1286 if is_valid(address):
1287 self.wallet.add_contact(address)
1288 self.update_contacts_tab()
1289 self.update_history_tab()
1290 self.update_completions()
1292 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1294 def show_master_public_key(self):
1295 dialog = QDialog(self)
1297 dialog.setWindowTitle(_("Master Public Key"))
1299 main_text = QTextEdit()
1300 main_text.setText(self.wallet.get_master_public_key())
1301 main_text.setReadOnly(True)
1302 main_text.setMaximumHeight(170)
1303 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1305 ok_button = QPushButton(_("OK"))
1306 ok_button.setDefault(True)
1307 ok_button.clicked.connect(dialog.accept)
1309 main_layout = QGridLayout()
1310 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1312 main_layout.addWidget(main_text, 1, 0)
1313 main_layout.addWidget(qrw, 1, 1 )
1315 vbox = QVBoxLayout()
1316 vbox.addLayout(main_layout)
1317 hbox = QHBoxLayout()
1319 hbox.addWidget(ok_button)
1320 vbox.addLayout(hbox)
1322 dialog.setLayout(vbox)
1327 def show_seed_dialog(self, password):
1328 if not self.wallet.seed:
1329 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1332 seed = self.wallet.decode_seed(password)
1334 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1336 self.show_seed(seed, self.wallet.imported_keys, self)
1340 def show_seed(self, seed, imported_keys, parent=None):
1341 dialog = QDialog(parent)
1343 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1345 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1347 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1349 seed_text = QTextEdit(brainwallet)
1350 seed_text.setReadOnly(True)
1351 seed_text.setMaximumHeight(130)
1353 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1354 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1355 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1356 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1358 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1359 label2 = QLabel(msg2)
1360 label2.setWordWrap(True)
1363 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1364 logo.setMaximumWidth(60)
1366 qrw = QRCodeWidget(seed)
1368 ok_button = QPushButton(_("OK"))
1369 ok_button.setDefault(True)
1370 ok_button.clicked.connect(dialog.accept)
1372 grid = QGridLayout()
1373 #main_layout.addWidget(logo, 0, 0)
1375 grid.addWidget(logo, 0, 0)
1376 grid.addWidget(label1, 0, 1)
1378 grid.addWidget(seed_text, 1, 0, 1, 2)
1380 grid.addWidget(qrw, 0, 2, 2, 1)
1382 vbox = QVBoxLayout()
1383 vbox.addLayout(grid)
1384 vbox.addWidget(label2)
1386 hbox = QHBoxLayout()
1388 hbox.addWidget(ok_button)
1389 vbox.addLayout(hbox)
1391 dialog.setLayout(vbox)
1394 def show_qrcode(self, data, title = "QR code"):
1398 d.setWindowTitle(title)
1399 d.setMinimumSize(270, 300)
1400 vbox = QVBoxLayout()
1401 qrw = QRCodeWidget(data)
1402 vbox.addWidget(qrw, 1)
1403 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1404 hbox = QHBoxLayout()
1408 filename = "qrcode.bmp"
1409 bmp.save_qrcode(qrw.qr, filename)
1410 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1412 b = QPushButton(_("Save"))
1414 b.clicked.connect(print_qr)
1416 b = QPushButton(_("Close"))
1418 b.clicked.connect(d.accept)
1421 vbox.addLayout(hbox)
1426 def do_protect(self, func, args):
1427 if self.wallet.use_encryption:
1428 password = self.password_dialog()
1434 if args != (False,):
1435 args = (self,) + args + (password,)
1437 args = (self,password)
1442 def show_private_key(self, address, password):
1443 if not address: return
1445 pk = self.wallet.get_private_key(address, password)
1446 except BaseException, e:
1447 self.show_message(str(e))
1449 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1453 def do_sign(self, address, message, signature, password):
1455 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1456 signature.setText(sig)
1457 except BaseException, e:
1458 self.show_message(str(e))
1460 def sign_message(self, address):
1461 if not address: return
1464 d.setWindowTitle(_('Sign Message'))
1465 d.setMinimumSize(410, 290)
1467 tab_widget = QTabWidget()
1469 layout = QGridLayout(tab)
1471 sign_address = QLineEdit()
1473 sign_address.setText(address)
1474 layout.addWidget(QLabel(_('Address')), 1, 0)
1475 layout.addWidget(sign_address, 1, 1)
1477 sign_message = QTextEdit()
1478 layout.addWidget(QLabel(_('Message')), 2, 0)
1479 layout.addWidget(sign_message, 2, 1)
1480 layout.setRowStretch(2,3)
1482 sign_signature = QTextEdit()
1483 layout.addWidget(QLabel(_('Signature')), 3, 0)
1484 layout.addWidget(sign_signature, 3, 1)
1485 layout.setRowStretch(3,1)
1488 hbox = QHBoxLayout()
1489 b = QPushButton(_("Sign"))
1491 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1492 b = QPushButton(_("Close"))
1493 b.clicked.connect(d.accept)
1495 layout.addLayout(hbox, 4, 1)
1496 tab_widget.addTab(tab, _("Sign"))
1500 layout = QGridLayout(tab)
1502 verify_address = QLineEdit()
1503 layout.addWidget(QLabel(_('Address')), 1, 0)
1504 layout.addWidget(verify_address, 1, 1)
1506 verify_message = QTextEdit()
1507 layout.addWidget(QLabel(_('Message')), 2, 0)
1508 layout.addWidget(verify_message, 2, 1)
1509 layout.setRowStretch(2,3)
1511 verify_signature = QTextEdit()
1512 layout.addWidget(QLabel(_('Signature')), 3, 0)
1513 layout.addWidget(verify_signature, 3, 1)
1514 layout.setRowStretch(3,1)
1517 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1518 self.show_message(_("Signature verified"))
1520 self.show_message(_("Error: wrong signature"))
1522 hbox = QHBoxLayout()
1523 b = QPushButton(_("Verify"))
1524 b.clicked.connect(do_verify)
1526 b = QPushButton(_("Close"))
1527 b.clicked.connect(d.accept)
1529 layout.addLayout(hbox, 4, 1)
1530 tab_widget.addTab(tab, _("Verify"))
1532 vbox = QVBoxLayout()
1533 vbox.addWidget(tab_widget)
1540 def question(self, msg):
1541 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1543 def show_message(self, msg):
1544 QMessageBox.information(self, _('Message'), msg, _('OK'))
1546 def password_dialog(self ):
1553 vbox = QVBoxLayout()
1554 msg = _('Please enter your password')
1555 vbox.addWidget(QLabel(msg))
1557 grid = QGridLayout()
1559 grid.addWidget(QLabel(_('Password')), 1, 0)
1560 grid.addWidget(pw, 1, 1)
1561 vbox.addLayout(grid)
1563 vbox.addLayout(ok_cancel_buttons(d))
1566 self.run_hook('password_dialog', pw, grid, 1)
1567 if not d.exec_(): return
1568 return unicode(pw.text())
1575 def change_password_dialog( wallet, parent=None ):
1578 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1586 new_pw = QLineEdit()
1587 new_pw.setEchoMode(2)
1588 conf_pw = QLineEdit()
1589 conf_pw.setEchoMode(2)
1591 vbox = QVBoxLayout()
1593 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1594 +_('To disable wallet encryption, enter an empty new password.')) \
1595 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1597 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1598 +_("Leave these fields empty if you want to disable encryption.")
1599 vbox.addWidget(QLabel(msg))
1601 grid = QGridLayout()
1604 if wallet.use_encryption:
1605 grid.addWidget(QLabel(_('Password')), 1, 0)
1606 grid.addWidget(pw, 1, 1)
1608 grid.addWidget(QLabel(_('New Password')), 2, 0)
1609 grid.addWidget(new_pw, 2, 1)
1611 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1612 grid.addWidget(conf_pw, 3, 1)
1613 vbox.addLayout(grid)
1615 vbox.addLayout(ok_cancel_buttons(d))
1618 if not d.exec_(): return
1620 password = unicode(pw.text()) if wallet.use_encryption else None
1621 new_password = unicode(new_pw.text())
1622 new_password2 = unicode(conf_pw.text())
1625 seed = wallet.decode_seed(password)
1627 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1630 if new_password != new_password2:
1631 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1632 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1635 wallet.update_password(seed, password, new_password)
1637 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1640 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1643 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1644 parent.password_button.setIcon( icon )
1648 def generate_transaction_information_widget(self, tx):
1649 tabs = QTabWidget(self)
1652 grid_ui = QGridLayout(tab1)
1653 grid_ui.setColumnStretch(0,1)
1654 tabs.addTab(tab1, _('Outputs') )
1656 tree_widget = MyTreeWidget(self)
1657 tree_widget.setColumnCount(2)
1658 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1659 tree_widget.setColumnWidth(0, 300)
1660 tree_widget.setColumnWidth(1, 50)
1662 for address, value in tx.outputs:
1663 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1664 tree_widget.addTopLevelItem(item)
1666 tree_widget.setMaximumHeight(100)
1668 grid_ui.addWidget(tree_widget)
1671 grid_ui = QGridLayout(tab2)
1672 grid_ui.setColumnStretch(0,1)
1673 tabs.addTab(tab2, _('Inputs') )
1675 tree_widget = MyTreeWidget(self)
1676 tree_widget.setColumnCount(2)
1677 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1679 for input_line in tx.inputs:
1680 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1681 tree_widget.addTopLevelItem(item)
1683 tree_widget.setMaximumHeight(100)
1685 grid_ui.addWidget(tree_widget)
1689 def tx_dict_from_text(self, txt):
1691 tx_dict = json.loads(str(txt))
1692 assert "hex" in tx_dict.keys()
1693 assert "complete" in tx_dict.keys()
1694 if not tx_dict["complete"]:
1695 assert "input_info" in tx_dict.keys()
1697 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1702 def read_tx_from_file(self):
1703 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1707 with open(fileName, "r") as f:
1708 file_content = f.read()
1709 except (ValueError, IOError, os.error), reason:
1710 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1712 return self.tx_dict_from_text(file_content)
1716 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1718 self.wallet.signrawtransaction(tx, input_info, [], password)
1720 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1722 with open(fileName, "w+") as f:
1723 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1724 self.show_message(_("Transaction saved successfully"))
1727 except BaseException, e:
1728 self.show_message(str(e))
1731 def send_raw_transaction(self, raw_tx, dialog = ""):
1732 result, result_message = self.wallet.sendtx( raw_tx )
1734 self.show_message("Transaction successfully sent: %s" % (result_message))
1738 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1740 def do_process_from_text(self):
1741 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1744 tx_dict = self.tx_dict_from_text(text)
1746 self.create_process_transaction_window(tx_dict)
1748 def do_process_from_file(self):
1749 tx_dict = self.read_tx_from_file()
1751 self.create_process_transaction_window(tx_dict)
1753 def create_process_transaction_window(self, tx_dict):
1754 tx = Transaction(tx_dict["hex"])
1756 dialog = QDialog(self)
1757 dialog.setMinimumWidth(500)
1758 dialog.setWindowTitle(_('Process raw transaction'))
1764 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1765 l.addWidget(QLabel(_("Actions")), 4,0)
1767 if tx_dict["complete"] == False:
1768 l.addWidget(QLabel(_("Unsigned")), 3,1)
1769 if self.wallet.seed :
1770 b = QPushButton("Sign transaction")
1771 input_info = json.loads(tx_dict["input_info"])
1772 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1773 l.addWidget(b, 4, 1)
1775 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1777 l.addWidget(QLabel(_("Signed")), 3,1)
1778 b = QPushButton("Broadcast transaction")
1779 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1782 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1783 cancelButton = QPushButton(_("Cancel"))
1784 cancelButton.clicked.connect(lambda: dialog.done(0))
1785 l.addWidget(cancelButton, 4,2)
1791 def do_export_privkeys(self, password):
1792 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.")))
1795 select_export = _('Select file to export your private keys to')
1796 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1798 with open(fileName, "w+") as csvfile:
1799 transaction = csv.writer(csvfile)
1800 transaction.writerow(["address", "private_key"])
1803 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1804 transaction.writerow(["%34s"%addr,pk])
1806 self.show_message(_("Private keys exported."))
1808 except (IOError, os.error), reason:
1809 export_error_label = _("Electrum was unable to produce a private key-export.")
1810 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1812 except BaseException, e:
1813 self.show_message(str(e))
1817 def do_import_labels(self):
1818 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1819 if not labelsFile: return
1821 f = open(labelsFile, 'r')
1824 for key, value in json.loads(data).items():
1825 self.wallet.labels[key] = value
1827 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1828 except (IOError, os.error), reason:
1829 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1832 def do_export_labels(self):
1833 labels = self.wallet.labels
1835 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1837 with open(fileName, 'w+') as f:
1838 json.dump(labels, f)
1839 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1840 except (IOError, os.error), reason:
1841 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1844 def do_export_history(self):
1845 from gui_lite import csv_transaction
1846 csv_transaction(self.wallet)
1850 def do_import_privkey(self, password):
1851 if not self.wallet.imported_keys:
1852 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1853 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1854 + _('Are you sure you understand what you are doing?'), 3, 4)
1857 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1860 text = str(text).split()
1865 addr = self.wallet.import_key(key, password)
1866 except BaseException as e:
1872 addrlist.append(addr)
1874 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1876 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1877 self.update_receive_tab()
1878 self.update_history_tab()
1881 def settings_dialog(self):
1883 d.setWindowTitle(_('Electrum Settings'))
1885 vbox = QVBoxLayout()
1887 tabs = QTabWidget(self)
1888 self.settings_tab = tabs
1889 vbox.addWidget(tabs)
1892 grid_ui = QGridLayout(tab1)
1893 grid_ui.setColumnStretch(0,1)
1894 tabs.addTab(tab1, _('Display') )
1896 nz_label = QLabel(_('Display zeros'))
1897 grid_ui.addWidget(nz_label, 0, 0)
1898 nz_e = AmountEdit(None,True)
1899 nz_e.setText("%d"% self.wallet.num_zeros)
1900 grid_ui.addWidget(nz_e, 0, 1)
1901 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1902 grid_ui.addWidget(HelpButton(msg), 0, 2)
1903 if not self.config.is_modifiable('num_zeros'):
1904 for w in [nz_e, nz_label]: w.setEnabled(False)
1906 lang_label=QLabel(_('Language') + ':')
1907 grid_ui.addWidget(lang_label, 1, 0)
1908 lang_combo = QComboBox()
1909 from i18n import languages
1910 lang_combo.addItems(languages.values())
1912 index = languages.keys().index(self.config.get("language",''))
1915 lang_combo.setCurrentIndex(index)
1916 grid_ui.addWidget(lang_combo, 1, 1)
1917 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1918 if not self.config.is_modifiable('language'):
1919 for w in [lang_combo, lang_label]: w.setEnabled(False)
1921 currencies = self.exchanger.get_currencies()
1922 currencies.insert(0, "None")
1924 cur_label=QLabel(_('Currency') + ':')
1925 grid_ui.addWidget(cur_label , 2, 0)
1926 cur_combo = QComboBox()
1927 cur_combo.addItems(currencies)
1929 index = currencies.index(self.config.get('currency', "None"))
1932 cur_combo.setCurrentIndex(index)
1933 grid_ui.addWidget(cur_combo, 2, 1)
1934 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1936 expert_cb = QCheckBox(_('Expert mode'))
1937 expert_cb.setChecked(self.expert_mode)
1938 grid_ui.addWidget(expert_cb, 3, 0)
1939 hh = _('In expert mode, your client will:') + '\n' \
1940 + _(' - Show change addresses in the Receive tab') + '\n' \
1941 + _(' - Display the balance of each address') + '\n' \
1942 + _(' - Add freeze/prioritize actions to addresses.')
1943 grid_ui.addWidget(HelpButton(hh), 3, 2)
1944 grid_ui.setRowStretch(4,1)
1948 grid_wallet = QGridLayout(tab2)
1949 grid_wallet.setColumnStretch(0,1)
1950 tabs.addTab(tab2, _('Wallet') )
1952 fee_label = QLabel(_('Transaction fee'))
1953 grid_wallet.addWidget(fee_label, 0, 0)
1954 fee_e = AmountEdit(self.base_unit)
1955 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1956 grid_wallet.addWidget(fee_e, 0, 2)
1957 msg = _('Fee per kilobyte of transaction.') + ' ' \
1958 + _('Recommended value') + ': ' + self.format_amount(50000)
1959 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1960 if not self.config.is_modifiable('fee_per_kb'):
1961 for w in [fee_e, fee_label]: w.setEnabled(False)
1963 usechange_cb = QCheckBox(_('Use change addresses'))
1964 usechange_cb.setChecked(self.wallet.use_change)
1965 grid_wallet.addWidget(usechange_cb, 1, 0)
1966 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1967 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1969 gap_label = QLabel(_('Gap limit'))
1970 grid_wallet.addWidget(gap_label, 2, 0)
1971 gap_e = AmountEdit(None,True)
1972 gap_e.setText("%d"% self.wallet.gap_limit)
1973 grid_wallet.addWidget(gap_e, 2, 2)
1974 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1975 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1976 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1977 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1978 + _('Warning') + ': ' \
1979 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1980 + _('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'
1981 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1982 if not self.config.is_modifiable('gap_limit'):
1983 for w in [gap_e, gap_label]: w.setEnabled(False)
1985 units = ['BTC', 'mBTC']
1986 unit_label = QLabel(_('Base unit'))
1987 grid_wallet.addWidget(unit_label, 3, 0)
1988 unit_combo = QComboBox()
1989 unit_combo.addItems(units)
1990 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1991 grid_wallet.addWidget(unit_combo, 3, 2)
1992 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1993 + '\n1BTC=1000mBTC.\n' \
1994 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1995 grid_wallet.setRowStretch(4,1)
2000 grid_io = QGridLayout(tab3)
2001 grid_io.setColumnStretch(0,1)
2002 tabs.addTab(tab3, _('Import/Export') )
2004 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2005 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2006 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2007 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2009 grid_io.addWidget(QLabel(_('History')), 2, 0)
2010 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2011 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2013 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2015 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2016 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2017 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2019 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2020 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2021 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2022 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2023 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2026 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2027 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2028 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2029 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2031 grid_io.setRowStretch(6,1)
2036 tab5 = QScrollArea()
2037 tab5.setEnabled(True)
2038 tab5.setWidgetResizable(True)
2040 grid_plugins = QGridLayout()
2041 grid_plugins.setColumnStretch(0,1)
2044 w.setLayout(grid_plugins)
2046 tab5.setMaximumSize(tab3.size()) # optional
2048 w.setMinimumHeight(len(self.plugins)*35)
2050 tabs.addTab(tab5, _('Plugins') )
2051 def mk_toggle(cb, p):
2052 return lambda: cb.setChecked(p.toggle())
2053 for i, p in enumerate(self.plugins):
2055 name, description = p.get_info()
2056 cb = QCheckBox(name)
2057 cb.setDisabled(not p.is_available())
2058 cb.setChecked(p.is_enabled())
2059 cb.clicked.connect(mk_toggle(cb,p))
2060 grid_plugins.addWidget(cb, i, 0)
2061 if p.requires_settings():
2062 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2063 grid_plugins.addWidget(HelpButton(description), i, 2)
2065 print_msg("Error: cannot display plugin", p)
2066 traceback.print_exc(file=sys.stdout)
2067 grid_plugins.setRowStretch(i+1,1)
2069 self.run_hook('create_settings_tab', tabs)
2071 vbox.addLayout(ok_cancel_buttons(d))
2075 if not d.exec_(): return
2077 fee = unicode(fee_e.text())
2079 fee = self.read_amount(fee)
2081 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2084 self.wallet.set_fee(fee)
2086 nz = unicode(nz_e.text())
2091 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2094 if self.wallet.num_zeros != nz:
2095 self.wallet.num_zeros = nz
2096 self.config.set_key('num_zeros', nz, True)
2097 self.update_history_tab()
2098 self.update_receive_tab()
2100 usechange_result = usechange_cb.isChecked()
2101 if self.wallet.use_change != usechange_result:
2102 self.wallet.use_change = usechange_result
2103 self.config.set_key('use_change', self.wallet.use_change, True)
2105 unit_result = units[unit_combo.currentIndex()]
2106 if self.base_unit() != unit_result:
2107 self.decimal_point = 8 if unit_result == 'BTC' else 5
2108 self.config.set_key('decimal_point', self.decimal_point, True)
2109 self.update_history_tab()
2110 self.update_status()
2113 n = int(gap_e.text())
2115 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2118 if self.wallet.gap_limit != n:
2119 r = self.wallet.change_gap_limit(n)
2121 self.update_receive_tab()
2122 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2124 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2126 need_restart = False
2128 lang_request = languages.keys()[lang_combo.currentIndex()]
2129 if lang_request != self.config.get('language'):
2130 self.config.set_key("language", lang_request, True)
2133 cur_request = str(currencies[cur_combo.currentIndex()])
2134 if cur_request != self.config.get('currency', "None"):
2135 self.config.set_key('currency', cur_request, True)
2136 self.update_wallet()
2138 self.run_hook('close_settings_dialog')
2141 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2143 self.receive_tab_set_mode(expert_cb.isChecked())
2145 def run_network_dialog(self):
2146 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2148 def closeEvent(self, event):
2150 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2151 self.save_column_widths()
2152 self.config.set_key("console-history",self.console.history[-50:])
2161 def __init__(self, wallet, config, app=None):
2162 self.wallet = wallet
2163 self.config = config
2165 self.app = QApplication(sys.argv)
2168 def restore_or_create(self):
2169 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2170 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2171 if r==2: return None
2172 return 'restore' if r==1 else 'create'
2175 def verify_seed(self):
2176 r = self.seed_dialog(False)
2177 if r != self.wallet.seed:
2178 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2185 def seed_dialog(self, is_restore=True):
2189 vbox = QVBoxLayout()
2191 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2193 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2195 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2198 label.setWordWrap(True)
2199 vbox.addWidget(label)
2201 seed_e = QTextEdit()
2202 seed_e.setMaximumHeight(100)
2203 vbox.addWidget(seed_e)
2206 grid = QGridLayout()
2208 gap_e = AmountEdit(None, True)
2210 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2211 grid.addWidget(gap_e, 2, 1)
2212 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2213 vbox.addLayout(grid)
2215 vbox.addLayout(ok_cancel_buttons(d))
2218 if not d.exec_(): return
2221 seed = str(seed_e.toPlainText())
2225 seed = mnemonic.mn_decode( seed.split() )
2227 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2231 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2238 gap = int(unicode(gap_e.text()))
2240 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2245 def network_dialog(self):
2246 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2249 def show_seed(self):
2250 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2252 def password_dialog(self):
2253 if self.wallet.seed:
2254 ElectrumWindow.change_password_dialog(self.wallet)
2257 def restore_wallet(self):
2258 wallet = self.wallet
2259 # wait until we are connected, because the user might have selected another server
2260 if not wallet.interface.is_connected:
2261 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2262 waiting_dialog(waiting)
2264 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2265 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2267 wallet.set_up_to_date(False)
2268 wallet.interface.poke('synchronizer')
2269 waiting_dialog(waiting)
2270 if wallet.is_found():
2271 print_error( "Recovery successful" )
2273 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2280 w = ElectrumWindow(self.wallet, self.config)
2281 if url: w.set_url(url)