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.tray = gui_object.tray
111 self.go_lite = gui_object.go_lite
114 self.create_status_bar()
115 self.need_update = threading.Event()
117 self.decimal_point = config.get('decimal_point', 5)
118 self.num_zeros = int(config.get('num_zeros',0))
120 set_language(config.get('language'))
122 self.funds_error = False
123 self.completions = QStringListModel()
125 self.tabs = tabs = QTabWidget(self)
126 self.column_widths = self.config.get("column_widths_2", default_column_widths )
127 tabs.addTab(self.create_history_tab(), _('History') )
128 tabs.addTab(self.create_send_tab(), _('Send') )
129 tabs.addTab(self.create_receive_tab(), _('Receive') )
130 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
131 tabs.addTab(self.create_console_tab(), _('Console') )
132 tabs.setMinimumSize(600, 400)
133 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
134 self.setCentralWidget(tabs)
136 g = self.config.get("winpos-qt",[100, 100, 840, 400])
137 self.setGeometry(g[0], g[1], g[2], g[3])
139 self.setWindowIcon(QIcon(":icons/electrum.png"))
142 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
143 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
144 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
145 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
146 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
148 for i in range(tabs.count()):
149 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
151 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
152 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
153 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
154 self.connect(self, QtCore.SIGNAL('send_tx2'), self.send_tx2)
155 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
157 self.history_list.setFocus(True)
161 self.network.register_callback('updated', lambda: self.need_update.set())
162 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
163 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
164 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
165 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
167 # set initial message
168 self.console.showMessage(self.network.banner)
173 def update_account_selector(self):
175 accounts = self.wallet.get_account_names()
176 self.account_selector.clear()
177 if len(accounts) > 1:
178 self.account_selector.addItems([_("All accounts")] + accounts.values())
179 self.account_selector.setCurrentIndex(0)
180 self.account_selector.show()
182 self.account_selector.hide()
185 def load_wallet(self, wallet):
188 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
189 self.current_account = self.wallet.storage.get("current_account", None)
191 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
192 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
193 self.setWindowTitle( title )
195 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
196 self.notify_transactions()
197 self.update_account_selector()
199 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
200 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
201 self.password_menu.setEnabled(not self.wallet.is_watching_only())
202 self.seed_menu.setEnabled(self.wallet.has_seed())
203 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
205 self.update_lock_icon()
206 self.update_buttons_on_seed()
207 self.update_console()
209 run_hook('load_wallet', wallet)
212 def open_wallet(self):
213 wallet_folder = self.wallet.storage.path
214 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
218 storage = WalletStorage({'wallet_path': filename})
219 if not storage.file_exists:
220 self.show_message("file not found "+ filename)
223 self.wallet.stop_threads()
226 wallet = Wallet(storage)
227 wallet.start_threads(self.network)
229 self.load_wallet(wallet)
233 def backup_wallet(self):
235 path = self.wallet.storage.path
236 wallet_folder = os.path.dirname(path)
237 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
241 new_path = os.path.join(wallet_folder, filename)
244 shutil.copy2(path, new_path)
245 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
246 except (IOError, os.error), reason:
247 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
250 def new_wallet(self):
253 wallet_folder = os.path.dirname(self.wallet.storage.path)
254 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
257 filename = os.path.join(wallet_folder, filename)
259 storage = WalletStorage({'wallet_path': filename})
260 if storage.file_exists:
261 QMessageBox.critical(None, "Error", _("File exists"))
264 wizard = installwizard.InstallWizard(self.config, self.network, storage)
265 wallet = wizard.run('new')
267 self.load_wallet(wallet)
271 def init_menubar(self):
274 file_menu = menubar.addMenu(_("&File"))
275 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
276 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
277 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
278 file_menu.addAction(_("&Quit"), self.close)
280 wallet_menu = menubar.addMenu(_("&Wallet"))
281 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
282 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
284 wallet_menu.addSeparator()
286 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
287 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
288 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
290 wallet_menu.addSeparator()
291 labels_menu = wallet_menu.addMenu(_("&Labels"))
292 labels_menu.addAction(_("&Import"), self.do_import_labels)
293 labels_menu.addAction(_("&Export"), self.do_export_labels)
295 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
296 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
297 self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
298 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
300 wallet_menu.addAction(_("&Export History"), self.do_export_history)
302 tools_menu = menubar.addMenu(_("&Tools"))
304 # Settings / Preferences are all reserved keywords in OSX using this as work around
305 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
306 tools_menu.addAction(_("&Network"), self.run_network_dialog)
307 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
308 tools_menu.addSeparator()
309 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
310 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
311 tools_menu.addSeparator()
313 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
314 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
315 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
317 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
318 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
319 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
320 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
322 help_menu = menubar.addMenu(_("&Help"))
323 help_menu.addAction(_("&About"), self.show_about)
324 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
325 help_menu.addSeparator()
326 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
327 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
329 self.setMenuBar(menubar)
331 def show_about(self):
332 QMessageBox.about(self, "Electrum",
333 _("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."))
335 def show_report_bug(self):
336 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
337 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
340 def notify_transactions(self):
341 if not self.network or not self.network.is_connected():
344 print_error("Notifying GUI")
345 if len(self.network.pending_transactions_for_notifications) > 0:
346 # Combine the transactions if there are more then three
347 tx_amount = len(self.network.pending_transactions_for_notifications)
350 for tx in self.network.pending_transactions_for_notifications:
351 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
355 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
356 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
358 self.network.pending_transactions_for_notifications = []
360 for tx in self.network.pending_transactions_for_notifications:
362 self.network.pending_transactions_for_notifications.remove(tx)
363 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
365 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
367 def notify(self, message):
368 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
372 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
373 def getOpenFileName(self, title, filter = ""):
374 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
375 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
376 if fileName and directory != os.path.dirname(fileName):
377 self.config.set_key('io_dir', os.path.dirname(fileName), True)
380 def getSaveFileName(self, title, filename, filter = ""):
381 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
382 path = os.path.join( directory, filename )
383 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
384 if fileName and directory != os.path.dirname(fileName):
385 self.config.set_key('io_dir', os.path.dirname(fileName), True)
389 QMainWindow.close(self)
390 run_hook('close_main_window')
392 def connect_slots(self, sender):
393 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
394 self.previous_payto_e=''
396 def timer_actions(self):
397 if self.need_update.is_set():
399 self.need_update.clear()
400 run_hook('timer_actions')
402 def format_amount(self, x, is_diff=False, whitespaces=False):
403 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
405 def read_amount(self, x):
406 if x in['.', '']: return None
407 p = pow(10, self.decimal_point)
408 return int( p * Decimal(x) )
411 assert self.decimal_point in [5,8]
412 return "BTC" if self.decimal_point == 8 else "mBTC"
415 def update_status(self):
416 if self.network is None or not self.network.is_running():
418 icon = QIcon(":icons/status_disconnected.png")
420 elif self.network.is_connected():
421 if not self.wallet.up_to_date:
422 text = _("Synchronizing...")
423 icon = QIcon(":icons/status_waiting.png")
424 elif self.network.server_lag > 1:
425 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
426 icon = QIcon(":icons/status_lagging.png")
428 c, u = self.wallet.get_account_balance(self.current_account)
429 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
430 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
432 # append fiat balance and price from exchange rate plugin
434 run_hook('get_fiat_status_text', c+u, r)
439 self.tray.setToolTip(text)
440 icon = QIcon(":icons/status_connected.png")
442 text = _("Not connected")
443 icon = QIcon(":icons/status_disconnected.png")
445 self.balance_label.setText(text)
446 self.status_button.setIcon( icon )
449 def update_wallet(self):
451 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
452 self.update_history_tab()
453 self.update_receive_tab()
454 self.update_contacts_tab()
455 self.update_completions()
458 def create_history_tab(self):
459 self.history_list = l = MyTreeWidget(self)
461 for i,width in enumerate(self.column_widths['history']):
462 l.setColumnWidth(i, width)
463 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
464 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
465 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
467 l.customContextMenuRequested.connect(self.create_history_menu)
471 def create_history_menu(self, position):
472 self.history_list.selectedIndexes()
473 item = self.history_list.currentItem()
474 be = self.config.get('block_explorer', 'Blockchain.info')
475 if be == 'Blockchain.info':
476 block_explorer = 'https://blockchain.info/tx/'
477 elif be == 'Blockr.io':
478 block_explorer = 'https://blockr.io/tx/info/'
479 elif be == 'Insight.is':
480 block_explorer = 'http://live.insight.is/tx/'
482 tx_hash = str(item.data(0, Qt.UserRole).toString())
483 if not tx_hash: return
485 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
486 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
487 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
488 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
489 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
492 def show_transaction(self, tx):
493 import transaction_dialog
494 d = transaction_dialog.TxDialog(tx, self)
497 def tx_label_clicked(self, item, column):
498 if column==2 and item.isSelected():
500 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
501 self.history_list.editItem( item, column )
502 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
505 def tx_label_changed(self, item, column):
509 tx_hash = str(item.data(0, Qt.UserRole).toString())
510 tx = self.wallet.transactions.get(tx_hash)
511 text = unicode( item.text(2) )
512 self.wallet.set_label(tx_hash, text)
514 item.setForeground(2, QBrush(QColor('black')))
516 text = self.wallet.get_default_label(tx_hash)
517 item.setText(2, text)
518 item.setForeground(2, QBrush(QColor('gray')))
522 def edit_label(self, is_recv):
523 l = self.receive_list if is_recv else self.contacts_list
524 item = l.currentItem()
525 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
526 l.editItem( item, 1 )
527 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
531 def address_label_clicked(self, item, column, l, column_addr, column_label):
532 if column == column_label and item.isSelected():
533 is_editable = item.data(0, 32).toBool()
536 addr = unicode( item.text(column_addr) )
537 label = unicode( item.text(column_label) )
538 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
539 l.editItem( item, column )
540 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
543 def address_label_changed(self, item, column, l, column_addr, column_label):
544 if column == column_label:
545 addr = unicode( item.text(column_addr) )
546 text = unicode( item.text(column_label) )
547 is_editable = item.data(0, 32).toBool()
551 changed = self.wallet.set_label(addr, text)
553 self.update_history_tab()
554 self.update_completions()
556 self.current_item_changed(item)
558 run_hook('item_changed', item, column)
561 def current_item_changed(self, a):
562 run_hook('current_item_changed', a)
566 def update_history_tab(self):
568 self.history_list.clear()
569 for item in self.wallet.get_tx_history(self.current_account):
570 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
571 time_str = _("unknown")
574 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
576 time_str = _("error")
579 time_str = 'unverified'
580 icon = QIcon(":icons/unconfirmed.png")
583 icon = QIcon(":icons/unconfirmed.png")
585 icon = QIcon(":icons/clock%d.png"%conf)
587 icon = QIcon(":icons/confirmed.png")
589 if value is not None:
590 v_str = self.format_amount(value, True, whitespaces=True)
594 balance_str = self.format_amount(balance, whitespaces=True)
597 label, is_default_label = self.wallet.get_label(tx_hash)
599 label = _('Pruned transaction outputs')
600 is_default_label = False
602 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
603 item.setFont(2, QFont(MONOSPACE_FONT))
604 item.setFont(3, QFont(MONOSPACE_FONT))
605 item.setFont(4, QFont(MONOSPACE_FONT))
607 item.setForeground(3, QBrush(QColor("#BC1E1E")))
609 item.setData(0, Qt.UserRole, tx_hash)
610 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
612 item.setForeground(2, QBrush(QColor('grey')))
614 item.setIcon(0, icon)
615 self.history_list.insertTopLevelItem(0,item)
618 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
619 run_hook('history_tab_update')
622 def create_send_tab(self):
627 grid.setColumnMinimumWidth(3,300)
628 grid.setColumnStretch(5,1)
631 self.payto_e = QLineEdit()
632 grid.addWidget(QLabel(_('Pay to')), 1, 0)
633 grid.addWidget(self.payto_e, 1, 1, 1, 3)
635 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)
637 completer = QCompleter()
638 completer.setCaseSensitivity(False)
639 self.payto_e.setCompleter(completer)
640 completer.setModel(self.completions)
642 self.message_e = QLineEdit()
643 grid.addWidget(QLabel(_('Description')), 2, 0)
644 grid.addWidget(self.message_e, 2, 1, 1, 3)
645 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)
647 self.from_label = QLabel(_('From'))
648 grid.addWidget(self.from_label, 3, 0)
649 self.from_list = QTreeWidget(self)
650 self.from_list.setColumnCount(2)
651 self.from_list.setColumnWidth(0, 350)
652 self.from_list.setColumnWidth(1, 50)
653 self.from_list.setHeaderHidden (True)
654 self.from_list.setMaximumHeight(80)
655 grid.addWidget(self.from_list, 3, 1, 1, 3)
656 self.set_pay_from([])
658 self.amount_e = AmountEdit(self.base_unit)
659 grid.addWidget(QLabel(_('Amount')), 4, 0)
660 grid.addWidget(self.amount_e, 4, 1, 1, 2)
661 grid.addWidget(HelpButton(
662 _('Amount to be sent.') + '\n\n' \
663 + _('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.') \
664 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
666 self.fee_e = AmountEdit(self.base_unit)
667 grid.addWidget(QLabel(_('Fee')), 5, 0)
668 grid.addWidget(self.fee_e, 5, 1, 1, 2)
669 grid.addWidget(HelpButton(
670 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
671 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
672 + _('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)
674 run_hook('exchange_rate_button', grid)
676 self.send_button = EnterButton(_("Send"), self.do_send)
677 grid.addWidget(self.send_button, 6, 1)
679 b = EnterButton(_("Clear"),self.do_clear)
680 grid.addWidget(b, 6, 2)
682 self.payto_sig = QLabel('')
683 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
685 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
686 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
695 def entry_changed( is_fee ):
696 self.funds_error = False
698 if self.amount_e.is_shortcut:
699 self.amount_e.is_shortcut = False
700 sendable = self.get_sendable_balance()
701 # there is only one output because we are completely spending inputs
702 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
703 fee = self.wallet.estimated_fee(inputs, 1)
705 self.amount_e.setText( self.format_amount(amount) )
706 self.fee_e.setText( self.format_amount( fee ) )
709 amount = self.read_amount(str(self.amount_e.text()))
710 fee = self.read_amount(str(self.fee_e.text()))
712 if not is_fee: fee = None
715 # assume that there will be 2 outputs (one for change)
716 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
718 self.fee_e.setText( self.format_amount( fee ) )
721 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
725 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
726 self.funds_error = True
727 text = _( "Not enough funds" )
728 c, u = self.wallet.get_frozen_balance()
729 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
731 self.statusBar().showMessage(text)
732 self.amount_e.setPalette(palette)
733 self.fee_e.setPalette(palette)
735 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
736 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
738 run_hook('create_send_tab', grid)
742 def set_pay_from(self, l):
744 self.from_list.clear()
745 self.from_label.setHidden(len(self.pay_from) == 0)
746 self.from_list.setHidden(len(self.pay_from) == 0)
747 for addr in self.pay_from:
748 c, u = self.wallet.get_addr_balance(addr)
749 balance = self.format_amount(c + u)
750 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
753 def update_completions(self):
755 for addr,label in self.wallet.labels.items():
756 if addr in self.wallet.addressbook:
757 l.append( label + ' <' + addr + '>')
759 run_hook('update_completions', l)
760 self.completions.setStringList(l)
764 return lambda s, *args: s.do_protect(func, args)
769 label = unicode( self.message_e.text() )
770 r = unicode( self.payto_e.text() )
773 # label or alias, with address in brackets
774 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
775 to_address = m.group(2) if m else r
777 if not is_valid(to_address):
778 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
782 amount = self.read_amount(unicode( self.amount_e.text()))
784 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
787 fee = self.read_amount(unicode( self.fee_e.text()))
789 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
792 confirm_amount = self.config.get('confirm_amount', 100000000)
793 if amount >= confirm_amount:
794 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
797 confirm_fee = self.config.get('confirm_fee', 100000)
798 if fee >= confirm_fee:
799 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()}):
802 self.send_tx(to_address, amount, fee, label)
805 def waiting_dialog(self, message):
807 d.setWindowTitle('Please wait')
809 vbox = QVBoxLayout(d)
816 def send_tx(self, to_address, amount, fee, label, password):
818 # first, create an unsigned tx
819 domain = self.get_payment_sources()
820 outputs = [(to_address, amount)]
822 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
824 except Exception as e:
825 traceback.print_exc(file=sys.stdout)
826 self.show_message(str(e))
829 # call hook to see if plugin needs gui interaction
830 run_hook('send_tx', tx)
836 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
837 self.wallet.sign_transaction(tx, keypairs, password)
838 self.signed_tx_data = (tx, fee, label)
839 self.emit(SIGNAL('send_tx2'))
840 self.tx_wait_dialog = self.waiting_dialog('Signing..')
841 threading.Thread(target=sign_thread).start()
843 # add recipient to addressbook
844 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
845 self.wallet.addressbook.append(to_address)
849 tx, fee, label = self.signed_tx_data
850 self.tx_wait_dialog.accept()
853 self.show_message(tx.error)
856 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
857 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
861 self.wallet.set_label(tx.hash(), label)
863 if not tx.is_complete() or self.config.get('show_before_broadcast'):
864 self.show_transaction(tx)
868 def broadcast_thread():
869 self.tx_broadcast_result = self.wallet.sendtx(tx)
870 self.emit(SIGNAL('send_tx3'))
871 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
872 threading.Thread(target=broadcast_thread).start()
876 self.tx_broadcast_dialog.accept()
877 status, msg = self.tx_broadcast_result
879 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
881 self.update_contacts_tab()
883 QMessageBox.warning(self, _('Error'), msg, _('OK'))
891 def set_send(self, address, amount, label, message):
893 if label and self.wallet.labels.get(address) != label:
894 if self.question('Give label "%s" to address %s ?'%(label,address)):
895 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
896 self.wallet.addressbook.append(address)
897 self.wallet.set_label(address, label)
899 self.tabs.setCurrentIndex(1)
900 label = self.wallet.labels.get(address)
901 m_addr = label + ' <'+ address +'>' if label else address
902 self.payto_e.setText(m_addr)
904 self.message_e.setText(message)
906 self.amount_e.setText(amount)
910 self.payto_sig.setVisible(False)
911 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
913 self.set_frozen(e,False)
915 self.set_pay_from([])
918 def set_frozen(self,entry,frozen):
920 entry.setReadOnly(True)
921 entry.setFrame(False)
923 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
924 entry.setPalette(palette)
926 entry.setReadOnly(False)
929 palette.setColor(entry.backgroundRole(), QColor('white'))
930 entry.setPalette(palette)
933 def set_addrs_frozen(self,addrs,freeze):
935 if not addr: continue
936 if addr in self.wallet.frozen_addresses and not freeze:
937 self.wallet.unfreeze(addr)
938 elif addr not in self.wallet.frozen_addresses and freeze:
939 self.wallet.freeze(addr)
940 self.update_receive_tab()
944 def create_list_tab(self, headers):
945 "generic tab creation method"
946 l = MyTreeWidget(self)
947 l.setColumnCount( len(headers) )
948 l.setHeaderLabels( headers )
958 vbox.addWidget(buttons)
963 buttons.setLayout(hbox)
968 def create_receive_tab(self):
969 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
970 l.setContextMenuPolicy(Qt.CustomContextMenu)
971 l.customContextMenuRequested.connect(self.create_receive_menu)
972 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
973 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
974 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
975 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
976 self.receive_list = l
977 self.receive_buttons_hbox = hbox
984 def save_column_widths(self):
985 self.column_widths["receive"] = []
986 for i in range(self.receive_list.columnCount() -1):
987 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
989 self.column_widths["history"] = []
990 for i in range(self.history_list.columnCount() - 1):
991 self.column_widths["history"].append(self.history_list.columnWidth(i))
993 self.column_widths["contacts"] = []
994 for i in range(self.contacts_list.columnCount() - 1):
995 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
997 self.config.set_key("column_widths_2", self.column_widths, True)
1000 def create_contacts_tab(self):
1001 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1002 l.setContextMenuPolicy(Qt.CustomContextMenu)
1003 l.customContextMenuRequested.connect(self.create_contact_menu)
1004 for i,width in enumerate(self.column_widths['contacts']):
1005 l.setColumnWidth(i, width)
1007 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1008 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1009 self.contacts_list = l
1010 self.contacts_buttons_hbox = hbox
1015 def delete_imported_key(self, addr):
1016 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1017 self.wallet.delete_imported_key(addr)
1018 self.update_receive_tab()
1019 self.update_history_tab()
1021 def edit_account_label(self, k):
1022 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1024 label = unicode(text)
1025 self.wallet.set_label(k,label)
1026 self.update_receive_tab()
1028 def account_set_expanded(self, item, k, b):
1030 self.accounts_expanded[k] = b
1032 def create_account_menu(self, position, k, item):
1034 if item.isExpanded():
1035 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1037 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1038 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1039 if self.wallet.seed_version > 4:
1040 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1041 if self.wallet.account_is_pending(k):
1042 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1043 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1045 def delete_pending_account(self, k):
1046 self.wallet.delete_pending_account(k)
1047 self.update_receive_tab()
1049 def create_receive_menu(self, position):
1050 # fixme: this function apparently has a side effect.
1051 # if it is not called the menu pops up several times
1052 #self.receive_list.selectedIndexes()
1054 selected = self.receive_list.selectedItems()
1055 multi_select = len(selected) > 1
1056 addrs = [unicode(item.text(0)) for item in selected]
1057 if not multi_select:
1058 item = self.receive_list.itemAt(position)
1062 if not is_valid(addr):
1063 k = str(item.data(0,32).toString())
1065 self.create_account_menu(position, k, item)
1067 item.setExpanded(not item.isExpanded())
1071 if not multi_select:
1072 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1073 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1074 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1075 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1076 if not self.wallet.is_watching_only():
1077 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1078 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1079 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1080 if self.wallet.is_imported(addr):
1081 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1083 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1084 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1085 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1086 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1088 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1089 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1091 run_hook('receive_menu', menu, addrs)
1092 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1095 def get_sendable_balance(self):
1096 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1099 def get_payment_sources(self):
1101 return self.pay_from
1103 return self.wallet.get_account_addresses(self.current_account)
1106 def send_from_addresses(self, addrs):
1107 self.set_pay_from( addrs )
1108 self.tabs.setCurrentIndex(1)
1111 def payto(self, addr):
1113 label = self.wallet.labels.get(addr)
1114 m_addr = label + ' <' + addr + '>' if label else addr
1115 self.tabs.setCurrentIndex(1)
1116 self.payto_e.setText(m_addr)
1117 self.amount_e.setFocus()
1120 def delete_contact(self, x):
1121 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1122 self.wallet.delete_contact(x)
1123 self.wallet.set_label(x, None)
1124 self.update_history_tab()
1125 self.update_contacts_tab()
1126 self.update_completions()
1129 def create_contact_menu(self, position):
1130 item = self.contacts_list.itemAt(position)
1133 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1135 addr = unicode(item.text(0))
1136 label = unicode(item.text(1))
1137 is_editable = item.data(0,32).toBool()
1138 payto_addr = item.data(0,33).toString()
1139 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1140 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1141 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1143 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1144 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1146 run_hook('create_contact_menu', menu, item)
1147 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1150 def update_receive_item(self, item):
1151 item.setFont(0, QFont(MONOSPACE_FONT))
1152 address = str(item.data(0,0).toString())
1153 label = self.wallet.labels.get(address,'')
1154 item.setData(1,0,label)
1155 item.setData(0,32, True) # is editable
1157 run_hook('update_receive_item', address, item)
1159 if not self.wallet.is_mine(address): return
1161 c, u = self.wallet.get_addr_balance(address)
1162 balance = self.format_amount(c + u)
1163 item.setData(2,0,balance)
1165 if address in self.wallet.frozen_addresses:
1166 item.setBackgroundColor(0, QColor('lightblue'))
1169 def update_receive_tab(self):
1170 l = self.receive_list
1171 # extend the syntax for consistency
1172 l.addChild = l.addTopLevelItem
1175 for i,width in enumerate(self.column_widths['receive']):
1176 l.setColumnWidth(i, width)
1178 accounts = self.wallet.get_accounts()
1179 if self.current_account is None:
1180 account_items = sorted(accounts.items())
1182 account_items = [(self.current_account, accounts.get(self.current_account))]
1185 for k, account in account_items:
1187 if len(accounts) > 1:
1188 name = self.wallet.get_account_name(k)
1189 c,u = self.wallet.get_account_balance(k)
1190 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1191 l.addTopLevelItem(account_item)
1192 account_item.setExpanded(self.accounts_expanded.get(k, True))
1193 account_item.setData(0, 32, k)
1197 sequences = [0,1] if account.has_change() else [0]
1198 for is_change in sequences:
1199 if len(sequences) > 1:
1200 name = _("Receiving") if not is_change else _("Change")
1201 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1202 account_item.addChild(seq_item)
1204 seq_item.setExpanded(True)
1206 seq_item = account_item
1208 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1214 for address in account.get_addresses(is_change):
1215 h = self.wallet.history.get(address,[])
1219 if gap > self.wallet.gap_limit:
1224 c, u = self.wallet.get_addr_balance(address)
1225 num_tx = '*' if h == ['*'] else "%d"%len(h)
1227 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1228 self.update_receive_item(item)
1230 item.setBackgroundColor(1, QColor('red'))
1231 if len(h) > 0 and c == -u:
1233 seq_item.insertChild(0,used_item)
1235 used_item.addChild(item)
1237 seq_item.addChild(item)
1239 # we use column 1 because column 0 may be hidden
1240 l.setCurrentItem(l.topLevelItem(0),1)
1243 def update_contacts_tab(self):
1244 l = self.contacts_list
1247 for address in self.wallet.addressbook:
1248 label = self.wallet.labels.get(address,'')
1249 n = self.wallet.get_num_tx(address)
1250 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1251 item.setFont(0, QFont(MONOSPACE_FONT))
1252 # 32 = label can be edited (bool)
1253 item.setData(0,32, True)
1255 item.setData(0,33, address)
1256 l.addTopLevelItem(item)
1258 run_hook('update_contacts_tab', l)
1259 l.setCurrentItem(l.topLevelItem(0))
1263 def create_console_tab(self):
1264 from console import Console
1265 self.console = console = Console()
1269 def update_console(self):
1270 console = self.console
1271 console.history = self.config.get("console-history",[])
1272 console.history_index = len(console.history)
1274 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1275 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1277 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1279 def mkfunc(f, method):
1280 return lambda *args: apply( f, (method, args, self.password_dialog ))
1282 if m[0]=='_' or m in ['network','wallet']: continue
1283 methods[m] = mkfunc(c._run, m)
1285 console.updateNamespace(methods)
1288 def change_account(self,s):
1289 if s == _("All accounts"):
1290 self.current_account = None
1292 accounts = self.wallet.get_account_names()
1293 for k, v in accounts.items():
1295 self.current_account = k
1296 self.update_history_tab()
1297 self.update_status()
1298 self.update_receive_tab()
1300 def create_status_bar(self):
1303 sb.setFixedHeight(35)
1304 qtVersion = qVersion()
1306 self.balance_label = QLabel("")
1307 sb.addWidget(self.balance_label)
1309 from version_getter import UpdateLabel
1310 self.updatelabel = UpdateLabel(self.config, sb)
1312 self.account_selector = QComboBox()
1313 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1314 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1315 sb.addPermanentWidget(self.account_selector)
1317 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1318 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1320 self.lock_icon = QIcon()
1321 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1322 sb.addPermanentWidget( self.password_button )
1324 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1325 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1326 sb.addPermanentWidget( self.seed_button )
1327 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1328 sb.addPermanentWidget( self.status_button )
1330 run_hook('create_status_bar', (sb,))
1332 self.setStatusBar(sb)
1335 def update_lock_icon(self):
1336 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1337 self.password_button.setIcon( icon )
1340 def update_buttons_on_seed(self):
1341 if self.wallet.has_seed():
1342 self.seed_button.show()
1344 self.seed_button.hide()
1346 if not self.wallet.is_watching_only():
1347 self.password_button.show()
1348 self.send_button.setText(_("Send"))
1350 self.password_button.hide()
1351 self.send_button.setText(_("Create unsigned transaction"))
1354 def change_password_dialog(self):
1355 from password_dialog import PasswordDialog
1356 d = PasswordDialog(self.wallet, self)
1358 self.update_lock_icon()
1361 def new_contact_dialog(self):
1364 d.setWindowTitle(_("New Contact"))
1365 vbox = QVBoxLayout(d)
1366 vbox.addWidget(QLabel(_('New Contact')+':'))
1368 grid = QGridLayout()
1371 grid.addWidget(QLabel(_("Address")), 1, 0)
1372 grid.addWidget(line1, 1, 1)
1373 grid.addWidget(QLabel(_("Name")), 2, 0)
1374 grid.addWidget(line2, 2, 1)
1376 vbox.addLayout(grid)
1377 vbox.addLayout(ok_cancel_buttons(d))
1382 address = str(line1.text())
1383 label = unicode(line2.text())
1385 if not is_valid(address):
1386 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1389 self.wallet.add_contact(address)
1391 self.wallet.set_label(address, label)
1393 self.update_contacts_tab()
1394 self.update_history_tab()
1395 self.update_completions()
1396 self.tabs.setCurrentIndex(3)
1400 def new_account_dialog(self, password):
1402 dialog = QDialog(self)
1404 dialog.setWindowTitle(_("New Account"))
1406 vbox = QVBoxLayout()
1407 vbox.addWidget(QLabel(_('Account name')+':'))
1410 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1411 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1416 vbox.addLayout(ok_cancel_buttons(dialog))
1417 dialog.setLayout(vbox)
1421 name = str(e.text())
1424 self.wallet.create_pending_account(name, password)
1425 self.update_receive_tab()
1426 self.tabs.setCurrentIndex(2)
1431 def show_master_public_keys(self):
1433 dialog = QDialog(self)
1435 dialog.setWindowTitle(_("Master Public Keys"))
1437 main_layout = QGridLayout()
1438 mpk_dict = self.wallet.get_master_public_keys()
1440 for key, value in mpk_dict.items():
1441 main_layout.addWidget(QLabel(key), i, 0)
1442 mpk_text = QTextEdit()
1443 mpk_text.setReadOnly(True)
1444 mpk_text.setMaximumHeight(170)
1445 mpk_text.setText(value)
1446 main_layout.addWidget(mpk_text, i + 1, 0)
1449 vbox = QVBoxLayout()
1450 vbox.addLayout(main_layout)
1451 vbox.addLayout(close_button(dialog))
1453 dialog.setLayout(vbox)
1458 def show_seed_dialog(self, password):
1459 if not self.wallet.has_seed():
1460 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1464 mnemonic = self.wallet.get_mnemonic(password)
1466 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1468 from seed_dialog import SeedDialog
1469 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1474 def show_qrcode(self, data, title = _("QR code")):
1478 d.setWindowTitle(title)
1479 d.setMinimumSize(270, 300)
1480 vbox = QVBoxLayout()
1481 qrw = QRCodeWidget(data)
1482 vbox.addWidget(qrw, 1)
1483 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1484 hbox = QHBoxLayout()
1487 filename = os.path.join(self.config.path, "qrcode.bmp")
1490 bmp.save_qrcode(qrw.qr, filename)
1491 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1493 def copy_to_clipboard():
1494 bmp.save_qrcode(qrw.qr, filename)
1495 self.app.clipboard().setImage(QImage(filename))
1496 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1498 b = QPushButton(_("Copy"))
1500 b.clicked.connect(copy_to_clipboard)
1502 b = QPushButton(_("Save"))
1504 b.clicked.connect(print_qr)
1506 b = QPushButton(_("Close"))
1508 b.clicked.connect(d.accept)
1511 vbox.addLayout(hbox)
1516 def do_protect(self, func, args):
1517 if self.wallet.use_encryption:
1518 password = self.password_dialog()
1524 if args != (False,):
1525 args = (self,) + args + (password,)
1527 args = (self,password)
1531 def show_public_keys(self, address):
1532 if not address: return
1534 pubkey_list = self.wallet.get_public_keys(address)
1535 except Exception as e:
1536 traceback.print_exc(file=sys.stdout)
1537 self.show_message(str(e))
1541 d.setMinimumSize(600, 200)
1543 vbox = QVBoxLayout()
1544 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1545 vbox.addWidget( QLabel(_("Public key") + ':'))
1547 keys.setReadOnly(True)
1548 keys.setText('\n'.join(pubkey_list))
1549 vbox.addWidget(keys)
1550 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1551 vbox.addLayout(close_button(d))
1556 def show_private_key(self, address, password):
1557 if not address: return
1559 pk_list = self.wallet.get_private_key(address, password)
1560 except Exception as e:
1561 traceback.print_exc(file=sys.stdout)
1562 self.show_message(str(e))
1566 d.setMinimumSize(600, 200)
1568 vbox = QVBoxLayout()
1569 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1570 vbox.addWidget( QLabel(_("Private key") + ':'))
1572 keys.setReadOnly(True)
1573 keys.setText('\n'.join(pk_list))
1574 vbox.addWidget(keys)
1575 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1576 vbox.addLayout(close_button(d))
1582 def do_sign(self, address, message, signature, password):
1583 message = unicode(message.toPlainText())
1584 message = message.encode('utf-8')
1586 sig = self.wallet.sign_message(str(address.text()), message, password)
1587 signature.setText(sig)
1588 except Exception as e:
1589 self.show_message(str(e))
1591 def do_verify(self, address, message, signature):
1592 message = unicode(message.toPlainText())
1593 message = message.encode('utf-8')
1594 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1595 self.show_message(_("Signature verified"))
1597 self.show_message(_("Error: wrong signature"))
1600 def sign_verify_message(self, address=''):
1603 d.setWindowTitle(_('Sign/verify Message'))
1604 d.setMinimumSize(410, 290)
1606 layout = QGridLayout(d)
1608 message_e = QTextEdit()
1609 layout.addWidget(QLabel(_('Message')), 1, 0)
1610 layout.addWidget(message_e, 1, 1)
1611 layout.setRowStretch(2,3)
1613 address_e = QLineEdit()
1614 address_e.setText(address)
1615 layout.addWidget(QLabel(_('Address')), 2, 0)
1616 layout.addWidget(address_e, 2, 1)
1618 signature_e = QTextEdit()
1619 layout.addWidget(QLabel(_('Signature')), 3, 0)
1620 layout.addWidget(signature_e, 3, 1)
1621 layout.setRowStretch(3,1)
1623 hbox = QHBoxLayout()
1625 b = QPushButton(_("Sign"))
1626 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1629 b = QPushButton(_("Verify"))
1630 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1633 b = QPushButton(_("Close"))
1634 b.clicked.connect(d.accept)
1636 layout.addLayout(hbox, 4, 1)
1641 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1643 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1644 message_e.setText(decrypted)
1645 except Exception as e:
1646 self.show_message(str(e))
1649 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1650 message = unicode(message_e.toPlainText())
1651 message = message.encode('utf-8')
1653 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1654 encrypted_e.setText(encrypted)
1655 except Exception as e:
1656 self.show_message(str(e))
1660 def encrypt_message(self, address = ''):
1663 d.setWindowTitle(_('Encrypt/decrypt Message'))
1664 d.setMinimumSize(610, 490)
1666 layout = QGridLayout(d)
1668 message_e = QTextEdit()
1669 layout.addWidget(QLabel(_('Message')), 1, 0)
1670 layout.addWidget(message_e, 1, 1)
1671 layout.setRowStretch(2,3)
1673 pubkey_e = QLineEdit()
1675 pubkey = self.wallet.getpubkeys(address)[0]
1676 pubkey_e.setText(pubkey)
1677 layout.addWidget(QLabel(_('Public key')), 2, 0)
1678 layout.addWidget(pubkey_e, 2, 1)
1680 encrypted_e = QTextEdit()
1681 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1682 layout.addWidget(encrypted_e, 3, 1)
1683 layout.setRowStretch(3,1)
1685 hbox = QHBoxLayout()
1686 b = QPushButton(_("Encrypt"))
1687 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1690 b = QPushButton(_("Decrypt"))
1691 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1694 b = QPushButton(_("Close"))
1695 b.clicked.connect(d.accept)
1698 layout.addLayout(hbox, 4, 1)
1702 def question(self, msg):
1703 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1705 def show_message(self, msg):
1706 QMessageBox.information(self, _('Message'), msg, _('OK'))
1708 def password_dialog(self ):
1711 d.setWindowTitle(_("Enter Password"))
1716 vbox = QVBoxLayout()
1717 msg = _('Please enter your password')
1718 vbox.addWidget(QLabel(msg))
1720 grid = QGridLayout()
1722 grid.addWidget(QLabel(_('Password')), 1, 0)
1723 grid.addWidget(pw, 1, 1)
1724 vbox.addLayout(grid)
1726 vbox.addLayout(ok_cancel_buttons(d))
1729 run_hook('password_dialog', pw, grid, 1)
1730 if not d.exec_(): return
1731 return unicode(pw.text())
1740 def tx_from_text(self, txt):
1741 "json or raw hexadecimal"
1744 tx = Transaction(txt)
1750 tx_dict = json.loads(str(txt))
1751 assert "hex" in tx_dict.keys()
1752 assert "complete" in tx_dict.keys()
1753 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1754 if not tx_dict["complete"]:
1755 assert "input_info" in tx_dict.keys()
1756 input_info = json.loads(tx_dict['input_info'])
1757 tx.add_input_info(input_info)
1762 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1766 def read_tx_from_file(self):
1767 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1771 with open(fileName, "r") as f:
1772 file_content = f.read()
1773 except (ValueError, IOError, os.error), reason:
1774 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1776 return self.tx_from_text(file_content)
1780 def sign_raw_transaction(self, tx, input_info, password):
1781 self.wallet.signrawtransaction(tx, input_info, [], password)
1783 def do_process_from_text(self):
1784 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1787 tx = self.tx_from_text(text)
1789 self.show_transaction(tx)
1791 def do_process_from_file(self):
1792 tx = self.read_tx_from_file()
1794 self.show_transaction(tx)
1796 def do_process_from_txid(self):
1797 from electrum import transaction
1798 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1800 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1802 tx = transaction.Transaction(r)
1804 self.show_transaction(tx)
1806 self.show_message("unknown transaction")
1808 def do_process_from_csvReader(self, csvReader):
1813 for position, row in enumerate(csvReader):
1815 if not is_valid(address):
1816 errors.append((position, address))
1818 amount = Decimal(row[1])
1819 amount = int(100000000*amount)
1820 outputs.append((address, amount))
1821 except (ValueError, IOError, os.error), reason:
1822 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1826 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1827 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1831 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1832 except Exception as e:
1833 self.show_message(str(e))
1836 self.show_transaction(tx)
1838 def do_process_from_csv_file(self):
1839 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1843 with open(fileName, "r") as f:
1844 csvReader = csv.reader(f)
1845 self.do_process_from_csvReader(csvReader)
1846 except (ValueError, IOError, os.error), reason:
1847 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1850 def do_process_from_csv_text(self):
1851 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1852 + _("Format: address, amount. One output per line"), _("Load CSV"))
1855 f = StringIO.StringIO(text)
1856 csvReader = csv.reader(f)
1857 self.do_process_from_csvReader(csvReader)
1862 def export_privkeys_dialog(self, password):
1863 if self.wallet.is_watching_only():
1864 self.show_message(_("This is a watching-only wallet"))
1868 d.setWindowTitle(_('Private keys'))
1869 d.setMinimumSize(850, 300)
1870 vbox = QVBoxLayout(d)
1872 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1873 _("Exposing a single private key can compromise your entire wallet!"),
1874 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1875 vbox.addWidget(QLabel(msg))
1881 hbox = QHBoxLayout()
1882 vbox.addLayout(hbox)
1884 defaultname = 'electrum-private-keys.csv'
1885 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
1886 path = os.path.join( directory, defaultname )
1887 filename_e = QLineEdit()
1888 filename_e.setText(path)
1890 select_export = _('Select file to export your private keys to')
1891 p = self.getSaveFileName(select_export, defaultname, "*.csv")
1893 filename_e.setText(p)
1895 button = QPushButton(_('File'))
1896 button.clicked.connect(func)
1897 hbox.addWidget(button)
1898 hbox.addWidget(filename_e)
1900 h, b = ok_cancel_buttons2(d, _('Export'))
1905 addresses = self.wallet.addresses(True)
1906 def privkeys_thread():
1908 for addr in addresses:
1909 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1910 d.emit(SIGNAL('computing_privkeys'))
1911 d.emit(SIGNAL('show_privkeys'))
1913 def show_privkeys():
1914 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1918 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1919 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1920 threading.Thread(target=privkeys_thread).start()
1925 filename = filename_e.text()
1928 self.do_export_privkeys(filename, private_keys)
1931 def do_export_privkeys(self, fileName, pklist):
1933 with open(fileName, "w+") as csvfile:
1934 transaction = csv.writer(csvfile)
1935 transaction.writerow(["address", "private_key"])
1936 for addr, pk in pklist.items():
1937 transaction.writerow(["%34s"%addr,pk])
1938 except (IOError, os.error), reason:
1939 export_error_label = _("Electrum was unable to produce a private key-export.")
1940 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1942 except Exception as e:
1943 self.show_message(str(e))
1946 self.show_message(_("Private keys exported."))
1949 def do_import_labels(self):
1950 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1951 if not labelsFile: return
1953 f = open(labelsFile, 'r')
1956 for key, value in json.loads(data).items():
1957 self.wallet.set_label(key, value)
1958 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1959 except (IOError, os.error), reason:
1960 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1963 def do_export_labels(self):
1964 labels = self.wallet.labels
1966 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1968 with open(fileName, 'w+') as f:
1969 json.dump(labels, f)
1970 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1971 except (IOError, os.error), reason:
1972 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1975 def do_export_history(self):
1976 wallet = self.wallet
1977 select_export = _('Select file to export your wallet transactions to')
1978 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
1983 with open(fileName, "w+") as csvfile:
1984 transaction = csv.writer(csvfile)
1985 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
1986 for item in wallet.get_tx_history():
1987 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
1989 if timestamp is not None:
1991 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
1992 except [RuntimeError, TypeError, NameError] as reason:
1993 time_string = "unknown"
1996 time_string = "unknown"
1998 time_string = "pending"
2000 if value is not None:
2001 value_string = format_satoshis(value, True)
2006 fee_string = format_satoshis(fee, True)
2011 label, is_default_label = wallet.get_label(tx_hash)
2012 label = label.encode('utf-8')
2016 balance_string = format_satoshis(balance, False)
2017 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2018 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
2020 except (IOError, os.error), reason:
2021 export_error_label = _("Electrum was unable to produce a transaction export.")
2022 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
2025 def sweep_key_dialog(self):
2027 d.setWindowTitle(_('Sweep private keys'))
2029 vbox = QVBoxLayout(d)
2030 vbox.addWidget(QLabel(_("Enter private keys")))
2032 keys_e = QTextEdit()
2033 keys_e.setTabChangesFocus(True)
2034 vbox.addWidget(keys_e)
2036 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2037 vbox.addLayout(hbox)
2038 button.setEnabled(False)
2040 keys_e.textChanged.connect(lambda: button.setEnabled(Wallet.is_private_key(str(keys_e.toPlainText()).strip())))
2044 text = str(keys_e.toPlainText()).strip()
2045 privkeys = text.split()
2046 to_address = self.wallet.addresses()[0]
2047 fee = self.wallet.fee
2048 tx = Transaction.sweep(privkeys, self.network, to_address, fee)
2049 self.show_transaction(tx)
2053 def do_import_privkey(self, password):
2054 if not self.wallet.imported_keys:
2055 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2056 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2057 + _('Are you sure you understand what you are doing?'), 3, 4)
2060 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2063 text = str(text).split()
2068 addr = self.wallet.import_key(key, password)
2069 except Exception as e:
2075 addrlist.append(addr)
2077 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2079 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2080 self.update_receive_tab()
2081 self.update_history_tab()
2084 def settings_dialog(self):
2086 d.setWindowTitle(_('Electrum Settings'))
2088 vbox = QVBoxLayout()
2089 grid = QGridLayout()
2090 grid.setColumnStretch(0,1)
2092 nz_label = QLabel(_('Display zeros') + ':')
2093 grid.addWidget(nz_label, 0, 0)
2094 nz_e = AmountEdit(None,True)
2095 nz_e.setText("%d"% self.num_zeros)
2096 grid.addWidget(nz_e, 0, 1)
2097 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2098 grid.addWidget(HelpButton(msg), 0, 2)
2099 if not self.config.is_modifiable('num_zeros'):
2100 for w in [nz_e, nz_label]: w.setEnabled(False)
2102 lang_label=QLabel(_('Language') + ':')
2103 grid.addWidget(lang_label, 1, 0)
2104 lang_combo = QComboBox()
2105 from electrum.i18n import languages
2106 lang_combo.addItems(languages.values())
2108 index = languages.keys().index(self.config.get("language",''))
2111 lang_combo.setCurrentIndex(index)
2112 grid.addWidget(lang_combo, 1, 1)
2113 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2114 if not self.config.is_modifiable('language'):
2115 for w in [lang_combo, lang_label]: w.setEnabled(False)
2118 fee_label = QLabel(_('Transaction fee') + ':')
2119 grid.addWidget(fee_label, 2, 0)
2120 fee_e = AmountEdit(self.base_unit)
2121 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2122 grid.addWidget(fee_e, 2, 1)
2123 msg = _('Fee per kilobyte of transaction.') + ' ' \
2124 + _('Recommended value') + ': ' + self.format_amount(20000)
2125 grid.addWidget(HelpButton(msg), 2, 2)
2126 if not self.config.is_modifiable('fee_per_kb'):
2127 for w in [fee_e, fee_label]: w.setEnabled(False)
2129 units = ['BTC', 'mBTC']
2130 unit_label = QLabel(_('Base unit') + ':')
2131 grid.addWidget(unit_label, 3, 0)
2132 unit_combo = QComboBox()
2133 unit_combo.addItems(units)
2134 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2135 grid.addWidget(unit_combo, 3, 1)
2136 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2137 + '\n1BTC=1000mBTC.\n' \
2138 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2140 usechange_cb = QCheckBox(_('Use change addresses'))
2141 usechange_cb.setChecked(self.wallet.use_change)
2142 grid.addWidget(usechange_cb, 4, 0)
2143 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2144 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2146 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2147 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2148 grid.addWidget(block_ex_label, 5, 0)
2149 block_ex_combo = QComboBox()
2150 block_ex_combo.addItems(block_explorers)
2151 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2152 grid.addWidget(block_ex_combo, 5, 1)
2153 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2155 show_tx = self.config.get('show_before_broadcast', False)
2156 showtx_cb = QCheckBox(_('Show before broadcast'))
2157 showtx_cb.setChecked(show_tx)
2158 grid.addWidget(showtx_cb, 6, 0)
2159 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2161 vbox.addLayout(grid)
2163 vbox.addLayout(ok_cancel_buttons(d))
2167 if not d.exec_(): return
2169 fee = unicode(fee_e.text())
2171 fee = self.read_amount(fee)
2173 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2176 self.wallet.set_fee(fee)
2178 nz = unicode(nz_e.text())
2183 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2186 if self.num_zeros != nz:
2188 self.config.set_key('num_zeros', nz, True)
2189 self.update_history_tab()
2190 self.update_receive_tab()
2192 usechange_result = usechange_cb.isChecked()
2193 if self.wallet.use_change != usechange_result:
2194 self.wallet.use_change = usechange_result
2195 self.wallet.storage.put('use_change', self.wallet.use_change)
2197 if showtx_cb.isChecked() != show_tx:
2198 self.config.set_key('show_before_broadcast', not show_tx)
2200 unit_result = units[unit_combo.currentIndex()]
2201 if self.base_unit() != unit_result:
2202 self.decimal_point = 8 if unit_result == 'BTC' else 5
2203 self.config.set_key('decimal_point', self.decimal_point, True)
2204 self.update_history_tab()
2205 self.update_status()
2207 need_restart = False
2209 lang_request = languages.keys()[lang_combo.currentIndex()]
2210 if lang_request != self.config.get('language'):
2211 self.config.set_key("language", lang_request, True)
2214 be_result = block_explorers[block_ex_combo.currentIndex()]
2215 self.config.set_key('block_explorer', be_result, True)
2217 run_hook('close_settings_dialog')
2220 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2223 def run_network_dialog(self):
2224 if not self.network:
2226 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2228 def closeEvent(self, event):
2231 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2232 self.save_column_widths()
2233 self.config.set_key("console-history", self.console.history[-50:], True)
2234 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2238 def plugins_dialog(self):
2239 from electrum.plugins import plugins
2242 d.setWindowTitle(_('Electrum Plugins'))
2245 vbox = QVBoxLayout(d)
2248 scroll = QScrollArea()
2249 scroll.setEnabled(True)
2250 scroll.setWidgetResizable(True)
2251 scroll.setMinimumSize(400,250)
2252 vbox.addWidget(scroll)
2256 w.setMinimumHeight(len(plugins)*35)
2258 grid = QGridLayout()
2259 grid.setColumnStretch(0,1)
2262 def do_toggle(cb, p, w):
2265 if w: w.setEnabled(r)
2267 def mk_toggle(cb, p, w):
2268 return lambda: do_toggle(cb,p,w)
2270 for i, p in enumerate(plugins):
2272 cb = QCheckBox(p.fullname())
2273 cb.setDisabled(not p.is_available())
2274 cb.setChecked(p.is_enabled())
2275 grid.addWidget(cb, i, 0)
2276 if p.requires_settings():
2277 w = p.settings_widget(self)
2278 w.setEnabled( p.is_enabled() )
2279 grid.addWidget(w, i, 1)
2282 cb.clicked.connect(mk_toggle(cb,p,w))
2283 grid.addWidget(HelpButton(p.description()), i, 2)
2285 print_msg(_("Error: cannot display plugin"), p)
2286 traceback.print_exc(file=sys.stdout)
2287 grid.setRowStretch(i+1,1)
2289 vbox.addLayout(close_button(d))
2294 def show_account_details(self, k):
2295 account = self.wallet.accounts[k]
2298 d.setWindowTitle(_('Account Details'))
2301 vbox = QVBoxLayout(d)
2302 name = self.wallet.get_account_name(k)
2303 label = QLabel('Name: ' + name)
2304 vbox.addWidget(label)
2306 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2308 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2310 vbox.addWidget(QLabel(_('Master Public Key:')))
2313 text.setReadOnly(True)
2314 text.setMaximumHeight(170)
2315 vbox.addWidget(text)
2317 mpk_text = '\n'.join( account.get_master_pubkeys() )
2318 text.setText(mpk_text)
2320 vbox.addLayout(close_button(d))