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
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))
121 set_language(config.get('language'))
123 self.funds_error = False
124 self.payment_request = None
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_console_tab(), _('Console') )
134 tabs.setMinimumSize(600, 400)
135 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
136 self.setCentralWidget(tabs)
138 g = self.config.get("winpos-qt",[100, 100, 840, 400])
139 self.setGeometry(g[0], g[1], g[2], g[3])
140 if self.config.get("is_maximized"):
143 self.setWindowIcon(QIcon(":icons/electrum.png"))
146 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
147 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
148 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
149 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
150 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
152 for i in range(tabs.count()):
153 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
155 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
156 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
157 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
158 self.connect(self, QtCore.SIGNAL('send_tx2'), self.send_tx2)
159 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
160 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
161 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
163 self.history_list.setFocus(True)
167 self.network.register_callback('updated', lambda: self.need_update.set())
168 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
169 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
170 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
171 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
173 # set initial message
174 self.console.showMessage(self.network.banner)
179 def update_account_selector(self):
181 accounts = self.wallet.get_account_names()
182 self.account_selector.clear()
183 if len(accounts) > 1:
184 self.account_selector.addItems([_("All accounts")] + accounts.values())
185 self.account_selector.setCurrentIndex(0)
186 self.account_selector.show()
188 self.account_selector.hide()
191 def load_wallet(self, wallet):
194 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
195 self.current_account = self.wallet.storage.get("current_account", None)
197 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
198 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
199 self.setWindowTitle( title )
201 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
202 self.notify_transactions()
203 self.update_account_selector()
205 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
206 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
207 self.password_menu.setEnabled(not self.wallet.is_watching_only())
208 self.seed_menu.setEnabled(self.wallet.has_seed())
209 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
210 self.import_menu.setEnabled(self.wallet.can_import())
212 self.update_lock_icon()
213 self.update_buttons_on_seed()
214 self.update_console()
216 run_hook('load_wallet', wallet)
219 def open_wallet(self):
220 wallet_folder = self.wallet.storage.path
221 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
225 storage = WalletStorage({'wallet_path': filename})
226 if not storage.file_exists:
227 self.show_message("file not found "+ filename)
230 self.wallet.stop_threads()
233 wallet = Wallet(storage)
234 wallet.start_threads(self.network)
236 self.load_wallet(wallet)
240 def backup_wallet(self):
242 path = self.wallet.storage.path
243 wallet_folder = os.path.dirname(path)
244 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
248 new_path = os.path.join(wallet_folder, filename)
251 shutil.copy2(path, new_path)
252 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
253 except (IOError, os.error), reason:
254 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
257 def new_wallet(self):
260 wallet_folder = os.path.dirname(self.wallet.storage.path)
261 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
264 filename = os.path.join(wallet_folder, filename)
266 storage = WalletStorage({'wallet_path': filename})
267 if storage.file_exists:
268 QMessageBox.critical(None, "Error", _("File exists"))
271 wizard = installwizard.InstallWizard(self.config, self.network, storage)
272 wallet = wizard.run('new')
274 self.load_wallet(wallet)
278 def init_menubar(self):
281 file_menu = menubar.addMenu(_("&File"))
282 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
283 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
284 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
285 file_menu.addAction(_("&Quit"), self.close)
287 wallet_menu = menubar.addMenu(_("&Wallet"))
288 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
289 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
291 wallet_menu.addSeparator()
293 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
294 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
295 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
297 wallet_menu.addSeparator()
298 labels_menu = wallet_menu.addMenu(_("&Labels"))
299 labels_menu.addAction(_("&Import"), self.do_import_labels)
300 labels_menu.addAction(_("&Export"), self.do_export_labels)
302 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
303 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
304 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
305 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
306 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
308 tools_menu = menubar.addMenu(_("&Tools"))
310 # Settings / Preferences are all reserved keywords in OSX using this as work around
311 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
312 tools_menu.addAction(_("&Network"), self.run_network_dialog)
313 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
314 tools_menu.addSeparator()
315 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
316 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
317 tools_menu.addSeparator()
319 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
320 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
321 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
323 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
324 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
325 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
326 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
328 help_menu = menubar.addMenu(_("&Help"))
329 help_menu.addAction(_("&About"), self.show_about)
330 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
331 help_menu.addSeparator()
332 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
333 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
335 self.setMenuBar(menubar)
337 def show_about(self):
338 QMessageBox.about(self, "Electrum",
339 _("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."))
341 def show_report_bug(self):
342 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
343 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
346 def notify_transactions(self):
347 if not self.network or not self.network.is_connected():
350 print_error("Notifying GUI")
351 if len(self.network.pending_transactions_for_notifications) > 0:
352 # Combine the transactions if there are more then three
353 tx_amount = len(self.network.pending_transactions_for_notifications)
356 for tx in self.network.pending_transactions_for_notifications:
357 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
361 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
362 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
364 self.network.pending_transactions_for_notifications = []
366 for tx in self.network.pending_transactions_for_notifications:
368 self.network.pending_transactions_for_notifications.remove(tx)
369 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
371 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
373 def notify(self, message):
374 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
378 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
379 def getOpenFileName(self, title, filter = ""):
380 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
381 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
382 if fileName and directory != os.path.dirname(fileName):
383 self.config.set_key('io_dir', os.path.dirname(fileName), True)
386 def getSaveFileName(self, title, filename, filter = ""):
387 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
388 path = os.path.join( directory, filename )
389 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
390 if fileName and directory != os.path.dirname(fileName):
391 self.config.set_key('io_dir', os.path.dirname(fileName), True)
395 QMainWindow.close(self)
396 run_hook('close_main_window')
398 def connect_slots(self, sender):
399 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
400 self.previous_payto_e=''
402 def timer_actions(self):
403 if self.need_update.is_set():
405 self.need_update.clear()
406 run_hook('timer_actions')
408 def format_amount(self, x, is_diff=False, whitespaces=False):
409 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
411 def read_amount(self, x):
412 if x in['.', '']: return None
413 p = pow(10, self.decimal_point)
414 return int( p * Decimal(x) )
417 assert self.decimal_point in [5,8]
418 return "BTC" if self.decimal_point == 8 else "mBTC"
421 def update_status(self):
422 if self.network is None or not self.network.is_running():
424 icon = QIcon(":icons/status_disconnected.png")
426 elif self.network.is_connected():
427 if not self.wallet.up_to_date:
428 text = _("Synchronizing...")
429 icon = QIcon(":icons/status_waiting.png")
430 elif self.network.server_lag > 1:
431 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
432 icon = QIcon(":icons/status_lagging.png")
434 c, u = self.wallet.get_account_balance(self.current_account)
435 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
436 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
438 # append fiat balance and price from exchange rate plugin
440 run_hook('get_fiat_status_text', c+u, r)
445 self.tray.setToolTip(text)
446 icon = QIcon(":icons/status_connected.png")
448 text = _("Not connected")
449 icon = QIcon(":icons/status_disconnected.png")
451 self.balance_label.setText(text)
452 self.status_button.setIcon( icon )
455 def update_wallet(self):
457 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
458 self.update_history_tab()
459 self.update_receive_tab()
460 self.update_contacts_tab()
461 self.update_completions()
464 def create_history_tab(self):
465 self.history_list = l = MyTreeWidget(self)
467 for i,width in enumerate(self.column_widths['history']):
468 l.setColumnWidth(i, width)
469 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
470 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
471 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
473 l.customContextMenuRequested.connect(self.create_history_menu)
477 def create_history_menu(self, position):
478 self.history_list.selectedIndexes()
479 item = self.history_list.currentItem()
480 be = self.config.get('block_explorer', 'Blockchain.info')
481 if be == 'Blockchain.info':
482 block_explorer = 'https://blockchain.info/tx/'
483 elif be == 'Blockr.io':
484 block_explorer = 'https://blockr.io/tx/info/'
485 elif be == 'Insight.is':
486 block_explorer = 'http://live.insight.is/tx/'
488 tx_hash = str(item.data(0, Qt.UserRole).toString())
489 if not tx_hash: return
491 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
492 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
493 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
494 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
495 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
498 def show_transaction(self, tx):
499 import transaction_dialog
500 d = transaction_dialog.TxDialog(tx, self)
503 def tx_label_clicked(self, item, column):
504 if column==2 and item.isSelected():
506 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
507 self.history_list.editItem( item, column )
508 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
511 def tx_label_changed(self, item, column):
515 tx_hash = str(item.data(0, Qt.UserRole).toString())
516 tx = self.wallet.transactions.get(tx_hash)
517 text = unicode( item.text(2) )
518 self.wallet.set_label(tx_hash, text)
520 item.setForeground(2, QBrush(QColor('black')))
522 text = self.wallet.get_default_label(tx_hash)
523 item.setText(2, text)
524 item.setForeground(2, QBrush(QColor('gray')))
528 def edit_label(self, is_recv):
529 l = self.receive_list if is_recv else self.contacts_list
530 item = l.currentItem()
531 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
532 l.editItem( item, 1 )
533 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
537 def address_label_clicked(self, item, column, l, column_addr, column_label):
538 if column == column_label and item.isSelected():
539 is_editable = item.data(0, 32).toBool()
542 addr = unicode( item.text(column_addr) )
543 label = unicode( item.text(column_label) )
544 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
545 l.editItem( item, column )
546 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
549 def address_label_changed(self, item, column, l, column_addr, column_label):
550 if column == column_label:
551 addr = unicode( item.text(column_addr) )
552 text = unicode( item.text(column_label) )
553 is_editable = item.data(0, 32).toBool()
557 changed = self.wallet.set_label(addr, text)
559 self.update_history_tab()
560 self.update_completions()
562 self.current_item_changed(item)
564 run_hook('item_changed', item, column)
567 def current_item_changed(self, a):
568 run_hook('current_item_changed', a)
572 def update_history_tab(self):
574 self.history_list.clear()
575 for item in self.wallet.get_tx_history(self.current_account):
576 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
577 time_str = _("unknown")
580 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
582 time_str = _("error")
585 time_str = 'unverified'
586 icon = QIcon(":icons/unconfirmed.png")
589 icon = QIcon(":icons/unconfirmed.png")
591 icon = QIcon(":icons/clock%d.png"%conf)
593 icon = QIcon(":icons/confirmed.png")
595 if value is not None:
596 v_str = self.format_amount(value, True, whitespaces=True)
600 balance_str = self.format_amount(balance, whitespaces=True)
603 label, is_default_label = self.wallet.get_label(tx_hash)
605 label = _('Pruned transaction outputs')
606 is_default_label = False
608 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
609 item.setFont(2, QFont(MONOSPACE_FONT))
610 item.setFont(3, QFont(MONOSPACE_FONT))
611 item.setFont(4, QFont(MONOSPACE_FONT))
613 item.setForeground(3, QBrush(QColor("#BC1E1E")))
615 item.setData(0, Qt.UserRole, tx_hash)
616 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
618 item.setForeground(2, QBrush(QColor('grey')))
620 item.setIcon(0, icon)
621 self.history_list.insertTopLevelItem(0,item)
624 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
625 run_hook('history_tab_update')
628 def create_send_tab(self):
633 grid.setColumnMinimumWidth(3,300)
634 grid.setColumnStretch(5,1)
637 self.payto_e = QLineEdit()
638 grid.addWidget(QLabel(_('Pay to')), 1, 0)
639 grid.addWidget(self.payto_e, 1, 1, 1, 3)
641 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)
643 completer = QCompleter()
644 completer.setCaseSensitivity(False)
645 self.payto_e.setCompleter(completer)
646 completer.setModel(self.completions)
648 self.message_e = QLineEdit()
649 grid.addWidget(QLabel(_('Description')), 2, 0)
650 grid.addWidget(self.message_e, 2, 1, 1, 3)
651 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)
653 self.from_label = QLabel(_('From'))
654 grid.addWidget(self.from_label, 3, 0)
655 self.from_list = QTreeWidget(self)
656 self.from_list.setColumnCount(2)
657 self.from_list.setColumnWidth(0, 350)
658 self.from_list.setColumnWidth(1, 50)
659 self.from_list.setHeaderHidden (True)
660 self.from_list.setMaximumHeight(80)
661 grid.addWidget(self.from_list, 3, 1, 1, 3)
662 self.set_pay_from([])
664 self.amount_e = AmountEdit(self.base_unit)
665 grid.addWidget(QLabel(_('Amount')), 4, 0)
666 grid.addWidget(self.amount_e, 4, 1, 1, 2)
667 grid.addWidget(HelpButton(
668 _('Amount to be sent.') + '\n\n' \
669 + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') \
670 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
672 self.fee_e = AmountEdit(self.base_unit)
673 grid.addWidget(QLabel(_('Fee')), 5, 0)
674 grid.addWidget(self.fee_e, 5, 1, 1, 2)
675 grid.addWidget(HelpButton(
676 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
677 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
678 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 5, 3)
680 run_hook('exchange_rate_button', grid)
682 self.send_button = EnterButton(_("Send"), self.do_send)
683 grid.addWidget(self.send_button, 6, 1)
685 b = EnterButton(_("Clear"),self.do_clear)
686 grid.addWidget(b, 6, 2)
688 self.payto_sig = QLabel('')
689 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
691 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
692 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
701 def entry_changed( is_fee ):
702 self.funds_error = False
704 if self.amount_e.is_shortcut:
705 self.amount_e.is_shortcut = False
706 sendable = self.get_sendable_balance()
707 # there is only one output because we are completely spending inputs
708 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
709 fee = self.wallet.estimated_fee(inputs, 1)
711 self.amount_e.setText( self.format_amount(amount) )
712 self.fee_e.setText( self.format_amount( fee ) )
715 amount = self.read_amount(str(self.amount_e.text()))
716 fee = self.read_amount(str(self.fee_e.text()))
718 if not is_fee: fee = None
721 # assume that there will be 2 outputs (one for change)
722 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
724 self.fee_e.setText( self.format_amount( fee ) )
727 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
731 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
732 self.funds_error = True
733 text = _( "Not enough funds" )
734 c, u = self.wallet.get_frozen_balance()
735 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
737 self.statusBar().showMessage(text)
738 self.amount_e.setPalette(palette)
739 self.fee_e.setPalette(palette)
741 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
742 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
744 run_hook('create_send_tab', grid)
748 def set_pay_from(self, l):
750 self.from_list.clear()
751 self.from_label.setHidden(len(self.pay_from) == 0)
752 self.from_list.setHidden(len(self.pay_from) == 0)
753 for addr in self.pay_from:
754 c, u = self.wallet.get_addr_balance(addr)
755 balance = self.format_amount(c + u)
756 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
759 def update_completions(self):
761 for addr,label in self.wallet.labels.items():
762 if addr in self.wallet.addressbook:
763 l.append( label + ' <' + addr + '>')
765 run_hook('update_completions', l)
766 self.completions.setStringList(l)
770 return lambda s, *args: s.do_protect(func, args)
774 label = unicode( self.message_e.text() )
776 if self.gui_object.payment_request:
777 outputs = self.gui_object.payment_request.outputs
778 amount = self.gui_object.payment_request.get_amount()
781 r = unicode( self.payto_e.text() )
784 # label or alias, with address in brackets
785 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
786 to_address = m.group(2) if m else r
787 if not is_valid(to_address):
788 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
792 amount = self.read_amount(unicode( self.amount_e.text()))
794 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
797 outputs = [(to_address, amount)]
800 fee = self.read_amount(unicode( self.fee_e.text()))
802 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
805 confirm_amount = self.config.get('confirm_amount', 100000000)
806 if amount >= confirm_amount:
807 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
810 confirm_fee = self.config.get('confirm_fee', 100000)
811 if fee >= confirm_fee:
812 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()}):
815 self.send_tx(outputs, fee, label)
818 def waiting_dialog(self, message):
820 d.setWindowTitle('Please wait')
822 vbox = QVBoxLayout(d)
829 def send_tx(self, outputs, fee, label, password):
831 # first, create an unsigned tx
832 domain = self.get_payment_sources()
834 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
836 except Exception as e:
837 traceback.print_exc(file=sys.stdout)
838 self.show_message(str(e))
841 # call hook to see if plugin needs gui interaction
842 run_hook('send_tx', tx)
848 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
849 self.wallet.sign_transaction(tx, keypairs, password)
850 self.signed_tx_data = (tx, fee, label)
851 self.emit(SIGNAL('send_tx2'))
852 self.tx_wait_dialog = self.waiting_dialog('Signing..')
853 threading.Thread(target=sign_thread).start()
858 tx, fee, label = self.signed_tx_data
859 self.tx_wait_dialog.accept()
862 self.show_message(tx.error)
865 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
866 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
870 self.wallet.set_label(tx.hash(), label)
872 if not tx.is_complete() or self.config.get('show_before_broadcast'):
873 self.show_transaction(tx)
876 def broadcast_thread():
877 if self.payment_request:
878 refund_address = self.wallet.addresses()[0]
879 self.payment_request.send_ack(str(tx), refund_address)
880 self.payment_request = None
881 # note: BIP 70 recommends not broadcasting the tx to the network and letting the merchant do that
882 self.tx_broadcast_result = self.wallet.sendtx(tx)
883 self.emit(SIGNAL('send_tx3'))
885 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
886 threading.Thread(target=broadcast_thread).start()
891 self.tx_broadcast_dialog.accept()
892 status, msg = self.tx_broadcast_result
894 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
897 QMessageBox.warning(self, _('Error'), msg, _('OK'))
901 def prepare_for_payment_request(self):
902 style = "QWidget { background-color:none;border:none;}"
903 self.tabs.setCurrentIndex(1)
904 self.payto_e.setReadOnly(True)
905 self.payto_e.setStyleSheet(style)
906 self.amount_e.setReadOnly(True)
907 self.payto_e.setText(_("please wait..."))
908 self.amount_e.setStyleSheet(style)
911 def payment_request_ok(self):
912 self.payto_e.setText(self.gui_object.payment_request.domain)
913 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
915 def payment_request_error(self):
916 self.payto_e.setText(self.gui_object.payment_request.error)
919 def set_send(self, address, amount, label, message):
921 if label and self.wallet.labels.get(address) != label:
922 if self.question('Give label "%s" to address %s ?'%(label,address)):
923 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
924 self.wallet.addressbook.append(address)
925 self.wallet.set_label(address, label)
927 self.tabs.setCurrentIndex(1)
928 label = self.wallet.labels.get(address)
929 m_addr = label + ' <'+ address +'>' if label else address
930 self.payto_e.setText(m_addr)
932 self.message_e.setText(message)
934 self.amount_e.setText(amount)
938 self.payto_sig.setVisible(False)
939 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
941 self.set_frozen(e,False)
944 self.set_pay_from([])
947 def set_frozen(self,entry,frozen):
949 entry.setReadOnly(True)
950 entry.setFrame(False)
952 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
953 entry.setPalette(palette)
955 entry.setReadOnly(False)
958 palette.setColor(entry.backgroundRole(), QColor('white'))
959 entry.setPalette(palette)
962 def set_addrs_frozen(self,addrs,freeze):
964 if not addr: continue
965 if addr in self.wallet.frozen_addresses and not freeze:
966 self.wallet.unfreeze(addr)
967 elif addr not in self.wallet.frozen_addresses and freeze:
968 self.wallet.freeze(addr)
969 self.update_receive_tab()
973 def create_list_tab(self, headers):
974 "generic tab creation method"
975 l = MyTreeWidget(self)
976 l.setColumnCount( len(headers) )
977 l.setHeaderLabels( headers )
987 vbox.addWidget(buttons)
992 buttons.setLayout(hbox)
997 def create_receive_tab(self):
998 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
999 l.setContextMenuPolicy(Qt.CustomContextMenu)
1000 l.customContextMenuRequested.connect(self.create_receive_menu)
1001 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1002 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1003 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1004 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1005 self.receive_list = l
1006 self.receive_buttons_hbox = hbox
1013 def save_column_widths(self):
1014 self.column_widths["receive"] = []
1015 for i in range(self.receive_list.columnCount() -1):
1016 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1018 self.column_widths["history"] = []
1019 for i in range(self.history_list.columnCount() - 1):
1020 self.column_widths["history"].append(self.history_list.columnWidth(i))
1022 self.column_widths["contacts"] = []
1023 for i in range(self.contacts_list.columnCount() - 1):
1024 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1026 self.config.set_key("column_widths_2", self.column_widths, True)
1029 def create_contacts_tab(self):
1030 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1031 l.setContextMenuPolicy(Qt.CustomContextMenu)
1032 l.customContextMenuRequested.connect(self.create_contact_menu)
1033 for i,width in enumerate(self.column_widths['contacts']):
1034 l.setColumnWidth(i, width)
1036 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1037 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1038 self.contacts_list = l
1039 self.contacts_buttons_hbox = hbox
1044 def delete_imported_key(self, addr):
1045 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1046 self.wallet.delete_imported_key(addr)
1047 self.update_receive_tab()
1048 self.update_history_tab()
1050 def edit_account_label(self, k):
1051 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1053 label = unicode(text)
1054 self.wallet.set_label(k,label)
1055 self.update_receive_tab()
1057 def account_set_expanded(self, item, k, b):
1059 self.accounts_expanded[k] = b
1061 def create_account_menu(self, position, k, item):
1063 if item.isExpanded():
1064 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1066 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1067 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1068 if self.wallet.seed_version > 4:
1069 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1070 if self.wallet.account_is_pending(k):
1071 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1072 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1074 def delete_pending_account(self, k):
1075 self.wallet.delete_pending_account(k)
1076 self.update_receive_tab()
1078 def create_receive_menu(self, position):
1079 # fixme: this function apparently has a side effect.
1080 # if it is not called the menu pops up several times
1081 #self.receive_list.selectedIndexes()
1083 selected = self.receive_list.selectedItems()
1084 multi_select = len(selected) > 1
1085 addrs = [unicode(item.text(0)) for item in selected]
1086 if not multi_select:
1087 item = self.receive_list.itemAt(position)
1091 if not is_valid(addr):
1092 k = str(item.data(0,32).toString())
1094 self.create_account_menu(position, k, item)
1096 item.setExpanded(not item.isExpanded())
1100 if not multi_select:
1101 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1102 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1103 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1104 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1105 if not self.wallet.is_watching_only():
1106 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1107 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1108 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1109 if self.wallet.is_imported(addr):
1110 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1112 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1113 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1114 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1115 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1117 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1118 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1120 run_hook('receive_menu', menu, addrs)
1121 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1124 def get_sendable_balance(self):
1125 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1128 def get_payment_sources(self):
1130 return self.pay_from
1132 return self.wallet.get_account_addresses(self.current_account)
1135 def send_from_addresses(self, addrs):
1136 self.set_pay_from( addrs )
1137 self.tabs.setCurrentIndex(1)
1140 def payto(self, addr):
1142 label = self.wallet.labels.get(addr)
1143 m_addr = label + ' <' + addr + '>' if label else addr
1144 self.tabs.setCurrentIndex(1)
1145 self.payto_e.setText(m_addr)
1146 self.amount_e.setFocus()
1149 def delete_contact(self, x):
1150 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1151 self.wallet.delete_contact(x)
1152 self.wallet.set_label(x, None)
1153 self.update_history_tab()
1154 self.update_contacts_tab()
1155 self.update_completions()
1158 def create_contact_menu(self, position):
1159 item = self.contacts_list.itemAt(position)
1162 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1164 addr = unicode(item.text(0))
1165 label = unicode(item.text(1))
1166 is_editable = item.data(0,32).toBool()
1167 payto_addr = item.data(0,33).toString()
1168 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1169 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1170 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1172 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1173 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1175 run_hook('create_contact_menu', menu, item)
1176 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1179 def update_receive_item(self, item):
1180 item.setFont(0, QFont(MONOSPACE_FONT))
1181 address = str(item.data(0,0).toString())
1182 label = self.wallet.labels.get(address,'')
1183 item.setData(1,0,label)
1184 item.setData(0,32, True) # is editable
1186 run_hook('update_receive_item', address, item)
1188 if not self.wallet.is_mine(address): return
1190 c, u = self.wallet.get_addr_balance(address)
1191 balance = self.format_amount(c + u)
1192 item.setData(2,0,balance)
1194 if address in self.wallet.frozen_addresses:
1195 item.setBackgroundColor(0, QColor('lightblue'))
1198 def update_receive_tab(self):
1199 l = self.receive_list
1200 # extend the syntax for consistency
1201 l.addChild = l.addTopLevelItem
1204 for i,width in enumerate(self.column_widths['receive']):
1205 l.setColumnWidth(i, width)
1207 accounts = self.wallet.get_accounts()
1208 if self.current_account is None:
1209 account_items = sorted(accounts.items())
1211 account_items = [(self.current_account, accounts.get(self.current_account))]
1214 for k, account in account_items:
1216 if len(accounts) > 1:
1217 name = self.wallet.get_account_name(k)
1218 c,u = self.wallet.get_account_balance(k)
1219 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1220 l.addTopLevelItem(account_item)
1221 account_item.setExpanded(self.accounts_expanded.get(k, True))
1222 account_item.setData(0, 32, k)
1226 sequences = [0,1] if account.has_change() else [0]
1227 for is_change in sequences:
1228 if len(sequences) > 1:
1229 name = _("Receiving") if not is_change else _("Change")
1230 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1231 account_item.addChild(seq_item)
1233 seq_item.setExpanded(True)
1235 seq_item = account_item
1237 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1243 for address in account.get_addresses(is_change):
1244 h = self.wallet.history.get(address,[])
1248 if gap > self.wallet.gap_limit:
1253 c, u = self.wallet.get_addr_balance(address)
1254 num_tx = '*' if h == ['*'] else "%d"%len(h)
1256 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1257 self.update_receive_item(item)
1259 item.setBackgroundColor(1, QColor('red'))
1260 if len(h) > 0 and c == -u:
1262 seq_item.insertChild(0,used_item)
1264 used_item.addChild(item)
1266 seq_item.addChild(item)
1268 # we use column 1 because column 0 may be hidden
1269 l.setCurrentItem(l.topLevelItem(0),1)
1272 def update_contacts_tab(self):
1273 l = self.contacts_list
1276 for address in self.wallet.addressbook:
1277 label = self.wallet.labels.get(address,'')
1278 n = self.wallet.get_num_tx(address)
1279 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1280 item.setFont(0, QFont(MONOSPACE_FONT))
1281 # 32 = label can be edited (bool)
1282 item.setData(0,32, True)
1284 item.setData(0,33, address)
1285 l.addTopLevelItem(item)
1287 run_hook('update_contacts_tab', l)
1288 l.setCurrentItem(l.topLevelItem(0))
1292 def create_console_tab(self):
1293 from console import Console
1294 self.console = console = Console()
1298 def update_console(self):
1299 console = self.console
1300 console.history = self.config.get("console-history",[])
1301 console.history_index = len(console.history)
1303 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1304 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1306 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1308 def mkfunc(f, method):
1309 return lambda *args: apply( f, (method, args, self.password_dialog ))
1311 if m[0]=='_' or m in ['network','wallet']: continue
1312 methods[m] = mkfunc(c._run, m)
1314 console.updateNamespace(methods)
1317 def change_account(self,s):
1318 if s == _("All accounts"):
1319 self.current_account = None
1321 accounts = self.wallet.get_account_names()
1322 for k, v in accounts.items():
1324 self.current_account = k
1325 self.update_history_tab()
1326 self.update_status()
1327 self.update_receive_tab()
1329 def create_status_bar(self):
1332 sb.setFixedHeight(35)
1333 qtVersion = qVersion()
1335 self.balance_label = QLabel("")
1336 sb.addWidget(self.balance_label)
1338 from version_getter import UpdateLabel
1339 self.updatelabel = UpdateLabel(self.config, sb)
1341 self.account_selector = QComboBox()
1342 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1343 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1344 sb.addPermanentWidget(self.account_selector)
1346 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1347 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1349 self.lock_icon = QIcon()
1350 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1351 sb.addPermanentWidget( self.password_button )
1353 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1354 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1355 sb.addPermanentWidget( self.seed_button )
1356 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1357 sb.addPermanentWidget( self.status_button )
1359 run_hook('create_status_bar', (sb,))
1361 self.setStatusBar(sb)
1364 def update_lock_icon(self):
1365 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1366 self.password_button.setIcon( icon )
1369 def update_buttons_on_seed(self):
1370 if self.wallet.has_seed():
1371 self.seed_button.show()
1373 self.seed_button.hide()
1375 if not self.wallet.is_watching_only():
1376 self.password_button.show()
1377 self.send_button.setText(_("Send"))
1379 self.password_button.hide()
1380 self.send_button.setText(_("Create unsigned transaction"))
1383 def change_password_dialog(self):
1384 from password_dialog import PasswordDialog
1385 d = PasswordDialog(self.wallet, self)
1387 self.update_lock_icon()
1390 def new_contact_dialog(self):
1393 d.setWindowTitle(_("New Contact"))
1394 vbox = QVBoxLayout(d)
1395 vbox.addWidget(QLabel(_('New Contact')+':'))
1397 grid = QGridLayout()
1400 grid.addWidget(QLabel(_("Address")), 1, 0)
1401 grid.addWidget(line1, 1, 1)
1402 grid.addWidget(QLabel(_("Name")), 2, 0)
1403 grid.addWidget(line2, 2, 1)
1405 vbox.addLayout(grid)
1406 vbox.addLayout(ok_cancel_buttons(d))
1411 address = str(line1.text())
1412 label = unicode(line2.text())
1414 if not is_valid(address):
1415 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1418 self.wallet.add_contact(address)
1420 self.wallet.set_label(address, label)
1422 self.update_contacts_tab()
1423 self.update_history_tab()
1424 self.update_completions()
1425 self.tabs.setCurrentIndex(3)
1429 def new_account_dialog(self, password):
1431 dialog = QDialog(self)
1433 dialog.setWindowTitle(_("New Account"))
1435 vbox = QVBoxLayout()
1436 vbox.addWidget(QLabel(_('Account name')+':'))
1439 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1440 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1445 vbox.addLayout(ok_cancel_buttons(dialog))
1446 dialog.setLayout(vbox)
1450 name = str(e.text())
1453 self.wallet.create_pending_account(name, password)
1454 self.update_receive_tab()
1455 self.tabs.setCurrentIndex(2)
1460 def show_master_public_keys(self):
1462 dialog = QDialog(self)
1464 dialog.setWindowTitle(_("Master Public Keys"))
1466 main_layout = QGridLayout()
1467 mpk_dict = self.wallet.get_master_public_keys()
1469 for key, value in mpk_dict.items():
1470 main_layout.addWidget(QLabel(key), i, 0)
1471 mpk_text = QTextEdit()
1472 mpk_text.setReadOnly(True)
1473 mpk_text.setMaximumHeight(170)
1474 mpk_text.setText(value)
1475 main_layout.addWidget(mpk_text, i + 1, 0)
1478 vbox = QVBoxLayout()
1479 vbox.addLayout(main_layout)
1480 vbox.addLayout(close_button(dialog))
1482 dialog.setLayout(vbox)
1487 def show_seed_dialog(self, password):
1488 if not self.wallet.has_seed():
1489 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1493 mnemonic = self.wallet.get_mnemonic(password)
1495 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1497 from seed_dialog import SeedDialog
1498 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1503 def show_qrcode(self, data, title = _("QR code")):
1507 d.setWindowTitle(title)
1508 d.setMinimumSize(270, 300)
1509 vbox = QVBoxLayout()
1510 qrw = QRCodeWidget(data)
1511 vbox.addWidget(qrw, 1)
1512 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1513 hbox = QHBoxLayout()
1516 filename = os.path.join(self.config.path, "qrcode.bmp")
1519 bmp.save_qrcode(qrw.qr, filename)
1520 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1522 def copy_to_clipboard():
1523 bmp.save_qrcode(qrw.qr, filename)
1524 self.app.clipboard().setImage(QImage(filename))
1525 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1527 b = QPushButton(_("Copy"))
1529 b.clicked.connect(copy_to_clipboard)
1531 b = QPushButton(_("Save"))
1533 b.clicked.connect(print_qr)
1535 b = QPushButton(_("Close"))
1537 b.clicked.connect(d.accept)
1540 vbox.addLayout(hbox)
1545 def do_protect(self, func, args):
1546 if self.wallet.use_encryption:
1547 password = self.password_dialog()
1553 if args != (False,):
1554 args = (self,) + args + (password,)
1556 args = (self,password)
1560 def show_public_keys(self, address):
1561 if not address: return
1563 pubkey_list = self.wallet.get_public_keys(address)
1564 except Exception as e:
1565 traceback.print_exc(file=sys.stdout)
1566 self.show_message(str(e))
1570 d.setMinimumSize(600, 200)
1572 vbox = QVBoxLayout()
1573 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1574 vbox.addWidget( QLabel(_("Public key") + ':'))
1576 keys.setReadOnly(True)
1577 keys.setText('\n'.join(pubkey_list))
1578 vbox.addWidget(keys)
1579 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1580 vbox.addLayout(close_button(d))
1585 def show_private_key(self, address, password):
1586 if not address: return
1588 pk_list = self.wallet.get_private_key(address, password)
1589 except Exception as e:
1590 traceback.print_exc(file=sys.stdout)
1591 self.show_message(str(e))
1595 d.setMinimumSize(600, 200)
1597 vbox = QVBoxLayout()
1598 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1599 vbox.addWidget( QLabel(_("Private key") + ':'))
1601 keys.setReadOnly(True)
1602 keys.setText('\n'.join(pk_list))
1603 vbox.addWidget(keys)
1604 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1605 vbox.addLayout(close_button(d))
1611 def do_sign(self, address, message, signature, password):
1612 message = unicode(message.toPlainText())
1613 message = message.encode('utf-8')
1615 sig = self.wallet.sign_message(str(address.text()), message, password)
1616 signature.setText(sig)
1617 except Exception as e:
1618 self.show_message(str(e))
1620 def do_verify(self, address, message, signature):
1621 message = unicode(message.toPlainText())
1622 message = message.encode('utf-8')
1623 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1624 self.show_message(_("Signature verified"))
1626 self.show_message(_("Error: wrong signature"))
1629 def sign_verify_message(self, address=''):
1632 d.setWindowTitle(_('Sign/verify Message'))
1633 d.setMinimumSize(410, 290)
1635 layout = QGridLayout(d)
1637 message_e = QTextEdit()
1638 layout.addWidget(QLabel(_('Message')), 1, 0)
1639 layout.addWidget(message_e, 1, 1)
1640 layout.setRowStretch(2,3)
1642 address_e = QLineEdit()
1643 address_e.setText(address)
1644 layout.addWidget(QLabel(_('Address')), 2, 0)
1645 layout.addWidget(address_e, 2, 1)
1647 signature_e = QTextEdit()
1648 layout.addWidget(QLabel(_('Signature')), 3, 0)
1649 layout.addWidget(signature_e, 3, 1)
1650 layout.setRowStretch(3,1)
1652 hbox = QHBoxLayout()
1654 b = QPushButton(_("Sign"))
1655 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1658 b = QPushButton(_("Verify"))
1659 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1662 b = QPushButton(_("Close"))
1663 b.clicked.connect(d.accept)
1665 layout.addLayout(hbox, 4, 1)
1670 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1672 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1673 message_e.setText(decrypted)
1674 except Exception as e:
1675 self.show_message(str(e))
1678 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1679 message = unicode(message_e.toPlainText())
1680 message = message.encode('utf-8')
1682 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1683 encrypted_e.setText(encrypted)
1684 except Exception as e:
1685 self.show_message(str(e))
1689 def encrypt_message(self, address = ''):
1692 d.setWindowTitle(_('Encrypt/decrypt Message'))
1693 d.setMinimumSize(610, 490)
1695 layout = QGridLayout(d)
1697 message_e = QTextEdit()
1698 layout.addWidget(QLabel(_('Message')), 1, 0)
1699 layout.addWidget(message_e, 1, 1)
1700 layout.setRowStretch(2,3)
1702 pubkey_e = QLineEdit()
1704 pubkey = self.wallet.getpubkeys(address)[0]
1705 pubkey_e.setText(pubkey)
1706 layout.addWidget(QLabel(_('Public key')), 2, 0)
1707 layout.addWidget(pubkey_e, 2, 1)
1709 encrypted_e = QTextEdit()
1710 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1711 layout.addWidget(encrypted_e, 3, 1)
1712 layout.setRowStretch(3,1)
1714 hbox = QHBoxLayout()
1715 b = QPushButton(_("Encrypt"))
1716 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1719 b = QPushButton(_("Decrypt"))
1720 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1723 b = QPushButton(_("Close"))
1724 b.clicked.connect(d.accept)
1727 layout.addLayout(hbox, 4, 1)
1731 def question(self, msg):
1732 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1734 def show_message(self, msg):
1735 QMessageBox.information(self, _('Message'), msg, _('OK'))
1737 def password_dialog(self ):
1740 d.setWindowTitle(_("Enter Password"))
1745 vbox = QVBoxLayout()
1746 msg = _('Please enter your password')
1747 vbox.addWidget(QLabel(msg))
1749 grid = QGridLayout()
1751 grid.addWidget(QLabel(_('Password')), 1, 0)
1752 grid.addWidget(pw, 1, 1)
1753 vbox.addLayout(grid)
1755 vbox.addLayout(ok_cancel_buttons(d))
1758 run_hook('password_dialog', pw, grid, 1)
1759 if not d.exec_(): return
1760 return unicode(pw.text())
1769 def tx_from_text(self, txt):
1770 "json or raw hexadecimal"
1773 tx = Transaction(txt)
1779 tx_dict = json.loads(str(txt))
1780 assert "hex" in tx_dict.keys()
1781 assert "complete" in tx_dict.keys()
1782 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1783 if not tx_dict["complete"]:
1784 assert "input_info" in tx_dict.keys()
1785 input_info = json.loads(tx_dict['input_info'])
1786 tx.add_input_info(input_info)
1791 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1795 def read_tx_from_file(self):
1796 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1800 with open(fileName, "r") as f:
1801 file_content = f.read()
1802 except (ValueError, IOError, os.error), reason:
1803 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1805 return self.tx_from_text(file_content)
1809 def sign_raw_transaction(self, tx, input_info, password):
1810 self.wallet.signrawtransaction(tx, input_info, [], password)
1812 def do_process_from_text(self):
1813 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1816 tx = self.tx_from_text(text)
1818 self.show_transaction(tx)
1820 def do_process_from_file(self):
1821 tx = self.read_tx_from_file()
1823 self.show_transaction(tx)
1825 def do_process_from_txid(self):
1826 from electrum import transaction
1827 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1829 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1831 tx = transaction.Transaction(r)
1833 self.show_transaction(tx)
1835 self.show_message("unknown transaction")
1837 def do_process_from_csvReader(self, csvReader):
1842 for position, row in enumerate(csvReader):
1844 if not is_valid(address):
1845 errors.append((position, address))
1847 amount = Decimal(row[1])
1848 amount = int(100000000*amount)
1849 outputs.append((address, amount))
1850 except (ValueError, IOError, os.error), reason:
1851 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1855 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1856 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1860 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1861 except Exception as e:
1862 self.show_message(str(e))
1865 self.show_transaction(tx)
1867 def do_process_from_csv_file(self):
1868 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1872 with open(fileName, "r") as f:
1873 csvReader = csv.reader(f)
1874 self.do_process_from_csvReader(csvReader)
1875 except (ValueError, IOError, os.error), reason:
1876 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1879 def do_process_from_csv_text(self):
1880 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1881 + _("Format: address, amount. One output per line"), _("Load CSV"))
1884 f = StringIO.StringIO(text)
1885 csvReader = csv.reader(f)
1886 self.do_process_from_csvReader(csvReader)
1891 def export_privkeys_dialog(self, password):
1892 if self.wallet.is_watching_only():
1893 self.show_message(_("This is a watching-only wallet"))
1897 d.setWindowTitle(_('Private keys'))
1898 d.setMinimumSize(850, 300)
1899 vbox = QVBoxLayout(d)
1901 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1902 _("Exposing a single private key can compromise your entire wallet!"),
1903 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1904 vbox.addWidget(QLabel(msg))
1910 defaultname = 'electrum-private-keys.csv'
1911 select_msg = _('Select file to export your private keys to')
1912 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1913 vbox.addLayout(hbox)
1915 h, b = ok_cancel_buttons2(d, _('Export'))
1920 addresses = self.wallet.addresses(True)
1922 def privkeys_thread():
1923 for addr in addresses:
1927 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1928 d.emit(SIGNAL('computing_privkeys'))
1929 d.emit(SIGNAL('show_privkeys'))
1931 def show_privkeys():
1932 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1936 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1937 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1938 threading.Thread(target=privkeys_thread).start()
1944 filename = filename_e.text()
1949 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1950 except (IOError, os.error), reason:
1951 export_error_label = _("Electrum was unable to produce a private key-export.")
1952 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1954 except Exception as e:
1955 self.show_message(str(e))
1958 self.show_message(_("Private keys exported."))
1961 def do_export_privkeys(self, fileName, pklist, is_csv):
1962 with open(fileName, "w+") as f:
1964 transaction = csv.writer(f)
1965 transaction.writerow(["address", "private_key"])
1966 for addr, pk in pklist.items():
1967 transaction.writerow(["%34s"%addr,pk])
1970 f.write(json.dumps(pklist, indent = 4))
1973 def do_import_labels(self):
1974 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1975 if not labelsFile: return
1977 f = open(labelsFile, 'r')
1980 for key, value in json.loads(data).items():
1981 self.wallet.set_label(key, value)
1982 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1983 except (IOError, os.error), reason:
1984 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1987 def do_export_labels(self):
1988 labels = self.wallet.labels
1990 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1992 with open(fileName, 'w+') as f:
1993 json.dump(labels, f)
1994 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1995 except (IOError, os.error), reason:
1996 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1999 def export_history_dialog(self):
2002 d.setWindowTitle(_('Export History'))
2003 d.setMinimumSize(400, 200)
2004 vbox = QVBoxLayout(d)
2006 defaultname = os.path.expanduser('~/electrum-history.csv')
2007 select_msg = _('Select file to export your wallet transactions to')
2009 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2010 vbox.addLayout(hbox)
2014 h, b = ok_cancel_buttons2(d, _('Export'))
2019 filename = filename_e.text()
2024 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2025 except (IOError, os.error), reason:
2026 export_error_label = _("Electrum was unable to produce a transaction export.")
2027 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2030 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2033 def do_export_history(self, wallet, fileName, is_csv):
2034 history = wallet.get_tx_history()
2036 for item in history:
2037 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2039 if timestamp is not None:
2041 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2042 except [RuntimeError, TypeError, NameError] as reason:
2043 time_string = "unknown"
2046 time_string = "unknown"
2048 time_string = "pending"
2050 if value is not None:
2051 value_string = format_satoshis(value, True)
2056 fee_string = format_satoshis(fee, True)
2061 label, is_default_label = wallet.get_label(tx_hash)
2062 label = label.encode('utf-8')
2066 balance_string = format_satoshis(balance, False)
2068 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2070 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2072 with open(fileName, "w+") as f:
2074 transaction = csv.writer(f)
2075 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2077 transaction.writerow(line)
2080 f.write(json.dumps(lines, indent = 4))
2083 def sweep_key_dialog(self):
2085 d.setWindowTitle(_('Sweep private keys'))
2086 d.setMinimumSize(600, 300)
2088 vbox = QVBoxLayout(d)
2089 vbox.addWidget(QLabel(_("Enter private keys")))
2091 keys_e = QTextEdit()
2092 keys_e.setTabChangesFocus(True)
2093 vbox.addWidget(keys_e)
2095 h, address_e = address_field(self.wallet.addresses())
2099 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2100 vbox.addLayout(hbox)
2101 button.setEnabled(False)
2104 addr = str(address_e.text())
2105 if bitcoin.is_address(addr):
2109 pk = str(keys_e.toPlainText()).strip()
2110 if Wallet.is_private_key(pk):
2113 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2114 keys_e.textChanged.connect(f)
2115 address_e.textChanged.connect(f)
2119 fee = self.wallet.fee
2120 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2121 self.show_transaction(tx)
2125 def do_import_privkey(self, password):
2126 if not self.wallet.imported_keys:
2127 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2128 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2129 + _('Are you sure you understand what you are doing?'), 3, 4)
2132 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2135 text = str(text).split()
2140 addr = self.wallet.import_key(key, password)
2141 except Exception as e:
2147 addrlist.append(addr)
2149 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2151 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2152 self.update_receive_tab()
2153 self.update_history_tab()
2156 def settings_dialog(self):
2158 d.setWindowTitle(_('Electrum Settings'))
2160 vbox = QVBoxLayout()
2161 grid = QGridLayout()
2162 grid.setColumnStretch(0,1)
2164 nz_label = QLabel(_('Display zeros') + ':')
2165 grid.addWidget(nz_label, 0, 0)
2166 nz_e = AmountEdit(None,True)
2167 nz_e.setText("%d"% self.num_zeros)
2168 grid.addWidget(nz_e, 0, 1)
2169 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2170 grid.addWidget(HelpButton(msg), 0, 2)
2171 if not self.config.is_modifiable('num_zeros'):
2172 for w in [nz_e, nz_label]: w.setEnabled(False)
2174 lang_label=QLabel(_('Language') + ':')
2175 grid.addWidget(lang_label, 1, 0)
2176 lang_combo = QComboBox()
2177 from electrum.i18n import languages
2178 lang_combo.addItems(languages.values())
2180 index = languages.keys().index(self.config.get("language",''))
2183 lang_combo.setCurrentIndex(index)
2184 grid.addWidget(lang_combo, 1, 1)
2185 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2186 if not self.config.is_modifiable('language'):
2187 for w in [lang_combo, lang_label]: w.setEnabled(False)
2190 fee_label = QLabel(_('Transaction fee') + ':')
2191 grid.addWidget(fee_label, 2, 0)
2192 fee_e = AmountEdit(self.base_unit)
2193 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2194 grid.addWidget(fee_e, 2, 1)
2195 msg = _('Fee per kilobyte of transaction.') + ' ' \
2196 + _('Recommended value') + ': ' + self.format_amount(20000)
2197 grid.addWidget(HelpButton(msg), 2, 2)
2198 if not self.config.is_modifiable('fee_per_kb'):
2199 for w in [fee_e, fee_label]: w.setEnabled(False)
2201 units = ['BTC', 'mBTC']
2202 unit_label = QLabel(_('Base unit') + ':')
2203 grid.addWidget(unit_label, 3, 0)
2204 unit_combo = QComboBox()
2205 unit_combo.addItems(units)
2206 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2207 grid.addWidget(unit_combo, 3, 1)
2208 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2209 + '\n1BTC=1000mBTC.\n' \
2210 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2212 usechange_cb = QCheckBox(_('Use change addresses'))
2213 usechange_cb.setChecked(self.wallet.use_change)
2214 grid.addWidget(usechange_cb, 4, 0)
2215 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2216 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2218 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2219 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2220 grid.addWidget(block_ex_label, 5, 0)
2221 block_ex_combo = QComboBox()
2222 block_ex_combo.addItems(block_explorers)
2223 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2224 grid.addWidget(block_ex_combo, 5, 1)
2225 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2227 show_tx = self.config.get('show_before_broadcast', False)
2228 showtx_cb = QCheckBox(_('Show before broadcast'))
2229 showtx_cb.setChecked(show_tx)
2230 grid.addWidget(showtx_cb, 6, 0)
2231 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2233 vbox.addLayout(grid)
2235 vbox.addLayout(ok_cancel_buttons(d))
2239 if not d.exec_(): return
2241 fee = unicode(fee_e.text())
2243 fee = self.read_amount(fee)
2245 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2248 self.wallet.set_fee(fee)
2250 nz = unicode(nz_e.text())
2255 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2258 if self.num_zeros != nz:
2260 self.config.set_key('num_zeros', nz, True)
2261 self.update_history_tab()
2262 self.update_receive_tab()
2264 usechange_result = usechange_cb.isChecked()
2265 if self.wallet.use_change != usechange_result:
2266 self.wallet.use_change = usechange_result
2267 self.wallet.storage.put('use_change', self.wallet.use_change)
2269 if showtx_cb.isChecked() != show_tx:
2270 self.config.set_key('show_before_broadcast', not show_tx)
2272 unit_result = units[unit_combo.currentIndex()]
2273 if self.base_unit() != unit_result:
2274 self.decimal_point = 8 if unit_result == 'BTC' else 5
2275 self.config.set_key('decimal_point', self.decimal_point, True)
2276 self.update_history_tab()
2277 self.update_status()
2279 need_restart = False
2281 lang_request = languages.keys()[lang_combo.currentIndex()]
2282 if lang_request != self.config.get('language'):
2283 self.config.set_key("language", lang_request, True)
2286 be_result = block_explorers[block_ex_combo.currentIndex()]
2287 self.config.set_key('block_explorer', be_result, True)
2289 run_hook('close_settings_dialog')
2292 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2295 def run_network_dialog(self):
2296 if not self.network:
2298 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2300 def closeEvent(self, event):
2302 self.config.set_key("is_maximized", self.isMaximized())
2303 if not self.isMaximized():
2305 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2306 self.save_column_widths()
2307 self.config.set_key("console-history", self.console.history[-50:], True)
2308 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2312 def plugins_dialog(self):
2313 from electrum.plugins import plugins
2316 d.setWindowTitle(_('Electrum Plugins'))
2319 vbox = QVBoxLayout(d)
2322 scroll = QScrollArea()
2323 scroll.setEnabled(True)
2324 scroll.setWidgetResizable(True)
2325 scroll.setMinimumSize(400,250)
2326 vbox.addWidget(scroll)
2330 w.setMinimumHeight(len(plugins)*35)
2332 grid = QGridLayout()
2333 grid.setColumnStretch(0,1)
2336 def do_toggle(cb, p, w):
2339 if w: w.setEnabled(r)
2341 def mk_toggle(cb, p, w):
2342 return lambda: do_toggle(cb,p,w)
2344 for i, p in enumerate(plugins):
2346 cb = QCheckBox(p.fullname())
2347 cb.setDisabled(not p.is_available())
2348 cb.setChecked(p.is_enabled())
2349 grid.addWidget(cb, i, 0)
2350 if p.requires_settings():
2351 w = p.settings_widget(self)
2352 w.setEnabled( p.is_enabled() )
2353 grid.addWidget(w, i, 1)
2356 cb.clicked.connect(mk_toggle(cb,p,w))
2357 grid.addWidget(HelpButton(p.description()), i, 2)
2359 print_msg(_("Error: cannot display plugin"), p)
2360 traceback.print_exc(file=sys.stdout)
2361 grid.setRowStretch(i+1,1)
2363 vbox.addLayout(close_button(d))
2368 def show_account_details(self, k):
2369 account = self.wallet.accounts[k]
2372 d.setWindowTitle(_('Account Details'))
2375 vbox = QVBoxLayout(d)
2376 name = self.wallet.get_account_name(k)
2377 label = QLabel('Name: ' + name)
2378 vbox.addWidget(label)
2380 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2382 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2384 vbox.addWidget(QLabel(_('Master Public Key:')))
2387 text.setReadOnly(True)
2388 text.setMaximumHeight(170)
2389 vbox.addWidget(text)
2391 mpk_text = '\n'.join( account.get_master_pubkeys() )
2392 text.setText(mpk_text)
2394 vbox.addLayout(close_button(d))