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
32 print PyQt4.QtCore.PYQT_VERSION_STR
34 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
35 from electrum.plugins import run_hook
39 from electrum.wallet import format_satoshis
40 from electrum import Transaction
41 from electrum import mnemonic
42 from electrum import util, bitcoin, commands, Interface, Wallet
43 from electrum import SimpleConfig, Wallet, WalletStorage
46 from electrum import bmp, pyqrnative
48 from amountedit import AmountEdit
49 from network_dialog import NetworkDialog
50 from qrcodewidget import QRCodeWidget
52 from decimal import Decimal
60 if platform.system() == 'Windows':
61 MONOSPACE_FONT = 'Lucida Console'
62 elif platform.system() == 'Darwin':
63 MONOSPACE_FONT = 'Monaco'
65 MONOSPACE_FONT = 'monospace'
67 from electrum import ELECTRUM_VERSION
77 class StatusBarButton(QPushButton):
78 def __init__(self, icon, tooltip, func):
79 QPushButton.__init__(self, icon, '')
80 self.setToolTip(tooltip)
82 self.setMaximumWidth(25)
83 self.clicked.connect(func)
85 self.setIconSize(QSize(25,25))
87 def keyPressEvent(self, e):
88 if e.key() == QtCore.Qt.Key_Return:
100 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
102 class ElectrumWindow(QMainWindow):
106 def __init__(self, config, network, gui_object):
107 QMainWindow.__init__(self)
110 self.network = network
111 self.gui_object = gui_object
112 self.tray = gui_object.tray
113 self.go_lite = gui_object.go_lite
116 self.create_status_bar()
117 self.need_update = threading.Event()
119 self.decimal_point = config.get('decimal_point', 5)
120 self.num_zeros = int(config.get('num_zeros',0))
122 set_language(config.get('language'))
124 self.funds_error = False
125 self.completions = QStringListModel()
127 self.tabs = tabs = QTabWidget(self)
128 self.column_widths = self.config.get("column_widths_2", default_column_widths )
129 tabs.addTab(self.create_history_tab(), _('History') )
130 tabs.addTab(self.create_send_tab(), _('Send') )
131 tabs.addTab(self.create_receive_tab(), _('Receive') )
132 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
133 tabs.addTab(self.create_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('payment_request_ok'), self.payment_request_ok)
159 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
161 self.history_list.setFocus(True)
165 self.network.register_callback('updated', lambda: self.need_update.set())
166 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
167 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
168 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
169 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
171 # set initial message
172 self.console.showMessage(self.network.banner)
177 def update_account_selector(self):
179 accounts = self.wallet.get_account_names()
180 self.account_selector.clear()
181 if len(accounts) > 1:
182 self.account_selector.addItems([_("All accounts")] + accounts.values())
183 self.account_selector.setCurrentIndex(0)
184 self.account_selector.show()
186 self.account_selector.hide()
189 def load_wallet(self, wallet):
192 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
193 self.current_account = self.wallet.storage.get("current_account", None)
195 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
196 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
197 self.setWindowTitle( title )
199 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
200 self.notify_transactions()
201 self.update_account_selector()
203 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
204 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
205 self.password_menu.setEnabled(not self.wallet.is_watching_only())
206 self.seed_menu.setEnabled(self.wallet.has_seed())
207 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
208 self.import_menu.setEnabled(self.wallet.can_import())
210 self.update_lock_icon()
211 self.update_buttons_on_seed()
212 self.update_console()
214 run_hook('load_wallet', wallet)
217 def open_wallet(self):
218 wallet_folder = self.wallet.storage.path
219 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
223 storage = WalletStorage({'wallet_path': filename})
224 if not storage.file_exists:
225 self.show_message("file not found "+ filename)
228 self.wallet.stop_threads()
231 wallet = Wallet(storage)
232 wallet.start_threads(self.network)
234 self.load_wallet(wallet)
238 def backup_wallet(self):
240 path = self.wallet.storage.path
241 wallet_folder = os.path.dirname(path)
242 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
246 new_path = os.path.join(wallet_folder, filename)
249 shutil.copy2(path, new_path)
250 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
251 except (IOError, os.error), reason:
252 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
255 def new_wallet(self):
258 wallet_folder = os.path.dirname(self.wallet.storage.path)
259 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
262 filename = os.path.join(wallet_folder, filename)
264 storage = WalletStorage({'wallet_path': filename})
265 if storage.file_exists:
266 QMessageBox.critical(None, "Error", _("File exists"))
269 wizard = installwizard.InstallWizard(self.config, self.network, storage)
270 wallet = wizard.run('new')
272 self.load_wallet(wallet)
276 def init_menubar(self):
279 file_menu = menubar.addMenu(_("&File"))
280 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
281 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
282 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
283 file_menu.addAction(_("&Quit"), self.close)
285 wallet_menu = menubar.addMenu(_("&Wallet"))
286 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
287 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
289 wallet_menu.addSeparator()
291 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
292 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
293 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
295 wallet_menu.addSeparator()
296 labels_menu = wallet_menu.addMenu(_("&Labels"))
297 labels_menu.addAction(_("&Import"), self.do_import_labels)
298 labels_menu.addAction(_("&Export"), self.do_export_labels)
300 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
301 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
302 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
303 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
304 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
306 tools_menu = menubar.addMenu(_("&Tools"))
308 # Settings / Preferences are all reserved keywords in OSX using this as work around
309 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
310 tools_menu.addAction(_("&Network"), self.run_network_dialog)
311 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
312 tools_menu.addSeparator()
313 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
314 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
315 tools_menu.addSeparator()
317 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
318 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
319 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
321 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
322 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
323 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
324 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
326 help_menu = menubar.addMenu(_("&Help"))
327 help_menu.addAction(_("&About"), self.show_about)
328 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
329 help_menu.addSeparator()
330 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
331 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
333 self.setMenuBar(menubar)
335 def show_about(self):
336 QMessageBox.about(self, "Electrum",
337 _("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."))
339 def show_report_bug(self):
340 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
341 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
344 def notify_transactions(self):
345 if not self.network or not self.network.is_connected():
348 print_error("Notifying GUI")
349 if len(self.network.pending_transactions_for_notifications) > 0:
350 # Combine the transactions if there are more then three
351 tx_amount = len(self.network.pending_transactions_for_notifications)
354 for tx in self.network.pending_transactions_for_notifications:
355 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
359 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
360 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
362 self.network.pending_transactions_for_notifications = []
364 for tx in self.network.pending_transactions_for_notifications:
366 self.network.pending_transactions_for_notifications.remove(tx)
367 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
369 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
371 def notify(self, message):
372 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
376 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
377 def getOpenFileName(self, title, filter = ""):
378 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
379 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
380 if fileName and directory != os.path.dirname(fileName):
381 self.config.set_key('io_dir', os.path.dirname(fileName), True)
384 def getSaveFileName(self, title, filename, filter = ""):
385 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
386 path = os.path.join( directory, filename )
387 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
388 if fileName and directory != os.path.dirname(fileName):
389 self.config.set_key('io_dir', os.path.dirname(fileName), True)
393 QMainWindow.close(self)
394 run_hook('close_main_window')
396 def connect_slots(self, sender):
397 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
398 self.previous_payto_e=''
400 def timer_actions(self):
401 if self.need_update.is_set():
403 self.need_update.clear()
404 run_hook('timer_actions')
406 def format_amount(self, x, is_diff=False, whitespaces=False):
407 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
409 def read_amount(self, x):
410 if x in['.', '']: return None
411 p = pow(10, self.decimal_point)
412 return int( p * Decimal(x) )
415 assert self.decimal_point in [5,8]
416 return "BTC" if self.decimal_point == 8 else "mBTC"
419 def update_status(self):
420 if self.network is None or not self.network.is_running():
422 icon = QIcon(":icons/status_disconnected.png")
424 elif self.network.is_connected():
425 if not self.wallet.up_to_date:
426 text = _("Synchronizing...")
427 icon = QIcon(":icons/status_waiting.png")
428 elif self.network.server_lag > 1:
429 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
430 icon = QIcon(":icons/status_lagging.png")
432 c, u = self.wallet.get_account_balance(self.current_account)
433 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
434 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
436 # append fiat balance and price from exchange rate plugin
438 run_hook('get_fiat_status_text', c+u, r)
443 self.tray.setToolTip(text)
444 icon = QIcon(":icons/status_connected.png")
446 text = _("Not connected")
447 icon = QIcon(":icons/status_disconnected.png")
449 self.balance_label.setText(text)
450 self.status_button.setIcon( icon )
453 def update_wallet(self):
455 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
456 self.update_history_tab()
457 self.update_receive_tab()
458 self.update_contacts_tab()
459 self.update_completions()
462 def create_history_tab(self):
463 self.history_list = l = MyTreeWidget(self)
465 for i,width in enumerate(self.column_widths['history']):
466 l.setColumnWidth(i, width)
467 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
468 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
469 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
471 l.customContextMenuRequested.connect(self.create_history_menu)
475 def create_history_menu(self, position):
476 self.history_list.selectedIndexes()
477 item = self.history_list.currentItem()
478 be = self.config.get('block_explorer', 'Blockchain.info')
479 if be == 'Blockchain.info':
480 block_explorer = 'https://blockchain.info/tx/'
481 elif be == 'Blockr.io':
482 block_explorer = 'https://blockr.io/tx/info/'
483 elif be == 'Insight.is':
484 block_explorer = 'http://live.insight.is/tx/'
486 tx_hash = str(item.data(0, Qt.UserRole).toString())
487 if not tx_hash: return
489 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
490 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
491 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
492 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
493 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
496 def show_transaction(self, tx):
497 import transaction_dialog
498 d = transaction_dialog.TxDialog(tx, self)
501 def tx_label_clicked(self, item, column):
502 if column==2 and item.isSelected():
504 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
505 self.history_list.editItem( item, column )
506 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
509 def tx_label_changed(self, item, column):
513 tx_hash = str(item.data(0, Qt.UserRole).toString())
514 tx = self.wallet.transactions.get(tx_hash)
515 text = unicode( item.text(2) )
516 self.wallet.set_label(tx_hash, text)
518 item.setForeground(2, QBrush(QColor('black')))
520 text = self.wallet.get_default_label(tx_hash)
521 item.setText(2, text)
522 item.setForeground(2, QBrush(QColor('gray')))
526 def edit_label(self, is_recv):
527 l = self.receive_list if is_recv else self.contacts_list
528 item = l.currentItem()
529 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
530 l.editItem( item, 1 )
531 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
535 def address_label_clicked(self, item, column, l, column_addr, column_label):
536 if column == column_label and item.isSelected():
537 is_editable = item.data(0, 32).toBool()
540 addr = unicode( item.text(column_addr) )
541 label = unicode( item.text(column_label) )
542 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
543 l.editItem( item, column )
544 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
547 def address_label_changed(self, item, column, l, column_addr, column_label):
548 if column == column_label:
549 addr = unicode( item.text(column_addr) )
550 text = unicode( item.text(column_label) )
551 is_editable = item.data(0, 32).toBool()
555 changed = self.wallet.set_label(addr, text)
557 self.update_history_tab()
558 self.update_completions()
560 self.current_item_changed(item)
562 run_hook('item_changed', item, column)
565 def current_item_changed(self, a):
566 run_hook('current_item_changed', a)
570 def update_history_tab(self):
572 self.history_list.clear()
573 for item in self.wallet.get_tx_history(self.current_account):
574 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
575 time_str = _("unknown")
578 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
580 time_str = _("error")
583 time_str = 'unverified'
584 icon = QIcon(":icons/unconfirmed.png")
587 icon = QIcon(":icons/unconfirmed.png")
589 icon = QIcon(":icons/clock%d.png"%conf)
591 icon = QIcon(":icons/confirmed.png")
593 if value is not None:
594 v_str = self.format_amount(value, True, whitespaces=True)
598 balance_str = self.format_amount(balance, whitespaces=True)
601 label, is_default_label = self.wallet.get_label(tx_hash)
603 label = _('Pruned transaction outputs')
604 is_default_label = False
606 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
607 item.setFont(2, QFont(MONOSPACE_FONT))
608 item.setFont(3, QFont(MONOSPACE_FONT))
609 item.setFont(4, QFont(MONOSPACE_FONT))
611 item.setForeground(3, QBrush(QColor("#BC1E1E")))
613 item.setData(0, Qt.UserRole, tx_hash)
614 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
616 item.setForeground(2, QBrush(QColor('grey')))
618 item.setIcon(0, icon)
619 self.history_list.insertTopLevelItem(0,item)
622 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
623 run_hook('history_tab_update')
626 def create_send_tab(self):
631 grid.setColumnMinimumWidth(3,300)
632 grid.setColumnStretch(5,1)
635 self.payto_e = QLineEdit()
636 self.payto_help = HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)'))
637 grid.addWidget(QLabel(_('Pay to')), 1, 0)
638 grid.addWidget(self.payto_e, 1, 1, 1, 3)
639 grid.addWidget(self.payto_help, 1, 4)
641 completer = QCompleter()
642 completer.setCaseSensitivity(False)
643 self.payto_e.setCompleter(completer)
644 completer.setModel(self.completions)
646 self.message_e = QLineEdit()
647 self.message_help = HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.'))
648 grid.addWidget(QLabel(_('Description')), 2, 0)
649 grid.addWidget(self.message_e, 2, 1, 1, 3)
650 grid.addWidget(self.message_help, 2, 4)
652 self.from_label = QLabel(_('From'))
653 grid.addWidget(self.from_label, 3, 0)
654 self.from_list = QTreeWidget(self)
655 self.from_list.setColumnCount(2)
656 self.from_list.setColumnWidth(0, 350)
657 self.from_list.setColumnWidth(1, 50)
658 self.from_list.setHeaderHidden (True)
659 self.from_list.setMaximumHeight(80)
660 grid.addWidget(self.from_list, 3, 1, 1, 3)
661 self.set_pay_from([])
663 self.amount_e = AmountEdit(self.base_unit)
664 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
665 + _('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.') \
666 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
667 grid.addWidget(QLabel(_('Amount')), 4, 0)
668 grid.addWidget(self.amount_e, 4, 1, 1, 2)
669 grid.addWidget(self.amount_help, 4, 3)
671 self.fee_e = AmountEdit(self.base_unit)
672 grid.addWidget(QLabel(_('Fee')), 5, 0)
673 grid.addWidget(self.fee_e, 5, 1, 1, 2)
674 grid.addWidget(HelpButton(
675 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
676 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
677 + _('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)
679 run_hook('exchange_rate_button', grid)
681 self.send_button = EnterButton(_("Send"), self.do_send)
682 grid.addWidget(self.send_button, 6, 1)
684 b = EnterButton(_("Clear"),self.do_clear)
685 grid.addWidget(b, 6, 2)
687 self.payto_sig = QLabel('')
688 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
690 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
691 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
700 def entry_changed( is_fee ):
701 self.funds_error = False
703 if self.amount_e.is_shortcut:
704 self.amount_e.is_shortcut = False
705 sendable = self.get_sendable_balance()
706 # there is only one output because we are completely spending inputs
707 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
708 fee = self.wallet.estimated_fee(inputs, 1)
710 self.amount_e.setText( self.format_amount(amount) )
711 self.fee_e.setText( self.format_amount( fee ) )
714 amount = self.read_amount(str(self.amount_e.text()))
715 fee = self.read_amount(str(self.fee_e.text()))
717 if not is_fee: fee = None
720 # assume that there will be 2 outputs (one for change)
721 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
723 self.fee_e.setText( self.format_amount( fee ) )
726 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
730 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
731 self.funds_error = True
732 text = _( "Not enough funds" )
733 c, u = self.wallet.get_frozen_balance()
734 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
736 self.statusBar().showMessage(text)
737 self.amount_e.setPalette(palette)
738 self.fee_e.setPalette(palette)
740 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
741 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
743 run_hook('create_send_tab', grid)
747 def set_pay_from(self, l):
749 self.from_list.clear()
750 self.from_label.setHidden(len(self.pay_from) == 0)
751 self.from_list.setHidden(len(self.pay_from) == 0)
752 for addr in self.pay_from:
753 c, u = self.wallet.get_addr_balance(addr)
754 balance = self.format_amount(c + u)
755 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
758 def update_completions(self):
760 for addr,label in self.wallet.labels.items():
761 if addr in self.wallet.addressbook:
762 l.append( label + ' <' + addr + '>')
764 run_hook('update_completions', l)
765 self.completions.setStringList(l)
769 return lambda s, *args: s.do_protect(func, args)
773 label = unicode( self.message_e.text() )
775 if self.gui_object.payment_request:
776 outputs = self.gui_object.payment_request.outputs
777 amount = self.gui_object.payment_request.get_amount()
780 r = unicode( self.payto_e.text() )
783 # label or alias, with address in brackets
784 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
785 to_address = m.group(2) if m else r
786 if not is_valid(to_address):
787 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
791 amount = self.read_amount(unicode( self.amount_e.text()))
793 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
796 outputs = [(to_address, amount)]
799 fee = self.read_amount(unicode( self.fee_e.text()))
801 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
804 confirm_amount = self.config.get('confirm_amount', 100000000)
805 if amount >= confirm_amount:
806 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
809 confirm_fee = self.config.get('confirm_fee', 100000)
810 if fee >= confirm_fee:
811 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()}):
814 self.send_tx(outputs, fee, label)
819 def send_tx(self, outputs, fee, label, password):
821 # first, create an unsigned tx
822 domain = self.get_payment_sources()
824 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
826 except Exception as e:
827 traceback.print_exc(file=sys.stdout)
828 self.show_message(str(e))
831 # call hook to see if plugin needs gui interaction
832 run_hook('send_tx', tx)
838 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
839 self.wallet.sign_transaction(tx, keypairs, password)
840 return tx, fee, label
842 def sign_done(tx, fee, label):
844 self.show_message(tx.error)
846 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
847 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
850 self.wallet.set_label(tx.hash(), label)
852 if not self.gui_object.payment_request:
853 if not tx.is_complete() or self.config.get('show_before_broadcast'):
854 self.show_transaction(tx)
857 self.broadcast_transaction(tx)
859 WaitingDialog(self, 'Signing..').start(sign_thread, sign_done)
863 def broadcast_transaction(self, tx):
865 def broadcast_thread():
866 if self.gui_object.payment_request:
867 refund_address = self.wallet.addresses()[0]
868 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
869 self.gui_object.payment_request = None
871 status, msg = self.wallet.sendtx(tx)
874 def broadcast_done(status, msg):
876 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
879 QMessageBox.warning(self, _('Error'), msg, _('OK'))
881 WaitingDialog(self, 'Broadcasting..').start(broadcast_thread, broadcast_done)
885 def prepare_for_payment_request(self):
886 style = "QWidget { background-color:none;border:none;}"
887 self.tabs.setCurrentIndex(1)
888 for e in [self.payto_e, self.amount_e, self.message_e]:
890 e.setStyleSheet(style)
891 for h in [self.payto_help, self.amount_help, self.message_help]:
893 self.payto_e.setText(_("please wait..."))
896 def payment_request_ok(self):
897 self.payto_e.setText(self.gui_object.payment_request.domain)
898 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
899 self.message_e.setText(self.gui_object.payment_request.memo)
901 def payment_request_error(self):
903 self.show_message(self.gui_object.payment_request.error)
906 def set_send(self, address, amount, label, message):
908 if label and self.wallet.labels.get(address) != label:
909 if self.question('Give label "%s" to address %s ?'%(label,address)):
910 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
911 self.wallet.addressbook.append(address)
912 self.wallet.set_label(address, label)
914 self.tabs.setCurrentIndex(1)
915 label = self.wallet.labels.get(address)
916 m_addr = label + ' <'+ address +'>' if label else address
917 self.payto_e.setText(m_addr)
919 self.message_e.setText(message)
921 self.amount_e.setText(amount)
925 self.payto_sig.setVisible(False)
926 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
928 self.set_frozen(e,False)
930 for h in [self.payto_help, self.amount_help, self.message_help]:
933 self.set_pay_from([])
936 def set_frozen(self,entry,frozen):
938 entry.setReadOnly(True)
939 entry.setFrame(False)
941 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
942 entry.setPalette(palette)
944 entry.setReadOnly(False)
947 palette.setColor(entry.backgroundRole(), QColor('white'))
948 entry.setPalette(palette)
951 def set_addrs_frozen(self,addrs,freeze):
953 if not addr: continue
954 if addr in self.wallet.frozen_addresses and not freeze:
955 self.wallet.unfreeze(addr)
956 elif addr not in self.wallet.frozen_addresses and freeze:
957 self.wallet.freeze(addr)
958 self.update_receive_tab()
962 def create_list_tab(self, headers):
963 "generic tab creation method"
964 l = MyTreeWidget(self)
965 l.setColumnCount( len(headers) )
966 l.setHeaderLabels( headers )
976 vbox.addWidget(buttons)
981 buttons.setLayout(hbox)
986 def create_receive_tab(self):
987 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
988 l.setContextMenuPolicy(Qt.CustomContextMenu)
989 l.customContextMenuRequested.connect(self.create_receive_menu)
990 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
991 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
992 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
993 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
994 self.receive_list = l
995 self.receive_buttons_hbox = hbox
1002 def save_column_widths(self):
1003 self.column_widths["receive"] = []
1004 for i in range(self.receive_list.columnCount() -1):
1005 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1007 self.column_widths["history"] = []
1008 for i in range(self.history_list.columnCount() - 1):
1009 self.column_widths["history"].append(self.history_list.columnWidth(i))
1011 self.column_widths["contacts"] = []
1012 for i in range(self.contacts_list.columnCount() - 1):
1013 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1015 self.config.set_key("column_widths_2", self.column_widths, True)
1018 def create_contacts_tab(self):
1019 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1020 l.setContextMenuPolicy(Qt.CustomContextMenu)
1021 l.customContextMenuRequested.connect(self.create_contact_menu)
1022 for i,width in enumerate(self.column_widths['contacts']):
1023 l.setColumnWidth(i, width)
1025 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1026 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1027 self.contacts_list = l
1028 self.contacts_buttons_hbox = hbox
1033 def delete_imported_key(self, addr):
1034 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1035 self.wallet.delete_imported_key(addr)
1036 self.update_receive_tab()
1037 self.update_history_tab()
1039 def edit_account_label(self, k):
1040 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1042 label = unicode(text)
1043 self.wallet.set_label(k,label)
1044 self.update_receive_tab()
1046 def account_set_expanded(self, item, k, b):
1048 self.accounts_expanded[k] = b
1050 def create_account_menu(self, position, k, item):
1052 if item.isExpanded():
1053 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1055 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1056 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1057 if self.wallet.seed_version > 4:
1058 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1059 if self.wallet.account_is_pending(k):
1060 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1061 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1063 def delete_pending_account(self, k):
1064 self.wallet.delete_pending_account(k)
1065 self.update_receive_tab()
1067 def create_receive_menu(self, position):
1068 # fixme: this function apparently has a side effect.
1069 # if it is not called the menu pops up several times
1070 #self.receive_list.selectedIndexes()
1072 selected = self.receive_list.selectedItems()
1073 multi_select = len(selected) > 1
1074 addrs = [unicode(item.text(0)) for item in selected]
1075 if not multi_select:
1076 item = self.receive_list.itemAt(position)
1080 if not is_valid(addr):
1081 k = str(item.data(0,32).toString())
1083 self.create_account_menu(position, k, item)
1085 item.setExpanded(not item.isExpanded())
1089 if not multi_select:
1090 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1091 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1092 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1093 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1094 if not self.wallet.is_watching_only():
1095 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1096 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1097 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1098 if self.wallet.is_imported(addr):
1099 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1101 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1102 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1103 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1104 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1106 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1107 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1109 run_hook('receive_menu', menu, addrs)
1110 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1113 def get_sendable_balance(self):
1114 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1117 def get_payment_sources(self):
1119 return self.pay_from
1121 return self.wallet.get_account_addresses(self.current_account)
1124 def send_from_addresses(self, addrs):
1125 self.set_pay_from( addrs )
1126 self.tabs.setCurrentIndex(1)
1129 def payto(self, addr):
1131 label = self.wallet.labels.get(addr)
1132 m_addr = label + ' <' + addr + '>' if label else addr
1133 self.tabs.setCurrentIndex(1)
1134 self.payto_e.setText(m_addr)
1135 self.amount_e.setFocus()
1138 def delete_contact(self, x):
1139 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1140 self.wallet.delete_contact(x)
1141 self.wallet.set_label(x, None)
1142 self.update_history_tab()
1143 self.update_contacts_tab()
1144 self.update_completions()
1147 def create_contact_menu(self, position):
1148 item = self.contacts_list.itemAt(position)
1151 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1153 addr = unicode(item.text(0))
1154 label = unicode(item.text(1))
1155 is_editable = item.data(0,32).toBool()
1156 payto_addr = item.data(0,33).toString()
1157 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1158 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1159 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1161 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1162 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1164 run_hook('create_contact_menu', menu, item)
1165 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1168 def update_receive_item(self, item):
1169 item.setFont(0, QFont(MONOSPACE_FONT))
1170 address = str(item.data(0,0).toString())
1171 label = self.wallet.labels.get(address,'')
1172 item.setData(1,0,label)
1173 item.setData(0,32, True) # is editable
1175 run_hook('update_receive_item', address, item)
1177 if not self.wallet.is_mine(address): return
1179 c, u = self.wallet.get_addr_balance(address)
1180 balance = self.format_amount(c + u)
1181 item.setData(2,0,balance)
1183 if address in self.wallet.frozen_addresses:
1184 item.setBackgroundColor(0, QColor('lightblue'))
1187 def update_receive_tab(self):
1188 l = self.receive_list
1189 # extend the syntax for consistency
1190 l.addChild = l.addTopLevelItem
1191 l.insertChild = l.insertTopLevelItem
1194 for i,width in enumerate(self.column_widths['receive']):
1195 l.setColumnWidth(i, width)
1197 accounts = self.wallet.get_accounts()
1198 if self.current_account is None:
1199 account_items = sorted(accounts.items())
1201 account_items = [(self.current_account, accounts.get(self.current_account))]
1204 for k, account in account_items:
1206 if len(accounts) > 1:
1207 name = self.wallet.get_account_name(k)
1208 c,u = self.wallet.get_account_balance(k)
1209 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1210 l.addTopLevelItem(account_item)
1211 account_item.setExpanded(self.accounts_expanded.get(k, True))
1212 account_item.setData(0, 32, k)
1216 sequences = [0,1] if account.has_change() else [0]
1217 for is_change in sequences:
1218 if len(sequences) > 1:
1219 name = _("Receiving") if not is_change else _("Change")
1220 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1221 account_item.addChild(seq_item)
1223 seq_item.setExpanded(True)
1225 seq_item = account_item
1227 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1233 for address in account.get_addresses(is_change):
1235 num, is_used = self.wallet.is_used(address)
1238 if gap > self.wallet.gap_limit:
1243 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1244 self.update_receive_item(item)
1246 item.setBackgroundColor(1, QColor('red'))
1250 seq_item.insertChild(0,used_item)
1252 used_item.addChild(item)
1254 seq_item.addChild(item)
1256 # we use column 1 because column 0 may be hidden
1257 l.setCurrentItem(l.topLevelItem(0),1)
1260 def update_contacts_tab(self):
1261 l = self.contacts_list
1264 for address in self.wallet.addressbook:
1265 label = self.wallet.labels.get(address,'')
1266 n = self.wallet.get_num_tx(address)
1267 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1268 item.setFont(0, QFont(MONOSPACE_FONT))
1269 # 32 = label can be edited (bool)
1270 item.setData(0,32, True)
1272 item.setData(0,33, address)
1273 l.addTopLevelItem(item)
1275 run_hook('update_contacts_tab', l)
1276 l.setCurrentItem(l.topLevelItem(0))
1280 def create_console_tab(self):
1281 from console import Console
1282 self.console = console = Console()
1286 def update_console(self):
1287 console = self.console
1288 console.history = self.config.get("console-history",[])
1289 console.history_index = len(console.history)
1291 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1292 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1294 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1296 def mkfunc(f, method):
1297 return lambda *args: apply( f, (method, args, self.password_dialog ))
1299 if m[0]=='_' or m in ['network','wallet']: continue
1300 methods[m] = mkfunc(c._run, m)
1302 console.updateNamespace(methods)
1305 def change_account(self,s):
1306 if s == _("All accounts"):
1307 self.current_account = None
1309 accounts = self.wallet.get_account_names()
1310 for k, v in accounts.items():
1312 self.current_account = k
1313 self.update_history_tab()
1314 self.update_status()
1315 self.update_receive_tab()
1317 def create_status_bar(self):
1320 sb.setFixedHeight(35)
1321 qtVersion = qVersion()
1323 self.balance_label = QLabel("")
1324 sb.addWidget(self.balance_label)
1326 from version_getter import UpdateLabel
1327 self.updatelabel = UpdateLabel(self.config, sb)
1329 self.account_selector = QComboBox()
1330 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1331 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1332 sb.addPermanentWidget(self.account_selector)
1334 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1335 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1337 self.lock_icon = QIcon()
1338 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1339 sb.addPermanentWidget( self.password_button )
1341 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1342 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1343 sb.addPermanentWidget( self.seed_button )
1344 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1345 sb.addPermanentWidget( self.status_button )
1347 run_hook('create_status_bar', (sb,))
1349 self.setStatusBar(sb)
1352 def update_lock_icon(self):
1353 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1354 self.password_button.setIcon( icon )
1357 def update_buttons_on_seed(self):
1358 if self.wallet.has_seed():
1359 self.seed_button.show()
1361 self.seed_button.hide()
1363 if not self.wallet.is_watching_only():
1364 self.password_button.show()
1365 self.send_button.setText(_("Send"))
1367 self.password_button.hide()
1368 self.send_button.setText(_("Create unsigned transaction"))
1371 def change_password_dialog(self):
1372 from password_dialog import PasswordDialog
1373 d = PasswordDialog(self.wallet, self)
1375 self.update_lock_icon()
1378 def new_contact_dialog(self):
1381 d.setWindowTitle(_("New Contact"))
1382 vbox = QVBoxLayout(d)
1383 vbox.addWidget(QLabel(_('New Contact')+':'))
1385 grid = QGridLayout()
1388 grid.addWidget(QLabel(_("Address")), 1, 0)
1389 grid.addWidget(line1, 1, 1)
1390 grid.addWidget(QLabel(_("Name")), 2, 0)
1391 grid.addWidget(line2, 2, 1)
1393 vbox.addLayout(grid)
1394 vbox.addLayout(ok_cancel_buttons(d))
1399 address = str(line1.text())
1400 label = unicode(line2.text())
1402 if not is_valid(address):
1403 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1406 self.wallet.add_contact(address)
1408 self.wallet.set_label(address, label)
1410 self.update_contacts_tab()
1411 self.update_history_tab()
1412 self.update_completions()
1413 self.tabs.setCurrentIndex(3)
1417 def new_account_dialog(self, password):
1419 dialog = QDialog(self)
1421 dialog.setWindowTitle(_("New Account"))
1423 vbox = QVBoxLayout()
1424 vbox.addWidget(QLabel(_('Account name')+':'))
1427 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1428 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1433 vbox.addLayout(ok_cancel_buttons(dialog))
1434 dialog.setLayout(vbox)
1438 name = str(e.text())
1441 self.wallet.create_pending_account(name, password)
1442 self.update_receive_tab()
1443 self.tabs.setCurrentIndex(2)
1448 def show_master_public_keys(self):
1450 dialog = QDialog(self)
1452 dialog.setWindowTitle(_("Master Public Keys"))
1454 main_layout = QGridLayout()
1455 mpk_dict = self.wallet.get_master_public_keys()
1457 for key, value in mpk_dict.items():
1458 main_layout.addWidget(QLabel(key), i, 0)
1459 mpk_text = QTextEdit()
1460 mpk_text.setReadOnly(True)
1461 mpk_text.setMaximumHeight(170)
1462 mpk_text.setText(value)
1463 main_layout.addWidget(mpk_text, i + 1, 0)
1466 vbox = QVBoxLayout()
1467 vbox.addLayout(main_layout)
1468 vbox.addLayout(close_button(dialog))
1470 dialog.setLayout(vbox)
1475 def show_seed_dialog(self, password):
1476 if not self.wallet.has_seed():
1477 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1481 mnemonic = self.wallet.get_mnemonic(password)
1483 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1485 from seed_dialog import SeedDialog
1486 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1491 def show_qrcode(self, data, title = _("QR code")):
1495 d.setWindowTitle(title)
1496 d.setMinimumSize(270, 300)
1497 vbox = QVBoxLayout()
1498 qrw = QRCodeWidget(data)
1499 vbox.addWidget(qrw, 1)
1500 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1501 hbox = QHBoxLayout()
1504 filename = os.path.join(self.config.path, "qrcode.bmp")
1507 bmp.save_qrcode(qrw.qr, filename)
1508 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1510 def copy_to_clipboard():
1511 bmp.save_qrcode(qrw.qr, filename)
1512 self.app.clipboard().setImage(QImage(filename))
1513 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1515 b = QPushButton(_("Copy"))
1517 b.clicked.connect(copy_to_clipboard)
1519 b = QPushButton(_("Save"))
1521 b.clicked.connect(print_qr)
1523 b = QPushButton(_("Close"))
1525 b.clicked.connect(d.accept)
1528 vbox.addLayout(hbox)
1533 def do_protect(self, func, args):
1534 if self.wallet.use_encryption:
1535 password = self.password_dialog()
1541 if args != (False,):
1542 args = (self,) + args + (password,)
1544 args = (self,password)
1548 def show_public_keys(self, address):
1549 if not address: return
1551 pubkey_list = self.wallet.get_public_keys(address)
1552 except Exception as e:
1553 traceback.print_exc(file=sys.stdout)
1554 self.show_message(str(e))
1558 d.setMinimumSize(600, 200)
1560 vbox = QVBoxLayout()
1561 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1562 vbox.addWidget( QLabel(_("Public key") + ':'))
1564 keys.setReadOnly(True)
1565 keys.setText('\n'.join(pubkey_list))
1566 vbox.addWidget(keys)
1567 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1568 vbox.addLayout(close_button(d))
1573 def show_private_key(self, address, password):
1574 if not address: return
1576 pk_list = self.wallet.get_private_key(address, password)
1577 except Exception as e:
1578 traceback.print_exc(file=sys.stdout)
1579 self.show_message(str(e))
1583 d.setMinimumSize(600, 200)
1585 vbox = QVBoxLayout()
1586 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1587 vbox.addWidget( QLabel(_("Private key") + ':'))
1589 keys.setReadOnly(True)
1590 keys.setText('\n'.join(pk_list))
1591 vbox.addWidget(keys)
1592 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1593 vbox.addLayout(close_button(d))
1599 def do_sign(self, address, message, signature, password):
1600 message = unicode(message.toPlainText())
1601 message = message.encode('utf-8')
1603 sig = self.wallet.sign_message(str(address.text()), message, password)
1604 signature.setText(sig)
1605 except Exception as e:
1606 self.show_message(str(e))
1608 def do_verify(self, address, message, signature):
1609 message = unicode(message.toPlainText())
1610 message = message.encode('utf-8')
1611 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1612 self.show_message(_("Signature verified"))
1614 self.show_message(_("Error: wrong signature"))
1617 def sign_verify_message(self, address=''):
1620 d.setWindowTitle(_('Sign/verify Message'))
1621 d.setMinimumSize(410, 290)
1623 layout = QGridLayout(d)
1625 message_e = QTextEdit()
1626 layout.addWidget(QLabel(_('Message')), 1, 0)
1627 layout.addWidget(message_e, 1, 1)
1628 layout.setRowStretch(2,3)
1630 address_e = QLineEdit()
1631 address_e.setText(address)
1632 layout.addWidget(QLabel(_('Address')), 2, 0)
1633 layout.addWidget(address_e, 2, 1)
1635 signature_e = QTextEdit()
1636 layout.addWidget(QLabel(_('Signature')), 3, 0)
1637 layout.addWidget(signature_e, 3, 1)
1638 layout.setRowStretch(3,1)
1640 hbox = QHBoxLayout()
1642 b = QPushButton(_("Sign"))
1643 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1646 b = QPushButton(_("Verify"))
1647 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1650 b = QPushButton(_("Close"))
1651 b.clicked.connect(d.accept)
1653 layout.addLayout(hbox, 4, 1)
1658 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1660 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1661 message_e.setText(decrypted)
1662 except Exception as e:
1663 self.show_message(str(e))
1666 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1667 message = unicode(message_e.toPlainText())
1668 message = message.encode('utf-8')
1670 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1671 encrypted_e.setText(encrypted)
1672 except Exception as e:
1673 self.show_message(str(e))
1677 def encrypt_message(self, address = ''):
1680 d.setWindowTitle(_('Encrypt/decrypt Message'))
1681 d.setMinimumSize(610, 490)
1683 layout = QGridLayout(d)
1685 message_e = QTextEdit()
1686 layout.addWidget(QLabel(_('Message')), 1, 0)
1687 layout.addWidget(message_e, 1, 1)
1688 layout.setRowStretch(2,3)
1690 pubkey_e = QLineEdit()
1692 pubkey = self.wallet.getpubkeys(address)[0]
1693 pubkey_e.setText(pubkey)
1694 layout.addWidget(QLabel(_('Public key')), 2, 0)
1695 layout.addWidget(pubkey_e, 2, 1)
1697 encrypted_e = QTextEdit()
1698 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1699 layout.addWidget(encrypted_e, 3, 1)
1700 layout.setRowStretch(3,1)
1702 hbox = QHBoxLayout()
1703 b = QPushButton(_("Encrypt"))
1704 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1707 b = QPushButton(_("Decrypt"))
1708 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1711 b = QPushButton(_("Close"))
1712 b.clicked.connect(d.accept)
1715 layout.addLayout(hbox, 4, 1)
1719 def question(self, msg):
1720 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1722 def show_message(self, msg):
1723 QMessageBox.information(self, _('Message'), msg, _('OK'))
1725 def password_dialog(self ):
1728 d.setWindowTitle(_("Enter Password"))
1733 vbox = QVBoxLayout()
1734 msg = _('Please enter your password')
1735 vbox.addWidget(QLabel(msg))
1737 grid = QGridLayout()
1739 grid.addWidget(QLabel(_('Password')), 1, 0)
1740 grid.addWidget(pw, 1, 1)
1741 vbox.addLayout(grid)
1743 vbox.addLayout(ok_cancel_buttons(d))
1746 run_hook('password_dialog', pw, grid, 1)
1747 if not d.exec_(): return
1748 return unicode(pw.text())
1757 def tx_from_text(self, txt):
1758 "json or raw hexadecimal"
1761 tx = Transaction(txt)
1767 tx_dict = json.loads(str(txt))
1768 assert "hex" in tx_dict.keys()
1769 tx = Transaction(tx_dict["hex"])
1770 if tx_dict.has_key("input_info"):
1771 input_info = json.loads(tx_dict['input_info'])
1772 tx.add_input_info(input_info)
1775 traceback.print_exc(file=sys.stdout)
1778 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1782 def read_tx_from_file(self):
1783 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1787 with open(fileName, "r") as f:
1788 file_content = f.read()
1789 except (ValueError, IOError, os.error), reason:
1790 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1792 return self.tx_from_text(file_content)
1796 def sign_raw_transaction(self, tx, input_info, password):
1797 self.wallet.signrawtransaction(tx, input_info, [], password)
1799 def do_process_from_text(self):
1800 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1803 tx = self.tx_from_text(text)
1805 self.show_transaction(tx)
1807 def do_process_from_file(self):
1808 tx = self.read_tx_from_file()
1810 self.show_transaction(tx)
1812 def do_process_from_txid(self):
1813 from electrum import transaction
1814 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1816 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1818 tx = transaction.Transaction(r)
1820 self.show_transaction(tx)
1822 self.show_message("unknown transaction")
1824 def do_process_from_csvReader(self, csvReader):
1829 for position, row in enumerate(csvReader):
1831 if not is_valid(address):
1832 errors.append((position, address))
1834 amount = Decimal(row[1])
1835 amount = int(100000000*amount)
1836 outputs.append((address, amount))
1837 except (ValueError, IOError, os.error), reason:
1838 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1842 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1843 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1847 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1848 except Exception as e:
1849 self.show_message(str(e))
1852 self.show_transaction(tx)
1854 def do_process_from_csv_file(self):
1855 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1859 with open(fileName, "r") as f:
1860 csvReader = csv.reader(f)
1861 self.do_process_from_csvReader(csvReader)
1862 except (ValueError, IOError, os.error), reason:
1863 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1866 def do_process_from_csv_text(self):
1867 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1868 + _("Format: address, amount. One output per line"), _("Load CSV"))
1871 f = StringIO.StringIO(text)
1872 csvReader = csv.reader(f)
1873 self.do_process_from_csvReader(csvReader)
1878 def export_privkeys_dialog(self, password):
1879 if self.wallet.is_watching_only():
1880 self.show_message(_("This is a watching-only wallet"))
1884 d.setWindowTitle(_('Private keys'))
1885 d.setMinimumSize(850, 300)
1886 vbox = QVBoxLayout(d)
1888 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1889 _("Exposing a single private key can compromise your entire wallet!"),
1890 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1891 vbox.addWidget(QLabel(msg))
1897 defaultname = 'electrum-private-keys.csv'
1898 select_msg = _('Select file to export your private keys to')
1899 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1900 vbox.addLayout(hbox)
1902 h, b = ok_cancel_buttons2(d, _('Export'))
1907 addresses = self.wallet.addresses(True)
1909 def privkeys_thread():
1910 for addr in addresses:
1914 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1915 d.emit(SIGNAL('computing_privkeys'))
1916 d.emit(SIGNAL('show_privkeys'))
1918 def show_privkeys():
1919 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1923 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1924 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1925 threading.Thread(target=privkeys_thread).start()
1931 filename = filename_e.text()
1936 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1937 except (IOError, os.error), reason:
1938 export_error_label = _("Electrum was unable to produce a private key-export.")
1939 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1941 except Exception as e:
1942 self.show_message(str(e))
1945 self.show_message(_("Private keys exported."))
1948 def do_export_privkeys(self, fileName, pklist, is_csv):
1949 with open(fileName, "w+") as f:
1951 transaction = csv.writer(f)
1952 transaction.writerow(["address", "private_key"])
1953 for addr, pk in pklist.items():
1954 transaction.writerow(["%34s"%addr,pk])
1957 f.write(json.dumps(pklist, indent = 4))
1960 def do_import_labels(self):
1961 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1962 if not labelsFile: return
1964 f = open(labelsFile, 'r')
1967 for key, value in json.loads(data).items():
1968 self.wallet.set_label(key, value)
1969 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1970 except (IOError, os.error), reason:
1971 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1974 def do_export_labels(self):
1975 labels = self.wallet.labels
1977 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1979 with open(fileName, 'w+') as f:
1980 json.dump(labels, f)
1981 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1982 except (IOError, os.error), reason:
1983 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1986 def export_history_dialog(self):
1989 d.setWindowTitle(_('Export History'))
1990 d.setMinimumSize(400, 200)
1991 vbox = QVBoxLayout(d)
1993 defaultname = os.path.expanduser('~/electrum-history.csv')
1994 select_msg = _('Select file to export your wallet transactions to')
1996 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1997 vbox.addLayout(hbox)
2001 h, b = ok_cancel_buttons2(d, _('Export'))
2006 filename = filename_e.text()
2011 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2012 except (IOError, os.error), reason:
2013 export_error_label = _("Electrum was unable to produce a transaction export.")
2014 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2017 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2020 def do_export_history(self, wallet, fileName, is_csv):
2021 history = wallet.get_tx_history()
2023 for item in history:
2024 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2026 if timestamp is not None:
2028 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2029 except [RuntimeError, TypeError, NameError] as reason:
2030 time_string = "unknown"
2033 time_string = "unknown"
2035 time_string = "pending"
2037 if value is not None:
2038 value_string = format_satoshis(value, True)
2043 fee_string = format_satoshis(fee, True)
2048 label, is_default_label = wallet.get_label(tx_hash)
2049 label = label.encode('utf-8')
2053 balance_string = format_satoshis(balance, False)
2055 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2057 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2059 with open(fileName, "w+") as f:
2061 transaction = csv.writer(f)
2062 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2064 transaction.writerow(line)
2067 f.write(json.dumps(lines, indent = 4))
2070 def sweep_key_dialog(self):
2072 d.setWindowTitle(_('Sweep private keys'))
2073 d.setMinimumSize(600, 300)
2075 vbox = QVBoxLayout(d)
2076 vbox.addWidget(QLabel(_("Enter private keys")))
2078 keys_e = QTextEdit()
2079 keys_e.setTabChangesFocus(True)
2080 vbox.addWidget(keys_e)
2082 h, address_e = address_field(self.wallet.addresses())
2086 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2087 vbox.addLayout(hbox)
2088 button.setEnabled(False)
2091 addr = str(address_e.text())
2092 if bitcoin.is_address(addr):
2096 pk = str(keys_e.toPlainText()).strip()
2097 if Wallet.is_private_key(pk):
2100 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2101 keys_e.textChanged.connect(f)
2102 address_e.textChanged.connect(f)
2106 fee = self.wallet.fee
2107 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2108 self.show_transaction(tx)
2112 def do_import_privkey(self, password):
2113 if not self.wallet.imported_keys:
2114 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2115 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2116 + _('Are you sure you understand what you are doing?'), 3, 4)
2119 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2122 text = str(text).split()
2127 addr = self.wallet.import_key(key, password)
2128 except Exception as e:
2134 addrlist.append(addr)
2136 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2138 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2139 self.update_receive_tab()
2140 self.update_history_tab()
2143 def settings_dialog(self):
2145 d.setWindowTitle(_('Electrum Settings'))
2147 vbox = QVBoxLayout()
2148 grid = QGridLayout()
2149 grid.setColumnStretch(0,1)
2151 nz_label = QLabel(_('Display zeros') + ':')
2152 grid.addWidget(nz_label, 0, 0)
2153 nz_e = AmountEdit(None,True)
2154 nz_e.setText("%d"% self.num_zeros)
2155 grid.addWidget(nz_e, 0, 1)
2156 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2157 grid.addWidget(HelpButton(msg), 0, 2)
2158 if not self.config.is_modifiable('num_zeros'):
2159 for w in [nz_e, nz_label]: w.setEnabled(False)
2161 lang_label=QLabel(_('Language') + ':')
2162 grid.addWidget(lang_label, 1, 0)
2163 lang_combo = QComboBox()
2164 from electrum.i18n import languages
2165 lang_combo.addItems(languages.values())
2167 index = languages.keys().index(self.config.get("language",''))
2170 lang_combo.setCurrentIndex(index)
2171 grid.addWidget(lang_combo, 1, 1)
2172 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2173 if not self.config.is_modifiable('language'):
2174 for w in [lang_combo, lang_label]: w.setEnabled(False)
2177 fee_label = QLabel(_('Transaction fee') + ':')
2178 grid.addWidget(fee_label, 2, 0)
2179 fee_e = AmountEdit(self.base_unit)
2180 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2181 grid.addWidget(fee_e, 2, 1)
2182 msg = _('Fee per kilobyte of transaction.') + ' ' \
2183 + _('Recommended value') + ': ' + self.format_amount(20000)
2184 grid.addWidget(HelpButton(msg), 2, 2)
2185 if not self.config.is_modifiable('fee_per_kb'):
2186 for w in [fee_e, fee_label]: w.setEnabled(False)
2188 units = ['BTC', 'mBTC']
2189 unit_label = QLabel(_('Base unit') + ':')
2190 grid.addWidget(unit_label, 3, 0)
2191 unit_combo = QComboBox()
2192 unit_combo.addItems(units)
2193 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2194 grid.addWidget(unit_combo, 3, 1)
2195 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2196 + '\n1BTC=1000mBTC.\n' \
2197 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2199 usechange_cb = QCheckBox(_('Use change addresses'))
2200 usechange_cb.setChecked(self.wallet.use_change)
2201 grid.addWidget(usechange_cb, 4, 0)
2202 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2203 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2205 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2206 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2207 grid.addWidget(block_ex_label, 5, 0)
2208 block_ex_combo = QComboBox()
2209 block_ex_combo.addItems(block_explorers)
2210 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2211 grid.addWidget(block_ex_combo, 5, 1)
2212 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2214 show_tx = self.config.get('show_before_broadcast', False)
2215 showtx_cb = QCheckBox(_('Show before broadcast'))
2216 showtx_cb.setChecked(show_tx)
2217 grid.addWidget(showtx_cb, 6, 0)
2218 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2220 vbox.addLayout(grid)
2222 vbox.addLayout(ok_cancel_buttons(d))
2226 if not d.exec_(): return
2228 fee = unicode(fee_e.text())
2230 fee = self.read_amount(fee)
2232 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2235 self.wallet.set_fee(fee)
2237 nz = unicode(nz_e.text())
2242 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2245 if self.num_zeros != nz:
2247 self.config.set_key('num_zeros', nz, True)
2248 self.update_history_tab()
2249 self.update_receive_tab()
2251 usechange_result = usechange_cb.isChecked()
2252 if self.wallet.use_change != usechange_result:
2253 self.wallet.use_change = usechange_result
2254 self.wallet.storage.put('use_change', self.wallet.use_change)
2256 if showtx_cb.isChecked() != show_tx:
2257 self.config.set_key('show_before_broadcast', not show_tx)
2259 unit_result = units[unit_combo.currentIndex()]
2260 if self.base_unit() != unit_result:
2261 self.decimal_point = 8 if unit_result == 'BTC' else 5
2262 self.config.set_key('decimal_point', self.decimal_point, True)
2263 self.update_history_tab()
2264 self.update_status()
2266 need_restart = False
2268 lang_request = languages.keys()[lang_combo.currentIndex()]
2269 if lang_request != self.config.get('language'):
2270 self.config.set_key("language", lang_request, True)
2273 be_result = block_explorers[block_ex_combo.currentIndex()]
2274 self.config.set_key('block_explorer', be_result, True)
2276 run_hook('close_settings_dialog')
2279 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2282 def run_network_dialog(self):
2283 if not self.network:
2285 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2287 def closeEvent(self, event):
2289 self.config.set_key("is_maximized", self.isMaximized())
2290 if not self.isMaximized():
2292 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2293 self.save_column_widths()
2294 self.config.set_key("console-history", self.console.history[-50:], True)
2295 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2299 def plugins_dialog(self):
2300 from electrum.plugins import plugins
2303 d.setWindowTitle(_('Electrum Plugins'))
2306 vbox = QVBoxLayout(d)
2309 scroll = QScrollArea()
2310 scroll.setEnabled(True)
2311 scroll.setWidgetResizable(True)
2312 scroll.setMinimumSize(400,250)
2313 vbox.addWidget(scroll)
2317 w.setMinimumHeight(len(plugins)*35)
2319 grid = QGridLayout()
2320 grid.setColumnStretch(0,1)
2323 def do_toggle(cb, p, w):
2326 if w: w.setEnabled(r)
2328 def mk_toggle(cb, p, w):
2329 return lambda: do_toggle(cb,p,w)
2331 for i, p in enumerate(plugins):
2333 cb = QCheckBox(p.fullname())
2334 cb.setDisabled(not p.is_available())
2335 cb.setChecked(p.is_enabled())
2336 grid.addWidget(cb, i, 0)
2337 if p.requires_settings():
2338 w = p.settings_widget(self)
2339 w.setEnabled( p.is_enabled() )
2340 grid.addWidget(w, i, 1)
2343 cb.clicked.connect(mk_toggle(cb,p,w))
2344 grid.addWidget(HelpButton(p.description()), i, 2)
2346 print_msg(_("Error: cannot display plugin"), p)
2347 traceback.print_exc(file=sys.stdout)
2348 grid.setRowStretch(i+1,1)
2350 vbox.addLayout(close_button(d))
2355 def show_account_details(self, k):
2356 account = self.wallet.accounts[k]
2359 d.setWindowTitle(_('Account Details'))
2362 vbox = QVBoxLayout(d)
2363 name = self.wallet.get_account_name(k)
2364 label = QLabel('Name: ' + name)
2365 vbox.addWidget(label)
2367 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2369 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2371 vbox.addWidget(QLabel(_('Master Public Key:')))
2374 text.setReadOnly(True)
2375 text.setMaximumHeight(170)
2376 vbox.addWidget(text)
2378 mpk_text = '\n'.join( account.get_master_pubkeys() )
2379 text.setText(mpk_text)
2381 vbox.addLayout(close_button(d))