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 electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit, MyLineEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.gui_object = gui_object
111 self.tray = gui_object.tray
112 self.go_lite = gui_object.go_lite
115 self.create_status_bar()
116 self.need_update = threading.Event()
118 self.decimal_point = config.get('decimal_point', 5)
119 self.num_zeros = int(config.get('num_zeros',0))
122 set_language(config.get('language'))
124 self.funds_error = False
125 self.completions = QStringListModel()
127 self.tabs = tabs = QTabWidget(self)
128 self.column_widths = self.config.get("column_widths_2", default_column_widths )
129 tabs.addTab(self.create_history_tab(), _('History') )
130 tabs.addTab(self.create_send_tab(), _('Send') )
131 tabs.addTab(self.create_receive_tab(), _('Receive') )
132 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
133 tabs.addTab(self.create_invoices_tab(), _('Invoices') )
134 tabs.addTab(self.create_console_tab(), _('Console') )
135 tabs.setMinimumSize(600, 400)
136 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
137 self.setCentralWidget(tabs)
139 g = self.config.get("winpos-qt",[100, 100, 840, 400])
140 self.setGeometry(g[0], g[1], g[2], g[3])
141 if self.config.get("is_maximized"):
144 self.setWindowIcon(QIcon(":icons/electrum.png"))
147 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
148 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
149 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
150 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
151 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
153 for i in range(tabs.count()):
154 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
156 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
157 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
158 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
159 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
160 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
162 self.history_list.setFocus(True)
166 self.network.register_callback('updated', lambda: self.need_update.set())
167 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
168 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
169 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
170 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
172 # set initial message
173 self.console.showMessage(self.network.banner)
178 def update_account_selector(self):
180 accounts = self.wallet.get_account_names()
181 self.account_selector.clear()
182 if len(accounts) > 1:
183 self.account_selector.addItems([_("All accounts")] + accounts.values())
184 self.account_selector.setCurrentIndex(0)
185 self.account_selector.show()
187 self.account_selector.hide()
190 def load_wallet(self, wallet):
194 self.update_wallet_format()
196 self.invoices = self.wallet.storage.get('invoices', {})
197 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
198 self.current_account = self.wallet.storage.get("current_account", None)
199 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
200 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
201 self.setWindowTitle( title )
203 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
204 self.notify_transactions()
205 self.update_account_selector()
207 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
208 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
209 self.password_menu.setEnabled(not self.wallet.is_watching_only())
210 self.seed_menu.setEnabled(self.wallet.has_seed())
211 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
212 self.import_menu.setEnabled(self.wallet.can_import())
214 self.update_lock_icon()
215 self.update_buttons_on_seed()
216 self.update_console()
218 run_hook('load_wallet', wallet)
221 def update_wallet_format(self):
222 # convert old-format imported keys
223 if self.wallet.imported_keys:
224 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
226 self.wallet.convert_imported_keys(password)
228 self.show_message("error")
231 def open_wallet(self):
232 wallet_folder = self.wallet.storage.path
233 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
237 storage = WalletStorage({'wallet_path': filename})
238 if not storage.file_exists:
239 self.show_message("file not found "+ filename)
242 self.wallet.stop_threads()
245 wallet = Wallet(storage)
246 wallet.start_threads(self.network)
248 self.load_wallet(wallet)
252 def backup_wallet(self):
254 path = self.wallet.storage.path
255 wallet_folder = os.path.dirname(path)
256 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
260 new_path = os.path.join(wallet_folder, filename)
263 shutil.copy2(path, new_path)
264 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
265 except (IOError, os.error), reason:
266 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
269 def new_wallet(self):
272 wallet_folder = os.path.dirname(self.wallet.storage.path)
273 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
276 filename = os.path.join(wallet_folder, filename)
278 storage = WalletStorage({'wallet_path': filename})
279 if storage.file_exists:
280 QMessageBox.critical(None, "Error", _("File exists"))
283 wizard = installwizard.InstallWizard(self.config, self.network, storage)
284 wallet = wizard.run('new')
286 self.load_wallet(wallet)
290 def init_menubar(self):
293 file_menu = menubar.addMenu(_("&File"))
294 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
295 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
296 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
297 file_menu.addAction(_("&Quit"), self.close)
299 wallet_menu = menubar.addMenu(_("&Wallet"))
300 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
301 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
303 wallet_menu.addSeparator()
305 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
306 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
307 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
309 wallet_menu.addSeparator()
310 labels_menu = wallet_menu.addMenu(_("&Labels"))
311 labels_menu.addAction(_("&Import"), self.do_import_labels)
312 labels_menu.addAction(_("&Export"), self.do_export_labels)
314 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
315 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
316 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
317 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
318 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
320 tools_menu = menubar.addMenu(_("&Tools"))
322 # Settings / Preferences are all reserved keywords in OSX using this as work around
323 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
324 tools_menu.addAction(_("&Network"), self.run_network_dialog)
325 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
326 tools_menu.addSeparator()
327 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
328 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
329 tools_menu.addSeparator()
331 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
332 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
333 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
335 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
336 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
337 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
338 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
340 help_menu = menubar.addMenu(_("&Help"))
341 help_menu.addAction(_("&About"), self.show_about)
342 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
343 help_menu.addSeparator()
344 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
345 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
347 self.setMenuBar(menubar)
349 def show_about(self):
350 QMessageBox.about(self, "Electrum",
351 _("Version")+" %s" % (self.wallet.electrum_version) + "\n\n" + _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
353 def show_report_bug(self):
354 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
355 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
358 def notify_transactions(self):
359 if not self.network or not self.network.is_connected():
362 print_error("Notifying GUI")
363 if len(self.network.pending_transactions_for_notifications) > 0:
364 # Combine the transactions if there are more then three
365 tx_amount = len(self.network.pending_transactions_for_notifications)
368 for tx in self.network.pending_transactions_for_notifications:
369 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
373 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
374 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
376 self.network.pending_transactions_for_notifications = []
378 for tx in self.network.pending_transactions_for_notifications:
380 self.network.pending_transactions_for_notifications.remove(tx)
381 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
383 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
385 def notify(self, message):
386 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
390 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
391 def getOpenFileName(self, title, filter = ""):
392 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
393 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
394 if fileName and directory != os.path.dirname(fileName):
395 self.config.set_key('io_dir', os.path.dirname(fileName), True)
398 def getSaveFileName(self, title, filename, filter = ""):
399 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
400 path = os.path.join( directory, filename )
401 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
402 if fileName and directory != os.path.dirname(fileName):
403 self.config.set_key('io_dir', os.path.dirname(fileName), True)
407 QMainWindow.close(self)
408 run_hook('close_main_window')
410 def connect_slots(self, sender):
411 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
412 self.previous_payto_e=''
414 def timer_actions(self):
415 if self.need_update.is_set():
417 self.need_update.clear()
418 run_hook('timer_actions')
420 def format_amount(self, x, is_diff=False, whitespaces=False):
421 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
424 def get_decimal_point(self):
425 return self.decimal_point
429 assert self.decimal_point in [5,8]
430 return "BTC" if self.decimal_point == 8 else "mBTC"
433 def update_status(self):
434 if self.network is None or not self.network.is_running():
436 icon = QIcon(":icons/status_disconnected.png")
438 elif self.network.is_connected():
439 if not self.wallet.up_to_date:
440 text = _("Synchronizing...")
441 icon = QIcon(":icons/status_waiting.png")
442 elif self.network.server_lag > 1:
443 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
444 icon = QIcon(":icons/status_lagging.png")
446 c, u = self.wallet.get_account_balance(self.current_account)
447 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
448 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
450 # append fiat balance and price from exchange rate plugin
452 run_hook('get_fiat_status_text', c+u, r)
457 self.tray.setToolTip(text)
458 icon = QIcon(":icons/status_connected.png")
460 text = _("Not connected")
461 icon = QIcon(":icons/status_disconnected.png")
463 self.balance_label.setText(text)
464 self.status_button.setIcon( icon )
467 def update_wallet(self):
469 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
470 self.update_history_tab()
471 self.update_receive_tab()
472 self.update_contacts_tab()
473 self.update_completions()
474 self.update_invoices_tab()
477 def create_history_tab(self):
478 self.history_list = l = MyTreeWidget(self)
480 for i,width in enumerate(self.column_widths['history']):
481 l.setColumnWidth(i, width)
482 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
483 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
484 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
486 l.customContextMenuRequested.connect(self.create_history_menu)
490 def create_history_menu(self, position):
491 self.history_list.selectedIndexes()
492 item = self.history_list.currentItem()
493 be = self.config.get('block_explorer', 'Blockchain.info')
494 if be == 'Blockchain.info':
495 block_explorer = 'https://blockchain.info/tx/'
496 elif be == 'Blockr.io':
497 block_explorer = 'https://blockr.io/tx/info/'
498 elif be == 'Insight.is':
499 block_explorer = 'http://live.insight.is/tx/'
501 tx_hash = str(item.data(0, Qt.UserRole).toString())
502 if not tx_hash: return
504 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
505 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
506 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
507 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
508 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
511 def show_transaction(self, tx):
512 import transaction_dialog
513 d = transaction_dialog.TxDialog(tx, self)
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.wallet.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.wallet.set_label(addr, text)
572 self.update_history_tab()
573 self.update_completions()
575 self.current_item_changed(item)
577 run_hook('item_changed', item, column)
580 def current_item_changed(self, a):
581 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
590 time_str = _("unknown")
593 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
595 time_str = _("error")
598 time_str = 'unverified'
599 icon = QIcon(":icons/unconfirmed.png")
602 icon = QIcon(":icons/unconfirmed.png")
604 icon = QIcon(":icons/clock%d.png"%conf)
606 icon = QIcon(":icons/confirmed.png")
608 if value is not None:
609 v_str = self.format_amount(value, True, whitespaces=True)
613 balance_str = self.format_amount(balance, whitespaces=True)
616 label, is_default_label = self.wallet.get_label(tx_hash)
618 label = _('Pruned transaction outputs')
619 is_default_label = False
621 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
622 item.setFont(2, QFont(MONOSPACE_FONT))
623 item.setFont(3, QFont(MONOSPACE_FONT))
624 item.setFont(4, QFont(MONOSPACE_FONT))
626 item.setForeground(3, QBrush(QColor("#BC1E1E")))
628 item.setData(0, Qt.UserRole, tx_hash)
629 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
631 item.setForeground(2, QBrush(QColor('grey')))
633 item.setIcon(0, icon)
634 self.history_list.insertTopLevelItem(0,item)
637 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
638 run_hook('history_tab_update')
641 def create_send_tab(self):
644 grid = QGridLayout(w)
646 grid.setColumnMinimumWidth(3,300)
647 grid.setColumnStretch(5,1)
648 grid.setRowStretch(8, 1)
650 from paytoedit import PayToEdit
651 self.amount_e = AmountEdit(self.get_decimal_point)
652 self.payto_e = PayToEdit(self.amount_e)
653 self.payto_help = 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)'))
654 grid.addWidget(QLabel(_('Pay to')), 1, 0)
655 grid.addWidget(self.payto_e, 1, 1, 1, 3)
656 grid.addWidget(self.payto_help, 1, 4)
658 completer = QCompleter()
659 completer.setCaseSensitivity(False)
660 self.payto_e.setCompleter(completer)
661 completer.setModel(self.completions)
663 self.message_e = MyLineEdit()
664 self.message_help = 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.'))
665 grid.addWidget(QLabel(_('Description')), 2, 0)
666 grid.addWidget(self.message_e, 2, 1, 1, 3)
667 grid.addWidget(self.message_help, 2, 4)
669 self.from_label = QLabel(_('From'))
670 grid.addWidget(self.from_label, 3, 0)
671 self.from_list = MyTreeWidget(self)
672 self.from_list.setColumnCount(2)
673 self.from_list.setColumnWidth(0, 350)
674 self.from_list.setColumnWidth(1, 50)
675 self.from_list.setHeaderHidden(True)
676 self.from_list.setMaximumHeight(80)
677 self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
678 self.from_list.customContextMenuRequested.connect(self.from_list_menu)
679 grid.addWidget(self.from_list, 3, 1, 1, 3)
680 self.set_pay_from([])
682 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
683 + _('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.') \
684 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
685 grid.addWidget(QLabel(_('Amount')), 4, 0)
686 grid.addWidget(self.amount_e, 4, 1, 1, 2)
687 grid.addWidget(self.amount_help, 4, 3)
689 self.fee_e = AmountEdit(self.get_decimal_point)
690 grid.addWidget(QLabel(_('Fee')), 5, 0)
691 grid.addWidget(self.fee_e, 5, 1, 1, 2)
692 grid.addWidget(HelpButton(
693 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
694 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
695 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 5, 3)
697 run_hook('exchange_rate_button', grid)
699 self.send_button = EnterButton(_("Send"), self.do_send)
700 grid.addWidget(self.send_button, 6, 1)
702 b = EnterButton(_("Clear"), self.do_clear)
703 grid.addWidget(b, 6, 2)
705 self.payto_sig = QLabel('')
706 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
708 #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
709 #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
712 def entry_changed( is_fee ):
713 self.funds_error = False
715 if self.amount_e.is_shortcut:
716 self.amount_e.is_shortcut = False
717 sendable = self.get_sendable_balance()
718 # there is only one output because we are completely spending inputs
719 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
720 fee = self.wallet.estimated_fee(inputs, 1)
722 self.amount_e.setText( self.format_amount(amount) )
723 self.fee_e.setText( self.format_amount( fee ) )
726 amount = self.amount_e.get_amount()
727 fee = self.fee_e.get_amount()
729 if not is_fee: fee = None
732 # assume that there will be 2 outputs (one for change)
733 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
735 self.fee_e.setText( self.format_amount( fee ) )
738 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
742 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
743 self.funds_error = True
744 text = _( "Not enough funds" )
745 c, u = self.wallet.get_frozen_balance()
746 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
748 self.statusBar().showMessage(text)
749 self.amount_e.setPalette(palette)
750 self.fee_e.setPalette(palette)
752 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
753 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
755 run_hook('create_send_tab', grid)
758 def from_list_delete(self, item):
759 i = self.from_list.indexOfTopLevelItem(item)
761 self.redraw_from_list()
763 def from_list_menu(self, position):
764 item = self.from_list.itemAt(position)
766 menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
767 menu.exec_(self.from_list.viewport().mapToGlobal(position))
769 def set_pay_from(self, domain = None):
770 self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
771 self.redraw_from_list()
773 def redraw_from_list(self):
774 self.from_list.clear()
775 self.from_label.setHidden(len(self.pay_from) == 0)
776 self.from_list.setHidden(len(self.pay_from) == 0)
779 h = x.get('prevout_hash')
780 return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
782 for item in self.pay_from:
783 self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
785 def update_completions(self):
787 for addr,label in self.wallet.labels.items():
788 if addr in self.wallet.addressbook:
789 l.append( label + ' <' + addr + '>')
791 run_hook('update_completions', l)
792 self.completions.setStringList(l)
796 return lambda s, *args: s.do_protect(func, args)
800 label = unicode( self.message_e.text() )
802 if self.gui_object.payment_request:
803 outputs = self.gui_object.payment_request.outputs
805 outputs = self.payto_e.get_outputs()
808 QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
811 for addr, x in outputs:
812 if addr is None or not bitcoin.is_address(addr):
813 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
816 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
819 amount = sum(map(lambda x:x[1], outputs))
822 fee = self.fee_e.get_amount()
824 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
827 confirm_amount = self.config.get('confirm_amount', 100000000)
828 if amount >= confirm_amount:
829 o = '\n'.join(map(lambda x:x[0], outputs))
830 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
833 confirm_fee = self.config.get('confirm_fee', 100000)
834 if fee >= confirm_fee:
835 if not self.question(_("The fee for this transaction seems unusually high.\nAre you really sure you want to pay %(fee)s in fees?")%{ 'fee' : self.format_amount(fee) + ' '+ self.base_unit()}):
838 self.send_tx(outputs, fee, label)
843 def send_tx(self, outputs, fee, label, password):
844 self.send_button.setDisabled(True)
846 # first, create an unsigned tx
847 coins = self.get_coins()
849 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
851 except Exception as e:
852 traceback.print_exc(file=sys.stdout)
853 self.show_message(str(e))
854 self.send_button.setDisabled(False)
857 # call hook to see if plugin needs gui interaction
858 run_hook('send_tx', tx)
864 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
865 self.wallet.sign_transaction(tx, keypairs, password)
866 return tx, fee, label
868 def sign_done(tx, fee, label):
870 self.show_message(tx.error)
871 self.send_button.setDisabled(False)
873 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
874 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
875 self.send_button.setDisabled(False)
878 self.wallet.set_label(tx.hash(), label)
880 if not tx.is_complete() or self.config.get('show_before_broadcast'):
881 self.show_transaction(tx)
883 self.send_button.setDisabled(False)
886 self.broadcast_transaction(tx)
888 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
889 self.waiting_dialog.start()
893 def broadcast_transaction(self, tx):
895 def broadcast_thread():
896 if self.gui_object.payment_request:
897 refund_address = self.wallet.addresses()[0]
898 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
899 self.gui_object.payment_request = None
901 status, msg = self.wallet.sendtx(tx)
904 def broadcast_done(status, msg):
906 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
909 QMessageBox.warning(self, _('Error'), msg, _('OK'))
910 self.send_button.setDisabled(False)
912 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
913 self.waiting_dialog.start()
917 def prepare_for_payment_request(self):
918 self.tabs.setCurrentIndex(1)
919 self.payto_e.is_pr = True
920 for e in [self.payto_e, self.amount_e, self.message_e]:
922 for h in [self.payto_help, self.amount_help, self.message_help]:
924 self.payto_e.setText(_("please wait..."))
927 def payment_request_ok(self):
928 pr = self.gui_object.payment_request
931 self.invoices[pr_id] = (pr.get_domain(), pr.get_amount())
932 self.wallet.storage.put('invoices', self.invoices)
933 self.update_invoices_tab()
935 self.payto_help.show()
936 self.payto_help.set_alt(pr.status)
937 self.payto_e.setGreen()
938 self.payto_e.setText(pr.domain)
939 self.amount_e.setText(self.format_amount(pr.get_amount()))
940 self.message_e.setText(pr.memo)
942 def payment_request_error(self):
944 self.show_message(self.gui_object.payment_request.error)
945 self.gui_object.payment_request = None
947 def set_send(self, address, amount, label, message):
949 if label and self.wallet.labels.get(address) != label:
950 if self.question('Give label "%s" to address %s ?'%(label,address)):
951 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
952 self.wallet.addressbook.append(address)
953 self.wallet.set_label(address, label)
955 self.tabs.setCurrentIndex(1)
956 label = self.wallet.labels.get(address)
957 m_addr = label + ' <'+ address +'>' if label else address
958 self.payto_e.setText(m_addr)
960 self.message_e.setText(message)
962 self.amount_e.setText(amount)
966 self.payto_e.is_pr = False
967 self.payto_sig.setVisible(False)
968 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
972 for h in [self.payto_help, self.amount_help, self.message_help]:
975 self.payto_help.set_alt(None)
977 self.set_pay_from([])
982 def set_addrs_frozen(self,addrs,freeze):
984 if not addr: continue
985 if addr in self.wallet.frozen_addresses and not freeze:
986 self.wallet.unfreeze(addr)
987 elif addr not in self.wallet.frozen_addresses and freeze:
988 self.wallet.freeze(addr)
989 self.update_receive_tab()
993 def create_list_tab(self, headers):
994 "generic tab creation method"
995 l = MyTreeWidget(self)
996 l.setColumnCount( len(headers) )
997 l.setHeaderLabels( headers )
1000 vbox = QVBoxLayout()
1007 vbox.addWidget(buttons)
1009 hbox = QHBoxLayout()
1012 buttons.setLayout(hbox)
1017 def create_receive_tab(self):
1018 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1019 l.setContextMenuPolicy(Qt.CustomContextMenu)
1020 l.customContextMenuRequested.connect(self.create_receive_menu)
1021 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1022 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1023 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1024 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1025 self.receive_list = l
1026 self.receive_buttons_hbox = hbox
1033 def save_column_widths(self):
1034 self.column_widths["receive"] = []
1035 for i in range(self.receive_list.columnCount() -1):
1036 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1038 self.column_widths["history"] = []
1039 for i in range(self.history_list.columnCount() - 1):
1040 self.column_widths["history"].append(self.history_list.columnWidth(i))
1042 self.column_widths["contacts"] = []
1043 for i in range(self.contacts_list.columnCount() - 1):
1044 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1046 self.config.set_key("column_widths_2", self.column_widths, True)
1049 def create_contacts_tab(self):
1050 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1051 l.setContextMenuPolicy(Qt.CustomContextMenu)
1052 l.customContextMenuRequested.connect(self.create_contact_menu)
1053 for i,width in enumerate(self.column_widths['contacts']):
1054 l.setColumnWidth(i, width)
1056 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1057 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1058 self.contacts_list = l
1059 self.contacts_buttons_hbox = hbox
1064 def create_invoices_tab(self):
1065 l,w,hbox = self.create_list_tab([_('Requestor'), _('Amount'), _('Status')])
1066 l.setContextMenuPolicy(Qt.CustomContextMenu)
1067 l.customContextMenuRequested.connect(self.create_invoice_menu)
1068 self.invoices_list = l
1072 def update_invoices_tab(self):
1073 invoices = self.wallet.storage.get('invoices', {})
1074 l = self.invoices_list
1077 for item, value in invoices.items():
1078 domain, amount = value
1079 item = QTreeWidgetItem( [ domain, self.format_amount(amount), ""] )
1080 l.addTopLevelItem(item)
1082 l.setCurrentItem(l.topLevelItem(0))
1086 def delete_imported_key(self, addr):
1087 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1088 self.wallet.delete_imported_key(addr)
1089 self.update_receive_tab()
1090 self.update_history_tab()
1092 def edit_account_label(self, k):
1093 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1095 label = unicode(text)
1096 self.wallet.set_label(k,label)
1097 self.update_receive_tab()
1099 def account_set_expanded(self, item, k, b):
1101 self.accounts_expanded[k] = b
1103 def create_account_menu(self, position, k, item):
1105 if item.isExpanded():
1106 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1108 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1109 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1110 if self.wallet.seed_version > 4:
1111 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1112 if self.wallet.account_is_pending(k):
1113 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1114 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1116 def delete_pending_account(self, k):
1117 self.wallet.delete_pending_account(k)
1118 self.update_receive_tab()
1120 def create_receive_menu(self, position):
1121 # fixme: this function apparently has a side effect.
1122 # if it is not called the menu pops up several times
1123 #self.receive_list.selectedIndexes()
1125 selected = self.receive_list.selectedItems()
1126 multi_select = len(selected) > 1
1127 addrs = [unicode(item.text(0)) for item in selected]
1128 if not multi_select:
1129 item = self.receive_list.itemAt(position)
1133 if not is_valid(addr):
1134 k = str(item.data(0,32).toString())
1136 self.create_account_menu(position, k, item)
1138 item.setExpanded(not item.isExpanded())
1142 if not multi_select:
1143 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1144 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1145 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1146 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1147 if not self.wallet.is_watching_only():
1148 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1149 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1150 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1151 if self.wallet.is_imported(addr):
1152 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1154 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1155 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1156 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1157 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1159 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1160 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1162 run_hook('receive_menu', menu, addrs)
1163 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1166 def get_sendable_balance(self):
1167 return sum(map(lambda x:x['value'], self.get_coins()))
1170 def get_coins(self):
1172 return self.pay_from
1174 domain = self.wallet.get_account_addresses(self.current_account)
1175 for i in self.wallet.frozen_addresses:
1176 if i in domain: domain.remove(i)
1177 return self.wallet.get_unspent_coins(domain)
1180 def send_from_addresses(self, addrs):
1181 self.set_pay_from( addrs )
1182 self.tabs.setCurrentIndex(1)
1185 def payto(self, addr):
1187 label = self.wallet.labels.get(addr)
1188 m_addr = label + ' <' + addr + '>' if label else addr
1189 self.tabs.setCurrentIndex(1)
1190 self.payto_e.setText(m_addr)
1191 self.amount_e.setFocus()
1194 def delete_contact(self, x):
1195 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1196 self.wallet.delete_contact(x)
1197 self.wallet.set_label(x, None)
1198 self.update_history_tab()
1199 self.update_contacts_tab()
1200 self.update_completions()
1203 def create_contact_menu(self, position):
1204 item = self.contacts_list.itemAt(position)
1207 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1209 addr = unicode(item.text(0))
1210 label = unicode(item.text(1))
1211 is_editable = item.data(0,32).toBool()
1212 payto_addr = item.data(0,33).toString()
1213 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1214 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1215 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1217 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1218 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1220 run_hook('create_contact_menu', menu, item)
1221 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1223 def delete_invoice(self, item):
1224 k = self.invoices_list.indexOfTopLevelItem(item)
1225 key = self.invoices.keys()[k]
1226 self.invoices.pop(key)
1227 self.wallet.storage.put('invoices', self.invoices)
1228 self.update_invoices_tab()
1230 def create_invoice_menu(self, position):
1231 item = self.invoices_list.itemAt(position)
1235 menu.addAction(_("Delete"), lambda: self.delete_invoice(item))
1236 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1239 def update_receive_item(self, item):
1240 item.setFont(0, QFont(MONOSPACE_FONT))
1241 address = str(item.data(0,0).toString())
1242 label = self.wallet.labels.get(address,'')
1243 item.setData(1,0,label)
1244 item.setData(0,32, True) # is editable
1246 run_hook('update_receive_item', address, item)
1248 if not self.wallet.is_mine(address): return
1250 c, u = self.wallet.get_addr_balance(address)
1251 balance = self.format_amount(c + u)
1252 item.setData(2,0,balance)
1254 if address in self.wallet.frozen_addresses:
1255 item.setBackgroundColor(0, QColor('lightblue'))
1258 def update_receive_tab(self):
1259 l = self.receive_list
1260 # extend the syntax for consistency
1261 l.addChild = l.addTopLevelItem
1262 l.insertChild = l.insertTopLevelItem
1265 for i,width in enumerate(self.column_widths['receive']):
1266 l.setColumnWidth(i, width)
1268 accounts = self.wallet.get_accounts()
1269 if self.current_account is None:
1270 account_items = sorted(accounts.items())
1272 account_items = [(self.current_account, accounts.get(self.current_account))]
1275 for k, account in account_items:
1277 if len(accounts) > 1:
1278 name = self.wallet.get_account_name(k)
1279 c,u = self.wallet.get_account_balance(k)
1280 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1281 l.addTopLevelItem(account_item)
1282 account_item.setExpanded(self.accounts_expanded.get(k, True))
1283 account_item.setData(0, 32, k)
1287 sequences = [0,1] if account.has_change() else [0]
1288 for is_change in sequences:
1289 if len(sequences) > 1:
1290 name = _("Receiving") if not is_change else _("Change")
1291 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1292 account_item.addChild(seq_item)
1294 seq_item.setExpanded(True)
1296 seq_item = account_item
1298 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1304 for address in account.get_addresses(is_change):
1306 num, is_used = self.wallet.is_used(address)
1309 if gap > self.wallet.gap_limit:
1314 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1315 self.update_receive_item(item)
1317 item.setBackgroundColor(1, QColor('red'))
1321 seq_item.insertChild(0,used_item)
1323 used_item.addChild(item)
1325 seq_item.addChild(item)
1327 # we use column 1 because column 0 may be hidden
1328 l.setCurrentItem(l.topLevelItem(0),1)
1331 def update_contacts_tab(self):
1332 l = self.contacts_list
1335 for address in self.wallet.addressbook:
1336 label = self.wallet.labels.get(address,'')
1337 n = self.wallet.get_num_tx(address)
1338 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1339 item.setFont(0, QFont(MONOSPACE_FONT))
1340 # 32 = label can be edited (bool)
1341 item.setData(0,32, True)
1343 item.setData(0,33, address)
1344 l.addTopLevelItem(item)
1346 run_hook('update_contacts_tab', l)
1347 l.setCurrentItem(l.topLevelItem(0))
1351 def create_console_tab(self):
1352 from console import Console
1353 self.console = console = Console()
1357 def update_console(self):
1358 console = self.console
1359 console.history = self.config.get("console-history",[])
1360 console.history_index = len(console.history)
1362 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1363 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1365 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1367 def mkfunc(f, method):
1368 return lambda *args: apply( f, (method, args, self.password_dialog ))
1370 if m[0]=='_' or m in ['network','wallet']: continue
1371 methods[m] = mkfunc(c._run, m)
1373 console.updateNamespace(methods)
1376 def change_account(self,s):
1377 if s == _("All accounts"):
1378 self.current_account = None
1380 accounts = self.wallet.get_account_names()
1381 for k, v in accounts.items():
1383 self.current_account = k
1384 self.update_history_tab()
1385 self.update_status()
1386 self.update_receive_tab()
1388 def create_status_bar(self):
1391 sb.setFixedHeight(35)
1392 qtVersion = qVersion()
1394 self.balance_label = QLabel("")
1395 sb.addWidget(self.balance_label)
1397 from version_getter import UpdateLabel
1398 self.updatelabel = UpdateLabel(self.config, sb)
1400 self.account_selector = QComboBox()
1401 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1402 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1403 sb.addPermanentWidget(self.account_selector)
1405 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1406 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1408 self.lock_icon = QIcon()
1409 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1410 sb.addPermanentWidget( self.password_button )
1412 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1413 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1414 sb.addPermanentWidget( self.seed_button )
1415 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1416 sb.addPermanentWidget( self.status_button )
1418 run_hook('create_status_bar', (sb,))
1420 self.setStatusBar(sb)
1423 def update_lock_icon(self):
1424 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1425 self.password_button.setIcon( icon )
1428 def update_buttons_on_seed(self):
1429 if self.wallet.has_seed():
1430 self.seed_button.show()
1432 self.seed_button.hide()
1434 if not self.wallet.is_watching_only():
1435 self.password_button.show()
1436 self.send_button.setText(_("Send"))
1438 self.password_button.hide()
1439 self.send_button.setText(_("Create unsigned transaction"))
1442 def change_password_dialog(self):
1443 from password_dialog import PasswordDialog
1444 d = PasswordDialog(self.wallet, self)
1446 self.update_lock_icon()
1449 def new_contact_dialog(self):
1452 d.setWindowTitle(_("New Contact"))
1453 vbox = QVBoxLayout(d)
1454 vbox.addWidget(QLabel(_('New Contact')+':'))
1456 grid = QGridLayout()
1459 grid.addWidget(QLabel(_("Address")), 1, 0)
1460 grid.addWidget(line1, 1, 1)
1461 grid.addWidget(QLabel(_("Name")), 2, 0)
1462 grid.addWidget(line2, 2, 1)
1464 vbox.addLayout(grid)
1465 vbox.addLayout(ok_cancel_buttons(d))
1470 address = str(line1.text())
1471 label = unicode(line2.text())
1473 if not is_valid(address):
1474 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1477 self.wallet.add_contact(address)
1479 self.wallet.set_label(address, label)
1481 self.update_contacts_tab()
1482 self.update_history_tab()
1483 self.update_completions()
1484 self.tabs.setCurrentIndex(3)
1488 def new_account_dialog(self, password):
1490 dialog = QDialog(self)
1492 dialog.setWindowTitle(_("New Account"))
1494 vbox = QVBoxLayout()
1495 vbox.addWidget(QLabel(_('Account name')+':'))
1498 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1499 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1504 vbox.addLayout(ok_cancel_buttons(dialog))
1505 dialog.setLayout(vbox)
1509 name = str(e.text())
1512 self.wallet.create_pending_account(name, password)
1513 self.update_receive_tab()
1514 self.tabs.setCurrentIndex(2)
1519 def show_master_public_keys(self):
1521 dialog = QDialog(self)
1523 dialog.setWindowTitle(_("Master Public Keys"))
1525 main_layout = QGridLayout()
1526 mpk_dict = self.wallet.get_master_public_keys()
1528 for key, value in mpk_dict.items():
1529 main_layout.addWidget(QLabel(key), i, 0)
1530 mpk_text = QTextEdit()
1531 mpk_text.setReadOnly(True)
1532 mpk_text.setMaximumHeight(170)
1533 mpk_text.setText(value)
1534 main_layout.addWidget(mpk_text, i + 1, 0)
1537 vbox = QVBoxLayout()
1538 vbox.addLayout(main_layout)
1539 vbox.addLayout(close_button(dialog))
1541 dialog.setLayout(vbox)
1546 def show_seed_dialog(self, password):
1547 if not self.wallet.has_seed():
1548 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1552 mnemonic = self.wallet.get_mnemonic(password)
1554 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1556 from seed_dialog import SeedDialog
1557 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1562 def show_qrcode(self, data, title = _("QR code")):
1566 d.setWindowTitle(title)
1567 d.setMinimumSize(270, 300)
1568 vbox = QVBoxLayout()
1569 qrw = QRCodeWidget(data)
1570 vbox.addWidget(qrw, 1)
1571 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1572 hbox = QHBoxLayout()
1575 filename = os.path.join(self.config.path, "qrcode.bmp")
1578 bmp.save_qrcode(qrw.qr, filename)
1579 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1581 def copy_to_clipboard():
1582 bmp.save_qrcode(qrw.qr, filename)
1583 self.app.clipboard().setImage(QImage(filename))
1584 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1586 b = QPushButton(_("Copy"))
1588 b.clicked.connect(copy_to_clipboard)
1590 b = QPushButton(_("Save"))
1592 b.clicked.connect(print_qr)
1594 b = QPushButton(_("Close"))
1596 b.clicked.connect(d.accept)
1599 vbox.addLayout(hbox)
1604 def do_protect(self, func, args):
1605 if self.wallet.use_encryption:
1606 password = self.password_dialog()
1612 if args != (False,):
1613 args = (self,) + args + (password,)
1615 args = (self,password)
1619 def show_public_keys(self, address):
1620 if not address: return
1622 pubkey_list = self.wallet.get_public_keys(address)
1623 except Exception as e:
1624 traceback.print_exc(file=sys.stdout)
1625 self.show_message(str(e))
1629 d.setMinimumSize(600, 200)
1631 vbox = QVBoxLayout()
1632 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1633 vbox.addWidget( QLabel(_("Public key") + ':'))
1635 keys.setReadOnly(True)
1636 keys.setText('\n'.join(pubkey_list))
1637 vbox.addWidget(keys)
1638 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1639 vbox.addLayout(close_button(d))
1644 def show_private_key(self, address, password):
1645 if not address: return
1647 pk_list = self.wallet.get_private_key(address, password)
1648 except Exception as e:
1649 traceback.print_exc(file=sys.stdout)
1650 self.show_message(str(e))
1654 d.setMinimumSize(600, 200)
1656 vbox = QVBoxLayout()
1657 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1658 vbox.addWidget( QLabel(_("Private key") + ':'))
1660 keys.setReadOnly(True)
1661 keys.setText('\n'.join(pk_list))
1662 vbox.addWidget(keys)
1663 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1664 vbox.addLayout(close_button(d))
1670 def do_sign(self, address, message, signature, password):
1671 message = unicode(message.toPlainText())
1672 message = message.encode('utf-8')
1674 sig = self.wallet.sign_message(str(address.text()), message, password)
1675 signature.setText(sig)
1676 except Exception as e:
1677 self.show_message(str(e))
1679 def do_verify(self, address, message, signature):
1680 message = unicode(message.toPlainText())
1681 message = message.encode('utf-8')
1682 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1683 self.show_message(_("Signature verified"))
1685 self.show_message(_("Error: wrong signature"))
1688 def sign_verify_message(self, address=''):
1691 d.setWindowTitle(_('Sign/verify Message'))
1692 d.setMinimumSize(410, 290)
1694 layout = QGridLayout(d)
1696 message_e = QTextEdit()
1697 layout.addWidget(QLabel(_('Message')), 1, 0)
1698 layout.addWidget(message_e, 1, 1)
1699 layout.setRowStretch(2,3)
1701 address_e = QLineEdit()
1702 address_e.setText(address)
1703 layout.addWidget(QLabel(_('Address')), 2, 0)
1704 layout.addWidget(address_e, 2, 1)
1706 signature_e = QTextEdit()
1707 layout.addWidget(QLabel(_('Signature')), 3, 0)
1708 layout.addWidget(signature_e, 3, 1)
1709 layout.setRowStretch(3,1)
1711 hbox = QHBoxLayout()
1713 b = QPushButton(_("Sign"))
1714 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1717 b = QPushButton(_("Verify"))
1718 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1721 b = QPushButton(_("Close"))
1722 b.clicked.connect(d.accept)
1724 layout.addLayout(hbox, 4, 1)
1729 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1731 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1732 message_e.setText(decrypted)
1733 except Exception as e:
1734 self.show_message(str(e))
1737 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1738 message = unicode(message_e.toPlainText())
1739 message = message.encode('utf-8')
1741 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1742 encrypted_e.setText(encrypted)
1743 except Exception as e:
1744 self.show_message(str(e))
1748 def encrypt_message(self, address = ''):
1751 d.setWindowTitle(_('Encrypt/decrypt Message'))
1752 d.setMinimumSize(610, 490)
1754 layout = QGridLayout(d)
1756 message_e = QTextEdit()
1757 layout.addWidget(QLabel(_('Message')), 1, 0)
1758 layout.addWidget(message_e, 1, 1)
1759 layout.setRowStretch(2,3)
1761 pubkey_e = QLineEdit()
1763 pubkey = self.wallet.getpubkeys(address)[0]
1764 pubkey_e.setText(pubkey)
1765 layout.addWidget(QLabel(_('Public key')), 2, 0)
1766 layout.addWidget(pubkey_e, 2, 1)
1768 encrypted_e = QTextEdit()
1769 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1770 layout.addWidget(encrypted_e, 3, 1)
1771 layout.setRowStretch(3,1)
1773 hbox = QHBoxLayout()
1774 b = QPushButton(_("Encrypt"))
1775 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1778 b = QPushButton(_("Decrypt"))
1779 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1782 b = QPushButton(_("Close"))
1783 b.clicked.connect(d.accept)
1786 layout.addLayout(hbox, 4, 1)
1790 def question(self, msg):
1791 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1793 def show_message(self, msg):
1794 QMessageBox.information(self, _('Message'), msg, _('OK'))
1796 def password_dialog(self, msg=None):
1799 d.setWindowTitle(_("Enter Password"))
1804 vbox = QVBoxLayout()
1806 msg = _('Please enter your password')
1807 vbox.addWidget(QLabel(msg))
1809 grid = QGridLayout()
1811 grid.addWidget(QLabel(_('Password')), 1, 0)
1812 grid.addWidget(pw, 1, 1)
1813 vbox.addLayout(grid)
1815 vbox.addLayout(ok_cancel_buttons(d))
1818 run_hook('password_dialog', pw, grid, 1)
1819 if not d.exec_(): return
1820 return unicode(pw.text())
1829 def tx_from_text(self, txt):
1830 "json or raw hexadecimal"
1833 tx = Transaction(txt)
1839 tx_dict = json.loads(str(txt))
1840 assert "hex" in tx_dict.keys()
1841 tx = Transaction(tx_dict["hex"])
1842 if tx_dict.has_key("input_info"):
1843 input_info = json.loads(tx_dict['input_info'])
1844 tx.add_input_info(input_info)
1847 traceback.print_exc(file=sys.stdout)
1850 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1854 def read_tx_from_file(self):
1855 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1859 with open(fileName, "r") as f:
1860 file_content = f.read()
1861 except (ValueError, IOError, os.error), reason:
1862 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1864 return self.tx_from_text(file_content)
1868 def sign_raw_transaction(self, tx, input_info, password):
1869 self.wallet.signrawtransaction(tx, input_info, [], password)
1871 def do_process_from_text(self):
1872 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1875 tx = self.tx_from_text(text)
1877 self.show_transaction(tx)
1879 def do_process_from_file(self):
1880 tx = self.read_tx_from_file()
1882 self.show_transaction(tx)
1884 def do_process_from_txid(self):
1885 from electrum import transaction
1886 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1888 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1890 tx = transaction.Transaction(r)
1892 self.show_transaction(tx)
1894 self.show_message("unknown transaction")
1896 def do_process_from_csvReader(self, csvReader):
1901 for position, row in enumerate(csvReader):
1903 if not is_valid(address):
1904 errors.append((position, address))
1906 amount = Decimal(row[1])
1907 amount = int(100000000*amount)
1908 outputs.append((address, amount))
1909 except (ValueError, IOError, os.error), reason:
1910 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1914 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1915 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1919 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1920 except Exception as e:
1921 self.show_message(str(e))
1924 self.show_transaction(tx)
1926 def do_process_from_csv_file(self):
1927 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1931 with open(fileName, "r") as f:
1932 csvReader = csv.reader(f)
1933 self.do_process_from_csvReader(csvReader)
1934 except (ValueError, IOError, os.error), reason:
1935 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1938 def do_process_from_csv_text(self):
1939 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1940 + _("Format: address, amount. One output per line"), _("Load CSV"))
1943 f = StringIO.StringIO(text)
1944 csvReader = csv.reader(f)
1945 self.do_process_from_csvReader(csvReader)
1950 def export_privkeys_dialog(self, password):
1951 if self.wallet.is_watching_only():
1952 self.show_message(_("This is a watching-only wallet"))
1956 d.setWindowTitle(_('Private keys'))
1957 d.setMinimumSize(850, 300)
1958 vbox = QVBoxLayout(d)
1960 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1961 _("Exposing a single private key can compromise your entire wallet!"),
1962 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1963 vbox.addWidget(QLabel(msg))
1969 defaultname = 'electrum-private-keys.csv'
1970 select_msg = _('Select file to export your private keys to')
1971 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1972 vbox.addLayout(hbox)
1974 h, b = ok_cancel_buttons2(d, _('Export'))
1979 addresses = self.wallet.addresses(True)
1981 def privkeys_thread():
1982 for addr in addresses:
1986 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1987 d.emit(SIGNAL('computing_privkeys'))
1988 d.emit(SIGNAL('show_privkeys'))
1990 def show_privkeys():
1991 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1995 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1996 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1997 threading.Thread(target=privkeys_thread).start()
2003 filename = filename_e.text()
2008 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2009 except (IOError, os.error), reason:
2010 export_error_label = _("Electrum was unable to produce a private key-export.")
2011 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2013 except Exception as e:
2014 self.show_message(str(e))
2017 self.show_message(_("Private keys exported."))
2020 def do_export_privkeys(self, fileName, pklist, is_csv):
2021 with open(fileName, "w+") as f:
2023 transaction = csv.writer(f)
2024 transaction.writerow(["address", "private_key"])
2025 for addr, pk in pklist.items():
2026 transaction.writerow(["%34s"%addr,pk])
2029 f.write(json.dumps(pklist, indent = 4))
2032 def do_import_labels(self):
2033 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2034 if not labelsFile: return
2036 f = open(labelsFile, 'r')
2039 for key, value in json.loads(data).items():
2040 self.wallet.set_label(key, value)
2041 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2042 except (IOError, os.error), reason:
2043 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2046 def do_export_labels(self):
2047 labels = self.wallet.labels
2049 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2051 with open(fileName, 'w+') as f:
2052 json.dump(labels, f)
2053 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2054 except (IOError, os.error), reason:
2055 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2058 def export_history_dialog(self):
2061 d.setWindowTitle(_('Export History'))
2062 d.setMinimumSize(400, 200)
2063 vbox = QVBoxLayout(d)
2065 defaultname = os.path.expanduser('~/electrum-history.csv')
2066 select_msg = _('Select file to export your wallet transactions to')
2068 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2069 vbox.addLayout(hbox)
2073 h, b = ok_cancel_buttons2(d, _('Export'))
2078 filename = filename_e.text()
2083 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2084 except (IOError, os.error), reason:
2085 export_error_label = _("Electrum was unable to produce a transaction export.")
2086 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2089 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2092 def do_export_history(self, wallet, fileName, is_csv):
2093 history = wallet.get_tx_history()
2095 for item in history:
2096 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2098 if timestamp is not None:
2100 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2101 except [RuntimeError, TypeError, NameError] as reason:
2102 time_string = "unknown"
2105 time_string = "unknown"
2107 time_string = "pending"
2109 if value is not None:
2110 value_string = format_satoshis(value, True)
2115 fee_string = format_satoshis(fee, True)
2120 label, is_default_label = wallet.get_label(tx_hash)
2121 label = label.encode('utf-8')
2125 balance_string = format_satoshis(balance, False)
2127 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2129 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2131 with open(fileName, "w+") as f:
2133 transaction = csv.writer(f)
2134 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2136 transaction.writerow(line)
2139 f.write(json.dumps(lines, indent = 4))
2142 def sweep_key_dialog(self):
2144 d.setWindowTitle(_('Sweep private keys'))
2145 d.setMinimumSize(600, 300)
2147 vbox = QVBoxLayout(d)
2148 vbox.addWidget(QLabel(_("Enter private keys")))
2150 keys_e = QTextEdit()
2151 keys_e.setTabChangesFocus(True)
2152 vbox.addWidget(keys_e)
2154 h, address_e = address_field(self.wallet.addresses())
2158 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2159 vbox.addLayout(hbox)
2160 button.setEnabled(False)
2163 addr = str(address_e.text())
2164 if bitcoin.is_address(addr):
2168 pk = str(keys_e.toPlainText()).strip()
2169 if Wallet.is_private_key(pk):
2172 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2173 keys_e.textChanged.connect(f)
2174 address_e.textChanged.connect(f)
2178 fee = self.wallet.fee
2179 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2180 self.show_transaction(tx)
2184 def do_import_privkey(self, password):
2185 if not self.wallet.has_imported_keys():
2186 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2187 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2188 + _('Are you sure you understand what you are doing?'), 3, 4)
2191 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2194 text = str(text).split()
2199 addr = self.wallet.import_key(key, password)
2200 except Exception as e:
2206 addrlist.append(addr)
2208 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2210 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2211 self.update_receive_tab()
2212 self.update_history_tab()
2215 def settings_dialog(self):
2217 d.setWindowTitle(_('Electrum Settings'))
2219 vbox = QVBoxLayout()
2220 grid = QGridLayout()
2221 grid.setColumnStretch(0,1)
2223 nz_label = QLabel(_('Display zeros') + ':')
2224 grid.addWidget(nz_label, 0, 0)
2225 nz_e = AmountEdit(None,True)
2226 nz_e.setText("%d"% self.num_zeros)
2227 grid.addWidget(nz_e, 0, 1)
2228 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2229 grid.addWidget(HelpButton(msg), 0, 2)
2230 if not self.config.is_modifiable('num_zeros'):
2231 for w in [nz_e, nz_label]: w.setEnabled(False)
2233 lang_label=QLabel(_('Language') + ':')
2234 grid.addWidget(lang_label, 1, 0)
2235 lang_combo = QComboBox()
2236 from electrum.i18n import languages
2237 lang_combo.addItems(languages.values())
2239 index = languages.keys().index(self.config.get("language",''))
2242 lang_combo.setCurrentIndex(index)
2243 grid.addWidget(lang_combo, 1, 1)
2244 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2245 if not self.config.is_modifiable('language'):
2246 for w in [lang_combo, lang_label]: w.setEnabled(False)
2249 fee_label = QLabel(_('Transaction fee') + ':')
2250 grid.addWidget(fee_label, 2, 0)
2251 fee_e = AmountEdit(self.get_decimal_point)
2252 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2253 grid.addWidget(fee_e, 2, 1)
2254 msg = _('Fee per kilobyte of transaction.') + ' ' \
2255 + _('Recommended value') + ': ' + self.format_amount(20000)
2256 grid.addWidget(HelpButton(msg), 2, 2)
2257 if not self.config.is_modifiable('fee_per_kb'):
2258 for w in [fee_e, fee_label]: w.setEnabled(False)
2260 units = ['BTC', 'mBTC']
2261 unit_label = QLabel(_('Base unit') + ':')
2262 grid.addWidget(unit_label, 3, 0)
2263 unit_combo = QComboBox()
2264 unit_combo.addItems(units)
2265 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2266 grid.addWidget(unit_combo, 3, 1)
2267 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2268 + '\n1BTC=1000mBTC.\n' \
2269 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2271 usechange_cb = QCheckBox(_('Use change addresses'))
2272 usechange_cb.setChecked(self.wallet.use_change)
2273 grid.addWidget(usechange_cb, 4, 0)
2274 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2275 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2277 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2278 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2279 grid.addWidget(block_ex_label, 5, 0)
2280 block_ex_combo = QComboBox()
2281 block_ex_combo.addItems(block_explorers)
2282 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2283 grid.addWidget(block_ex_combo, 5, 1)
2284 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2286 show_tx = self.config.get('show_before_broadcast', False)
2287 showtx_cb = QCheckBox(_('Show before broadcast'))
2288 showtx_cb.setChecked(show_tx)
2289 grid.addWidget(showtx_cb, 6, 0)
2290 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2292 vbox.addLayout(grid)
2294 vbox.addLayout(ok_cancel_buttons(d))
2298 if not d.exec_(): return
2301 fee = self.fee_e.get_amount()
2303 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2306 self.wallet.set_fee(fee)
2308 nz = unicode(nz_e.text())
2313 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2316 if self.num_zeros != nz:
2318 self.config.set_key('num_zeros', nz, True)
2319 self.update_history_tab()
2320 self.update_receive_tab()
2322 usechange_result = usechange_cb.isChecked()
2323 if self.wallet.use_change != usechange_result:
2324 self.wallet.use_change = usechange_result
2325 self.wallet.storage.put('use_change', self.wallet.use_change)
2327 if showtx_cb.isChecked() != show_tx:
2328 self.config.set_key('show_before_broadcast', not show_tx)
2330 unit_result = units[unit_combo.currentIndex()]
2331 if self.base_unit() != unit_result:
2332 self.decimal_point = 8 if unit_result == 'BTC' else 5
2333 self.config.set_key('decimal_point', self.decimal_point, True)
2334 self.update_history_tab()
2335 self.update_status()
2337 need_restart = False
2339 lang_request = languages.keys()[lang_combo.currentIndex()]
2340 if lang_request != self.config.get('language'):
2341 self.config.set_key("language", lang_request, True)
2344 be_result = block_explorers[block_ex_combo.currentIndex()]
2345 self.config.set_key('block_explorer', be_result, True)
2347 run_hook('close_settings_dialog')
2350 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2353 def run_network_dialog(self):
2354 if not self.network:
2356 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2358 def closeEvent(self, event):
2360 self.config.set_key("is_maximized", self.isMaximized())
2361 if not self.isMaximized():
2363 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2364 self.save_column_widths()
2365 self.config.set_key("console-history", self.console.history[-50:], True)
2366 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2370 def plugins_dialog(self):
2371 from electrum.plugins import plugins
2374 d.setWindowTitle(_('Electrum Plugins'))
2377 vbox = QVBoxLayout(d)
2380 scroll = QScrollArea()
2381 scroll.setEnabled(True)
2382 scroll.setWidgetResizable(True)
2383 scroll.setMinimumSize(400,250)
2384 vbox.addWidget(scroll)
2388 w.setMinimumHeight(len(plugins)*35)
2390 grid = QGridLayout()
2391 grid.setColumnStretch(0,1)
2394 def do_toggle(cb, p, w):
2397 if w: w.setEnabled(r)
2399 def mk_toggle(cb, p, w):
2400 return lambda: do_toggle(cb,p,w)
2402 for i, p in enumerate(plugins):
2404 cb = QCheckBox(p.fullname())
2405 cb.setDisabled(not p.is_available())
2406 cb.setChecked(p.is_enabled())
2407 grid.addWidget(cb, i, 0)
2408 if p.requires_settings():
2409 w = p.settings_widget(self)
2410 w.setEnabled( p.is_enabled() )
2411 grid.addWidget(w, i, 1)
2414 cb.clicked.connect(mk_toggle(cb,p,w))
2415 grid.addWidget(HelpButton(p.description()), i, 2)
2417 print_msg(_("Error: cannot display plugin"), p)
2418 traceback.print_exc(file=sys.stdout)
2419 grid.setRowStretch(i+1,1)
2421 vbox.addLayout(close_button(d))
2426 def show_account_details(self, k):
2427 account = self.wallet.accounts[k]
2430 d.setWindowTitle(_('Account Details'))
2433 vbox = QVBoxLayout(d)
2434 name = self.wallet.get_account_name(k)
2435 label = QLabel('Name: ' + name)
2436 vbox.addWidget(label)
2438 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2440 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2442 vbox.addWidget(QLabel(_('Master Public Key:')))
2445 text.setReadOnly(True)
2446 text.setMaximumHeight(170)
2447 vbox.addWidget(text)
2449 mpk_text = '\n'.join( account.get_master_pubkeys() )
2450 text.setText(mpk_text)
2452 vbox.addLayout(close_button(d))