3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit, MyLineEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.gui_object = gui_object
111 self.tray = gui_object.tray
112 self.go_lite = gui_object.go_lite
115 self.create_status_bar()
116 self.need_update = threading.Event()
118 self.decimal_point = config.get('decimal_point', 5)
119 self.num_zeros = int(config.get('num_zeros',0))
121 set_language(config.get('language'))
123 self.funds_error = False
124 self.completions = QStringListModel()
126 self.tabs = tabs = QTabWidget(self)
127 self.column_widths = self.config.get("column_widths_2", default_column_widths )
128 tabs.addTab(self.create_history_tab(), _('History') )
129 tabs.addTab(self.create_send_tab(), _('Send') )
130 tabs.addTab(self.create_receive_tab(), _('Receive') )
131 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
132 tabs.addTab(self.create_console_tab(), _('Console') )
133 tabs.setMinimumSize(600, 400)
134 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
135 self.setCentralWidget(tabs)
137 g = self.config.get("winpos-qt",[100, 100, 840, 400])
138 self.setGeometry(g[0], g[1], g[2], g[3])
139 if self.config.get("is_maximized"):
142 self.setWindowIcon(QIcon(":icons/electrum.png"))
145 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
146 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
147 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
148 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
149 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
151 for i in range(tabs.count()):
152 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
154 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
155 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
156 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
157 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
158 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
160 self.history_list.setFocus(True)
164 self.network.register_callback('updated', lambda: self.need_update.set())
165 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
166 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
167 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
168 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
170 # set initial message
171 self.console.showMessage(self.network.banner)
176 def update_account_selector(self):
178 accounts = self.wallet.get_account_names()
179 self.account_selector.clear()
180 if len(accounts) > 1:
181 self.account_selector.addItems([_("All accounts")] + accounts.values())
182 self.account_selector.setCurrentIndex(0)
183 self.account_selector.show()
185 self.account_selector.hide()
188 def load_wallet(self, wallet):
192 self.update_wallet_format()
194 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
195 self.current_account = self.wallet.storage.get("current_account", None)
196 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
197 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
198 self.setWindowTitle( title )
200 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
201 self.notify_transactions()
202 self.update_account_selector()
204 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
205 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
206 self.password_menu.setEnabled(not self.wallet.is_watching_only())
207 self.seed_menu.setEnabled(self.wallet.has_seed())
208 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
209 self.import_menu.setEnabled(self.wallet.can_import())
211 self.update_lock_icon()
212 self.update_buttons_on_seed()
213 self.update_console()
215 run_hook('load_wallet', wallet)
218 def update_wallet_format(self):
219 # convert old-format imported keys
220 if self.wallet.imported_keys:
221 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
223 self.wallet.convert_imported_keys(password)
225 self.show_message("error")
228 def open_wallet(self):
229 wallet_folder = self.wallet.storage.path
230 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
234 storage = WalletStorage({'wallet_path': filename})
235 if not storage.file_exists:
236 self.show_message("file not found "+ filename)
239 self.wallet.stop_threads()
242 wallet = Wallet(storage)
243 wallet.start_threads(self.network)
245 self.load_wallet(wallet)
249 def backup_wallet(self):
251 path = self.wallet.storage.path
252 wallet_folder = os.path.dirname(path)
253 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
257 new_path = os.path.join(wallet_folder, filename)
260 shutil.copy2(path, new_path)
261 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
262 except (IOError, os.error), reason:
263 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
266 def new_wallet(self):
269 wallet_folder = os.path.dirname(self.wallet.storage.path)
270 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
273 filename = os.path.join(wallet_folder, filename)
275 storage = WalletStorage({'wallet_path': filename})
276 if storage.file_exists:
277 QMessageBox.critical(None, "Error", _("File exists"))
280 wizard = installwizard.InstallWizard(self.config, self.network, storage)
281 wallet = wizard.run('new')
283 self.load_wallet(wallet)
287 def init_menubar(self):
290 file_menu = menubar.addMenu(_("&File"))
291 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
292 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
293 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
294 file_menu.addAction(_("&Quit"), self.close)
296 wallet_menu = menubar.addMenu(_("&Wallet"))
297 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
298 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
300 wallet_menu.addSeparator()
302 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
303 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
304 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
306 wallet_menu.addSeparator()
307 labels_menu = wallet_menu.addMenu(_("&Labels"))
308 labels_menu.addAction(_("&Import"), self.do_import_labels)
309 labels_menu.addAction(_("&Export"), self.do_export_labels)
311 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
312 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
313 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
314 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
315 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
317 tools_menu = menubar.addMenu(_("&Tools"))
319 # Settings / Preferences are all reserved keywords in OSX using this as work around
320 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
321 tools_menu.addAction(_("&Network"), self.run_network_dialog)
322 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
323 tools_menu.addSeparator()
324 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
325 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
326 tools_menu.addSeparator()
328 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
329 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
330 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
332 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
333 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
334 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
335 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
337 help_menu = menubar.addMenu(_("&Help"))
338 help_menu.addAction(_("&About"), self.show_about)
339 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
340 help_menu.addSeparator()
341 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
342 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
344 self.setMenuBar(menubar)
346 def show_about(self):
347 QMessageBox.about(self, "Electrum",
348 _("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."))
350 def show_report_bug(self):
351 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
352 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
355 def notify_transactions(self):
356 if not self.network or not self.network.is_connected():
359 print_error("Notifying GUI")
360 if len(self.network.pending_transactions_for_notifications) > 0:
361 # Combine the transactions if there are more then three
362 tx_amount = len(self.network.pending_transactions_for_notifications)
365 for tx in self.network.pending_transactions_for_notifications:
366 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
370 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
371 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
373 self.network.pending_transactions_for_notifications = []
375 for tx in self.network.pending_transactions_for_notifications:
377 self.network.pending_transactions_for_notifications.remove(tx)
378 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
380 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
382 def notify(self, message):
383 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
387 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
388 def getOpenFileName(self, title, filter = ""):
389 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
390 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
391 if fileName and directory != os.path.dirname(fileName):
392 self.config.set_key('io_dir', os.path.dirname(fileName), True)
395 def getSaveFileName(self, title, filename, filter = ""):
396 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
397 path = os.path.join( directory, filename )
398 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
399 if fileName and directory != os.path.dirname(fileName):
400 self.config.set_key('io_dir', os.path.dirname(fileName), True)
404 QMainWindow.close(self)
405 run_hook('close_main_window')
407 def connect_slots(self, sender):
408 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
409 self.previous_payto_e=''
411 def timer_actions(self):
412 if self.need_update.is_set():
414 self.need_update.clear()
415 run_hook('timer_actions')
417 def format_amount(self, x, is_diff=False, whitespaces=False):
418 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
421 def get_decimal_point(self):
422 return self.decimal_point
426 assert self.decimal_point in [5,8]
427 return "BTC" if self.decimal_point == 8 else "mBTC"
430 def update_status(self):
431 if self.network is None or not self.network.is_running():
433 icon = QIcon(":icons/status_disconnected.png")
435 elif self.network.is_connected():
436 if not self.wallet.up_to_date:
437 text = _("Synchronizing...")
438 icon = QIcon(":icons/status_waiting.png")
439 elif self.network.server_lag > 1:
440 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
441 icon = QIcon(":icons/status_lagging.png")
443 c, u = self.wallet.get_account_balance(self.current_account)
444 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
445 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
447 # append fiat balance and price from exchange rate plugin
449 run_hook('get_fiat_status_text', c+u, r)
454 self.tray.setToolTip(text)
455 icon = QIcon(":icons/status_connected.png")
457 text = _("Not connected")
458 icon = QIcon(":icons/status_disconnected.png")
460 self.balance_label.setText(text)
461 self.status_button.setIcon( icon )
464 def update_wallet(self):
466 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
467 self.update_history_tab()
468 self.update_receive_tab()
469 self.update_contacts_tab()
470 self.update_completions()
473 def create_history_tab(self):
474 self.history_list = l = MyTreeWidget(self)
476 for i,width in enumerate(self.column_widths['history']):
477 l.setColumnWidth(i, width)
478 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
479 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
480 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
482 l.customContextMenuRequested.connect(self.create_history_menu)
486 def create_history_menu(self, position):
487 self.history_list.selectedIndexes()
488 item = self.history_list.currentItem()
489 be = self.config.get('block_explorer', 'Blockchain.info')
490 if be == 'Blockchain.info':
491 block_explorer = 'https://blockchain.info/tx/'
492 elif be == 'Blockr.io':
493 block_explorer = 'https://blockr.io/tx/info/'
494 elif be == 'Insight.is':
495 block_explorer = 'http://live.insight.is/tx/'
497 tx_hash = str(item.data(0, Qt.UserRole).toString())
498 if not tx_hash: return
500 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
501 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
502 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
503 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
504 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
507 def show_transaction(self, tx):
508 import transaction_dialog
509 d = transaction_dialog.TxDialog(tx, self)
512 def tx_label_clicked(self, item, column):
513 if column==2 and item.isSelected():
515 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
516 self.history_list.editItem( item, column )
517 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
520 def tx_label_changed(self, item, column):
524 tx_hash = str(item.data(0, Qt.UserRole).toString())
525 tx = self.wallet.transactions.get(tx_hash)
526 text = unicode( item.text(2) )
527 self.wallet.set_label(tx_hash, text)
529 item.setForeground(2, QBrush(QColor('black')))
531 text = self.wallet.get_default_label(tx_hash)
532 item.setText(2, text)
533 item.setForeground(2, QBrush(QColor('gray')))
537 def edit_label(self, is_recv):
538 l = self.receive_list if is_recv else self.contacts_list
539 item = l.currentItem()
540 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
541 l.editItem( item, 1 )
542 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
546 def address_label_clicked(self, item, column, l, column_addr, column_label):
547 if column == column_label and item.isSelected():
548 is_editable = item.data(0, 32).toBool()
551 addr = unicode( item.text(column_addr) )
552 label = unicode( item.text(column_label) )
553 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
554 l.editItem( item, column )
555 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 def address_label_changed(self, item, column, l, column_addr, column_label):
559 if column == column_label:
560 addr = unicode( item.text(column_addr) )
561 text = unicode( item.text(column_label) )
562 is_editable = item.data(0, 32).toBool()
566 changed = self.wallet.set_label(addr, text)
568 self.update_history_tab()
569 self.update_completions()
571 self.current_item_changed(item)
573 run_hook('item_changed', item, column)
576 def current_item_changed(self, a):
577 run_hook('current_item_changed', a)
581 def update_history_tab(self):
583 self.history_list.clear()
584 for item in self.wallet.get_tx_history(self.current_account):
585 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
586 time_str = _("unknown")
589 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
591 time_str = _("error")
594 time_str = 'unverified'
595 icon = QIcon(":icons/unconfirmed.png")
598 icon = QIcon(":icons/unconfirmed.png")
600 icon = QIcon(":icons/clock%d.png"%conf)
602 icon = QIcon(":icons/confirmed.png")
604 if value is not None:
605 v_str = self.format_amount(value, True, whitespaces=True)
609 balance_str = self.format_amount(balance, whitespaces=True)
612 label, is_default_label = self.wallet.get_label(tx_hash)
614 label = _('Pruned transaction outputs')
615 is_default_label = False
617 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
618 item.setFont(2, QFont(MONOSPACE_FONT))
619 item.setFont(3, QFont(MONOSPACE_FONT))
620 item.setFont(4, QFont(MONOSPACE_FONT))
622 item.setForeground(3, QBrush(QColor("#BC1E1E")))
624 item.setData(0, Qt.UserRole, tx_hash)
625 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
627 item.setForeground(2, QBrush(QColor('grey')))
629 item.setIcon(0, icon)
630 self.history_list.insertTopLevelItem(0,item)
633 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
634 run_hook('history_tab_update')
637 def create_send_tab(self):
640 grid = QGridLayout(w)
642 grid.setColumnMinimumWidth(3,300)
643 grid.setColumnStretch(5,1)
644 grid.setRowStretch(8, 1)
646 from paytoedit import PayToEdit
647 self.amount_e = AmountEdit(self.get_decimal_point)
648 self.payto_e = PayToEdit(self.amount_e)
649 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)'))
650 grid.addWidget(QLabel(_('Pay to')), 1, 0)
651 grid.addWidget(self.payto_e, 1, 1, 1, 3)
652 grid.addWidget(self.payto_help, 1, 4)
654 completer = QCompleter()
655 completer.setCaseSensitivity(False)
656 self.payto_e.setCompleter(completer)
657 completer.setModel(self.completions)
659 self.message_e = MyLineEdit()
660 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.'))
661 grid.addWidget(QLabel(_('Description')), 2, 0)
662 grid.addWidget(self.message_e, 2, 1, 1, 3)
663 grid.addWidget(self.message_help, 2, 4)
665 self.from_label = QLabel(_('From'))
666 grid.addWidget(self.from_label, 3, 0)
667 self.from_list = MyTreeWidget(self)
668 self.from_list.setColumnCount(2)
669 self.from_list.setColumnWidth(0, 350)
670 self.from_list.setColumnWidth(1, 50)
671 self.from_list.setHeaderHidden(True)
672 self.from_list.setMaximumHeight(80)
673 self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
674 self.from_list.customContextMenuRequested.connect(self.from_list_menu)
675 grid.addWidget(self.from_list, 3, 1, 1, 3)
676 self.set_pay_from([])
678 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
679 + _('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.') \
680 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
681 grid.addWidget(QLabel(_('Amount')), 4, 0)
682 grid.addWidget(self.amount_e, 4, 1, 1, 2)
683 grid.addWidget(self.amount_help, 4, 3)
685 self.fee_e = AmountEdit(self.get_decimal_point)
686 grid.addWidget(QLabel(_('Fee')), 5, 0)
687 grid.addWidget(self.fee_e, 5, 1, 1, 2)
688 grid.addWidget(HelpButton(
689 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
690 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
691 + _('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)
693 run_hook('exchange_rate_button', grid)
695 self.send_button = EnterButton(_("Send"), self.do_send)
696 grid.addWidget(self.send_button, 6, 1)
698 b = EnterButton(_("Clear"), self.do_clear)
699 grid.addWidget(b, 6, 2)
701 self.payto_sig = QLabel('')
702 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
704 #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
705 #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
708 def entry_changed( is_fee ):
709 self.funds_error = False
711 if self.amount_e.is_shortcut:
712 self.amount_e.is_shortcut = False
713 sendable = self.get_sendable_balance()
714 # there is only one output because we are completely spending inputs
715 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
716 fee = self.wallet.estimated_fee(inputs, 1)
718 self.amount_e.setText( self.format_amount(amount) )
719 self.fee_e.setText( self.format_amount( fee ) )
722 amount = self.amount_e.get_amount()
723 fee = self.fee_e.get_amount()
725 if not is_fee: fee = None
728 # assume that there will be 2 outputs (one for change)
729 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
731 self.fee_e.setText( self.format_amount( fee ) )
734 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
738 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
739 self.funds_error = True
740 text = _( "Not enough funds" )
741 c, u = self.wallet.get_frozen_balance()
742 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
744 self.statusBar().showMessage(text)
745 self.amount_e.setPalette(palette)
746 self.fee_e.setPalette(palette)
748 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
749 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
751 run_hook('create_send_tab', grid)
754 def from_list_delete(self, item):
755 i = self.from_list.indexOfTopLevelItem(item)
757 self.redraw_from_list()
759 def from_list_menu(self, position):
760 item = self.from_list.itemAt(position)
762 menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
763 menu.exec_(self.from_list.viewport().mapToGlobal(position))
765 def set_pay_from(self, domain = None):
766 self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
767 self.redraw_from_list()
769 def redraw_from_list(self):
770 self.from_list.clear()
771 self.from_label.setHidden(len(self.pay_from) == 0)
772 self.from_list.setHidden(len(self.pay_from) == 0)
775 h = x.get('prevout_hash')
776 return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
778 for item in self.pay_from:
779 self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
781 def update_completions(self):
783 for addr,label in self.wallet.labels.items():
784 if addr in self.wallet.addressbook:
785 l.append( label + ' <' + addr + '>')
787 run_hook('update_completions', l)
788 self.completions.setStringList(l)
792 return lambda s, *args: s.do_protect(func, args)
796 label = unicode( self.message_e.text() )
798 if self.gui_object.payment_request:
799 outputs = self.gui_object.payment_request.outputs
800 amount = self.gui_object.payment_request.get_amount()
802 outputs = self.payto_e.get_outputs()
803 amount = sum(map(lambda x:x[1], outputs))
806 fee = self.fee_e.get_amount()
808 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
811 confirm_amount = self.config.get('confirm_amount', 100000000)
812 if amount >= confirm_amount:
813 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
816 confirm_fee = self.config.get('confirm_fee', 100000)
817 if fee >= confirm_fee:
818 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()}):
821 self.send_tx(outputs, fee, label)
826 def send_tx(self, outputs, fee, label, password):
827 self.send_button.setDisabled(True)
829 # first, create an unsigned tx
830 coins = self.get_coins()
832 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
834 except Exception as e:
835 traceback.print_exc(file=sys.stdout)
836 self.show_message(str(e))
837 self.send_button.setDisabled(False)
840 # call hook to see if plugin needs gui interaction
841 run_hook('send_tx', tx)
847 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
848 self.wallet.sign_transaction(tx, keypairs, password)
849 return tx, fee, label
851 def sign_done(tx, fee, label):
853 self.show_message(tx.error)
854 self.send_button.setDisabled(False)
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'))
858 self.send_button.setDisabled(False)
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)
866 self.send_button.setDisabled(False)
869 self.broadcast_transaction(tx)
871 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
872 self.waiting_dialog.start()
876 def broadcast_transaction(self, tx):
878 def broadcast_thread():
879 if self.gui_object.payment_request:
880 refund_address = self.wallet.addresses()[0]
881 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
882 self.gui_object.payment_request = None
884 status, msg = self.wallet.sendtx(tx)
887 def broadcast_done(status, msg):
889 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
892 QMessageBox.warning(self, _('Error'), msg, _('OK'))
893 self.send_button.setDisabled(False)
895 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
896 self.waiting_dialog.start()
900 def prepare_for_payment_request(self):
901 self.tabs.setCurrentIndex(1)
902 for e in [self.payto_e, self.amount_e, self.message_e]:
904 for h in [self.payto_help, self.amount_help, self.message_help]:
906 self.payto_e.setText(_("please wait..."))
909 def payment_request_ok(self):
910 pr = self.gui_object.payment_request
911 self.payto_help.show()
912 self.payto_help.set_alt(pr.status)
913 self.payto_e.setGreen()
914 self.payto_e.setText(pr.domain)
915 self.amount_e.setText(self.format_amount(pr.get_amount()))
916 self.message_e.setText(pr.memo)
918 def payment_request_error(self):
920 self.show_message(self.gui_object.payment_request.error)
921 self.gui_object.payment_request = None
923 def set_send(self, address, amount, label, message):
925 if label and self.wallet.labels.get(address) != label:
926 if self.question('Give label "%s" to address %s ?'%(label,address)):
927 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
928 self.wallet.addressbook.append(address)
929 self.wallet.set_label(address, label)
931 self.tabs.setCurrentIndex(1)
932 label = self.wallet.labels.get(address)
933 m_addr = label + ' <'+ address +'>' if label else address
934 self.payto_e.setText(m_addr)
936 self.message_e.setText(message)
938 self.amount_e.setText(amount)
942 self.payto_sig.setVisible(False)
943 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
947 for h in [self.payto_help, self.amount_help, self.message_help]:
950 self.payto_help.set_alt(None)
952 self.set_pay_from([])
957 def set_addrs_frozen(self,addrs,freeze):
959 if not addr: continue
960 if addr in self.wallet.frozen_addresses and not freeze:
961 self.wallet.unfreeze(addr)
962 elif addr not in self.wallet.frozen_addresses and freeze:
963 self.wallet.freeze(addr)
964 self.update_receive_tab()
968 def create_list_tab(self, headers):
969 "generic tab creation method"
970 l = MyTreeWidget(self)
971 l.setColumnCount( len(headers) )
972 l.setHeaderLabels( headers )
982 vbox.addWidget(buttons)
987 buttons.setLayout(hbox)
992 def create_receive_tab(self):
993 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
994 l.setContextMenuPolicy(Qt.CustomContextMenu)
995 l.customContextMenuRequested.connect(self.create_receive_menu)
996 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
997 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
998 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
999 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1000 self.receive_list = l
1001 self.receive_buttons_hbox = hbox
1008 def save_column_widths(self):
1009 self.column_widths["receive"] = []
1010 for i in range(self.receive_list.columnCount() -1):
1011 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1013 self.column_widths["history"] = []
1014 for i in range(self.history_list.columnCount() - 1):
1015 self.column_widths["history"].append(self.history_list.columnWidth(i))
1017 self.column_widths["contacts"] = []
1018 for i in range(self.contacts_list.columnCount() - 1):
1019 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1021 self.config.set_key("column_widths_2", self.column_widths, True)
1024 def create_contacts_tab(self):
1025 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1026 l.setContextMenuPolicy(Qt.CustomContextMenu)
1027 l.customContextMenuRequested.connect(self.create_contact_menu)
1028 for i,width in enumerate(self.column_widths['contacts']):
1029 l.setColumnWidth(i, width)
1031 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1032 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1033 self.contacts_list = l
1034 self.contacts_buttons_hbox = hbox
1039 def delete_imported_key(self, addr):
1040 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1041 self.wallet.delete_imported_key(addr)
1042 self.update_receive_tab()
1043 self.update_history_tab()
1045 def edit_account_label(self, k):
1046 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1048 label = unicode(text)
1049 self.wallet.set_label(k,label)
1050 self.update_receive_tab()
1052 def account_set_expanded(self, item, k, b):
1054 self.accounts_expanded[k] = b
1056 def create_account_menu(self, position, k, item):
1058 if item.isExpanded():
1059 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1061 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1062 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1063 if self.wallet.seed_version > 4:
1064 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1065 if self.wallet.account_is_pending(k):
1066 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1067 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1069 def delete_pending_account(self, k):
1070 self.wallet.delete_pending_account(k)
1071 self.update_receive_tab()
1073 def create_receive_menu(self, position):
1074 # fixme: this function apparently has a side effect.
1075 # if it is not called the menu pops up several times
1076 #self.receive_list.selectedIndexes()
1078 selected = self.receive_list.selectedItems()
1079 multi_select = len(selected) > 1
1080 addrs = [unicode(item.text(0)) for item in selected]
1081 if not multi_select:
1082 item = self.receive_list.itemAt(position)
1086 if not is_valid(addr):
1087 k = str(item.data(0,32).toString())
1089 self.create_account_menu(position, k, item)
1091 item.setExpanded(not item.isExpanded())
1095 if not multi_select:
1096 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1097 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1098 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1099 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1100 if not self.wallet.is_watching_only():
1101 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1102 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1103 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1104 if self.wallet.is_imported(addr):
1105 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1107 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1108 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1109 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1110 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1112 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1113 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1115 run_hook('receive_menu', menu, addrs)
1116 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1119 def get_sendable_balance(self):
1120 return sum(map(lambda x:x['value'], self.get_coins()))
1123 def get_coins(self):
1125 return self.pay_from
1127 domain = self.wallet.get_account_addresses(self.current_account)
1128 for i in self.wallet.frozen_addresses:
1129 if i in domain: domain.remove(i)
1130 return self.wallet.get_unspent_coins(domain)
1133 def send_from_addresses(self, addrs):
1134 self.set_pay_from( addrs )
1135 self.tabs.setCurrentIndex(1)
1138 def payto(self, addr):
1140 label = self.wallet.labels.get(addr)
1141 m_addr = label + ' <' + addr + '>' if label else addr
1142 self.tabs.setCurrentIndex(1)
1143 self.payto_e.setText(m_addr)
1144 self.amount_e.setFocus()
1147 def delete_contact(self, x):
1148 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1149 self.wallet.delete_contact(x)
1150 self.wallet.set_label(x, None)
1151 self.update_history_tab()
1152 self.update_contacts_tab()
1153 self.update_completions()
1156 def create_contact_menu(self, position):
1157 item = self.contacts_list.itemAt(position)
1160 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1162 addr = unicode(item.text(0))
1163 label = unicode(item.text(1))
1164 is_editable = item.data(0,32).toBool()
1165 payto_addr = item.data(0,33).toString()
1166 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1167 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1168 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1170 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1171 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1173 run_hook('create_contact_menu', menu, item)
1174 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1177 def update_receive_item(self, item):
1178 item.setFont(0, QFont(MONOSPACE_FONT))
1179 address = str(item.data(0,0).toString())
1180 label = self.wallet.labels.get(address,'')
1181 item.setData(1,0,label)
1182 item.setData(0,32, True) # is editable
1184 run_hook('update_receive_item', address, item)
1186 if not self.wallet.is_mine(address): return
1188 c, u = self.wallet.get_addr_balance(address)
1189 balance = self.format_amount(c + u)
1190 item.setData(2,0,balance)
1192 if address in self.wallet.frozen_addresses:
1193 item.setBackgroundColor(0, QColor('lightblue'))
1196 def update_receive_tab(self):
1197 l = self.receive_list
1198 # extend the syntax for consistency
1199 l.addChild = l.addTopLevelItem
1200 l.insertChild = l.insertTopLevelItem
1203 for i,width in enumerate(self.column_widths['receive']):
1204 l.setColumnWidth(i, width)
1206 accounts = self.wallet.get_accounts()
1207 if self.current_account is None:
1208 account_items = sorted(accounts.items())
1210 account_items = [(self.current_account, accounts.get(self.current_account))]
1213 for k, account in account_items:
1215 if len(accounts) > 1:
1216 name = self.wallet.get_account_name(k)
1217 c,u = self.wallet.get_account_balance(k)
1218 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1219 l.addTopLevelItem(account_item)
1220 account_item.setExpanded(self.accounts_expanded.get(k, True))
1221 account_item.setData(0, 32, k)
1225 sequences = [0,1] if account.has_change() else [0]
1226 for is_change in sequences:
1227 if len(sequences) > 1:
1228 name = _("Receiving") if not is_change else _("Change")
1229 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1230 account_item.addChild(seq_item)
1232 seq_item.setExpanded(True)
1234 seq_item = account_item
1236 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1242 for address in account.get_addresses(is_change):
1244 num, is_used = self.wallet.is_used(address)
1247 if gap > self.wallet.gap_limit:
1252 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1253 self.update_receive_item(item)
1255 item.setBackgroundColor(1, QColor('red'))
1259 seq_item.insertChild(0,used_item)
1261 used_item.addChild(item)
1263 seq_item.addChild(item)
1265 # we use column 1 because column 0 may be hidden
1266 l.setCurrentItem(l.topLevelItem(0),1)
1269 def update_contacts_tab(self):
1270 l = self.contacts_list
1273 for address in self.wallet.addressbook:
1274 label = self.wallet.labels.get(address,'')
1275 n = self.wallet.get_num_tx(address)
1276 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1277 item.setFont(0, QFont(MONOSPACE_FONT))
1278 # 32 = label can be edited (bool)
1279 item.setData(0,32, True)
1281 item.setData(0,33, address)
1282 l.addTopLevelItem(item)
1284 run_hook('update_contacts_tab', l)
1285 l.setCurrentItem(l.topLevelItem(0))
1289 def create_console_tab(self):
1290 from console import Console
1291 self.console = console = Console()
1295 def update_console(self):
1296 console = self.console
1297 console.history = self.config.get("console-history",[])
1298 console.history_index = len(console.history)
1300 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1301 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1303 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1305 def mkfunc(f, method):
1306 return lambda *args: apply( f, (method, args, self.password_dialog ))
1308 if m[0]=='_' or m in ['network','wallet']: continue
1309 methods[m] = mkfunc(c._run, m)
1311 console.updateNamespace(methods)
1314 def change_account(self,s):
1315 if s == _("All accounts"):
1316 self.current_account = None
1318 accounts = self.wallet.get_account_names()
1319 for k, v in accounts.items():
1321 self.current_account = k
1322 self.update_history_tab()
1323 self.update_status()
1324 self.update_receive_tab()
1326 def create_status_bar(self):
1329 sb.setFixedHeight(35)
1330 qtVersion = qVersion()
1332 self.balance_label = QLabel("")
1333 sb.addWidget(self.balance_label)
1335 from version_getter import UpdateLabel
1336 self.updatelabel = UpdateLabel(self.config, sb)
1338 self.account_selector = QComboBox()
1339 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1340 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1341 sb.addPermanentWidget(self.account_selector)
1343 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1344 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1346 self.lock_icon = QIcon()
1347 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1348 sb.addPermanentWidget( self.password_button )
1350 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1351 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1352 sb.addPermanentWidget( self.seed_button )
1353 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1354 sb.addPermanentWidget( self.status_button )
1356 run_hook('create_status_bar', (sb,))
1358 self.setStatusBar(sb)
1361 def update_lock_icon(self):
1362 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1363 self.password_button.setIcon( icon )
1366 def update_buttons_on_seed(self):
1367 if self.wallet.has_seed():
1368 self.seed_button.show()
1370 self.seed_button.hide()
1372 if not self.wallet.is_watching_only():
1373 self.password_button.show()
1374 self.send_button.setText(_("Send"))
1376 self.password_button.hide()
1377 self.send_button.setText(_("Create unsigned transaction"))
1380 def change_password_dialog(self):
1381 from password_dialog import PasswordDialog
1382 d = PasswordDialog(self.wallet, self)
1384 self.update_lock_icon()
1387 def new_contact_dialog(self):
1390 d.setWindowTitle(_("New Contact"))
1391 vbox = QVBoxLayout(d)
1392 vbox.addWidget(QLabel(_('New Contact')+':'))
1394 grid = QGridLayout()
1397 grid.addWidget(QLabel(_("Address")), 1, 0)
1398 grid.addWidget(line1, 1, 1)
1399 grid.addWidget(QLabel(_("Name")), 2, 0)
1400 grid.addWidget(line2, 2, 1)
1402 vbox.addLayout(grid)
1403 vbox.addLayout(ok_cancel_buttons(d))
1408 address = str(line1.text())
1409 label = unicode(line2.text())
1411 if not is_valid(address):
1412 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1415 self.wallet.add_contact(address)
1417 self.wallet.set_label(address, label)
1419 self.update_contacts_tab()
1420 self.update_history_tab()
1421 self.update_completions()
1422 self.tabs.setCurrentIndex(3)
1426 def new_account_dialog(self, password):
1428 dialog = QDialog(self)
1430 dialog.setWindowTitle(_("New Account"))
1432 vbox = QVBoxLayout()
1433 vbox.addWidget(QLabel(_('Account name')+':'))
1436 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1437 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1442 vbox.addLayout(ok_cancel_buttons(dialog))
1443 dialog.setLayout(vbox)
1447 name = str(e.text())
1450 self.wallet.create_pending_account(name, password)
1451 self.update_receive_tab()
1452 self.tabs.setCurrentIndex(2)
1457 def show_master_public_keys(self):
1459 dialog = QDialog(self)
1461 dialog.setWindowTitle(_("Master Public Keys"))
1463 main_layout = QGridLayout()
1464 mpk_dict = self.wallet.get_master_public_keys()
1466 for key, value in mpk_dict.items():
1467 main_layout.addWidget(QLabel(key), i, 0)
1468 mpk_text = QTextEdit()
1469 mpk_text.setReadOnly(True)
1470 mpk_text.setMaximumHeight(170)
1471 mpk_text.setText(value)
1472 main_layout.addWidget(mpk_text, i + 1, 0)
1475 vbox = QVBoxLayout()
1476 vbox.addLayout(main_layout)
1477 vbox.addLayout(close_button(dialog))
1479 dialog.setLayout(vbox)
1484 def show_seed_dialog(self, password):
1485 if not self.wallet.has_seed():
1486 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1490 mnemonic = self.wallet.get_mnemonic(password)
1492 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1494 from seed_dialog import SeedDialog
1495 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1500 def show_qrcode(self, data, title = _("QR code")):
1504 d.setWindowTitle(title)
1505 d.setMinimumSize(270, 300)
1506 vbox = QVBoxLayout()
1507 qrw = QRCodeWidget(data)
1508 vbox.addWidget(qrw, 1)
1509 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1510 hbox = QHBoxLayout()
1513 filename = os.path.join(self.config.path, "qrcode.bmp")
1516 bmp.save_qrcode(qrw.qr, filename)
1517 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1519 def copy_to_clipboard():
1520 bmp.save_qrcode(qrw.qr, filename)
1521 self.app.clipboard().setImage(QImage(filename))
1522 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1524 b = QPushButton(_("Copy"))
1526 b.clicked.connect(copy_to_clipboard)
1528 b = QPushButton(_("Save"))
1530 b.clicked.connect(print_qr)
1532 b = QPushButton(_("Close"))
1534 b.clicked.connect(d.accept)
1537 vbox.addLayout(hbox)
1542 def do_protect(self, func, args):
1543 if self.wallet.use_encryption:
1544 password = self.password_dialog()
1550 if args != (False,):
1551 args = (self,) + args + (password,)
1553 args = (self,password)
1557 def show_public_keys(self, address):
1558 if not address: return
1560 pubkey_list = self.wallet.get_public_keys(address)
1561 except Exception as e:
1562 traceback.print_exc(file=sys.stdout)
1563 self.show_message(str(e))
1567 d.setMinimumSize(600, 200)
1569 vbox = QVBoxLayout()
1570 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1571 vbox.addWidget( QLabel(_("Public key") + ':'))
1573 keys.setReadOnly(True)
1574 keys.setText('\n'.join(pubkey_list))
1575 vbox.addWidget(keys)
1576 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1577 vbox.addLayout(close_button(d))
1582 def show_private_key(self, address, password):
1583 if not address: return
1585 pk_list = self.wallet.get_private_key(address, password)
1586 except Exception as e:
1587 traceback.print_exc(file=sys.stdout)
1588 self.show_message(str(e))
1592 d.setMinimumSize(600, 200)
1594 vbox = QVBoxLayout()
1595 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1596 vbox.addWidget( QLabel(_("Private key") + ':'))
1598 keys.setReadOnly(True)
1599 keys.setText('\n'.join(pk_list))
1600 vbox.addWidget(keys)
1601 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1602 vbox.addLayout(close_button(d))
1608 def do_sign(self, address, message, signature, password):
1609 message = unicode(message.toPlainText())
1610 message = message.encode('utf-8')
1612 sig = self.wallet.sign_message(str(address.text()), message, password)
1613 signature.setText(sig)
1614 except Exception as e:
1615 self.show_message(str(e))
1617 def do_verify(self, address, message, signature):
1618 message = unicode(message.toPlainText())
1619 message = message.encode('utf-8')
1620 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1621 self.show_message(_("Signature verified"))
1623 self.show_message(_("Error: wrong signature"))
1626 def sign_verify_message(self, address=''):
1629 d.setWindowTitle(_('Sign/verify Message'))
1630 d.setMinimumSize(410, 290)
1632 layout = QGridLayout(d)
1634 message_e = QTextEdit()
1635 layout.addWidget(QLabel(_('Message')), 1, 0)
1636 layout.addWidget(message_e, 1, 1)
1637 layout.setRowStretch(2,3)
1639 address_e = QLineEdit()
1640 address_e.setText(address)
1641 layout.addWidget(QLabel(_('Address')), 2, 0)
1642 layout.addWidget(address_e, 2, 1)
1644 signature_e = QTextEdit()
1645 layout.addWidget(QLabel(_('Signature')), 3, 0)
1646 layout.addWidget(signature_e, 3, 1)
1647 layout.setRowStretch(3,1)
1649 hbox = QHBoxLayout()
1651 b = QPushButton(_("Sign"))
1652 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1655 b = QPushButton(_("Verify"))
1656 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1659 b = QPushButton(_("Close"))
1660 b.clicked.connect(d.accept)
1662 layout.addLayout(hbox, 4, 1)
1667 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1669 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1670 message_e.setText(decrypted)
1671 except Exception as e:
1672 self.show_message(str(e))
1675 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1676 message = unicode(message_e.toPlainText())
1677 message = message.encode('utf-8')
1679 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1680 encrypted_e.setText(encrypted)
1681 except Exception as e:
1682 self.show_message(str(e))
1686 def encrypt_message(self, address = ''):
1689 d.setWindowTitle(_('Encrypt/decrypt Message'))
1690 d.setMinimumSize(610, 490)
1692 layout = QGridLayout(d)
1694 message_e = QTextEdit()
1695 layout.addWidget(QLabel(_('Message')), 1, 0)
1696 layout.addWidget(message_e, 1, 1)
1697 layout.setRowStretch(2,3)
1699 pubkey_e = QLineEdit()
1701 pubkey = self.wallet.getpubkeys(address)[0]
1702 pubkey_e.setText(pubkey)
1703 layout.addWidget(QLabel(_('Public key')), 2, 0)
1704 layout.addWidget(pubkey_e, 2, 1)
1706 encrypted_e = QTextEdit()
1707 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1708 layout.addWidget(encrypted_e, 3, 1)
1709 layout.setRowStretch(3,1)
1711 hbox = QHBoxLayout()
1712 b = QPushButton(_("Encrypt"))
1713 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1716 b = QPushButton(_("Decrypt"))
1717 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1720 b = QPushButton(_("Close"))
1721 b.clicked.connect(d.accept)
1724 layout.addLayout(hbox, 4, 1)
1728 def question(self, msg):
1729 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1731 def show_message(self, msg):
1732 QMessageBox.information(self, _('Message'), msg, _('OK'))
1734 def password_dialog(self, msg=None):
1737 d.setWindowTitle(_("Enter Password"))
1742 vbox = QVBoxLayout()
1744 msg = _('Please enter your password')
1745 vbox.addWidget(QLabel(msg))
1747 grid = QGridLayout()
1749 grid.addWidget(QLabel(_('Password')), 1, 0)
1750 grid.addWidget(pw, 1, 1)
1751 vbox.addLayout(grid)
1753 vbox.addLayout(ok_cancel_buttons(d))
1756 run_hook('password_dialog', pw, grid, 1)
1757 if not d.exec_(): return
1758 return unicode(pw.text())
1767 def tx_from_text(self, txt):
1768 "json or raw hexadecimal"
1771 tx = Transaction(txt)
1777 tx_dict = json.loads(str(txt))
1778 assert "hex" in tx_dict.keys()
1779 tx = Transaction(tx_dict["hex"])
1780 if tx_dict.has_key("input_info"):
1781 input_info = json.loads(tx_dict['input_info'])
1782 tx.add_input_info(input_info)
1785 traceback.print_exc(file=sys.stdout)
1788 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1792 def read_tx_from_file(self):
1793 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1797 with open(fileName, "r") as f:
1798 file_content = f.read()
1799 except (ValueError, IOError, os.error), reason:
1800 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1802 return self.tx_from_text(file_content)
1806 def sign_raw_transaction(self, tx, input_info, password):
1807 self.wallet.signrawtransaction(tx, input_info, [], password)
1809 def do_process_from_text(self):
1810 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1813 tx = self.tx_from_text(text)
1815 self.show_transaction(tx)
1817 def do_process_from_file(self):
1818 tx = self.read_tx_from_file()
1820 self.show_transaction(tx)
1822 def do_process_from_txid(self):
1823 from electrum import transaction
1824 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1826 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1828 tx = transaction.Transaction(r)
1830 self.show_transaction(tx)
1832 self.show_message("unknown transaction")
1834 def do_process_from_csvReader(self, csvReader):
1839 for position, row in enumerate(csvReader):
1841 if not is_valid(address):
1842 errors.append((position, address))
1844 amount = Decimal(row[1])
1845 amount = int(100000000*amount)
1846 outputs.append((address, amount))
1847 except (ValueError, IOError, os.error), reason:
1848 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1852 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1853 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1857 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1858 except Exception as e:
1859 self.show_message(str(e))
1862 self.show_transaction(tx)
1864 def do_process_from_csv_file(self):
1865 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1869 with open(fileName, "r") as f:
1870 csvReader = csv.reader(f)
1871 self.do_process_from_csvReader(csvReader)
1872 except (ValueError, IOError, os.error), reason:
1873 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1876 def do_process_from_csv_text(self):
1877 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1878 + _("Format: address, amount. One output per line"), _("Load CSV"))
1881 f = StringIO.StringIO(text)
1882 csvReader = csv.reader(f)
1883 self.do_process_from_csvReader(csvReader)
1888 def export_privkeys_dialog(self, password):
1889 if self.wallet.is_watching_only():
1890 self.show_message(_("This is a watching-only wallet"))
1894 d.setWindowTitle(_('Private keys'))
1895 d.setMinimumSize(850, 300)
1896 vbox = QVBoxLayout(d)
1898 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1899 _("Exposing a single private key can compromise your entire wallet!"),
1900 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1901 vbox.addWidget(QLabel(msg))
1907 defaultname = 'electrum-private-keys.csv'
1908 select_msg = _('Select file to export your private keys to')
1909 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1910 vbox.addLayout(hbox)
1912 h, b = ok_cancel_buttons2(d, _('Export'))
1917 addresses = self.wallet.addresses(True)
1919 def privkeys_thread():
1920 for addr in addresses:
1924 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1925 d.emit(SIGNAL('computing_privkeys'))
1926 d.emit(SIGNAL('show_privkeys'))
1928 def show_privkeys():
1929 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1933 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1934 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1935 threading.Thread(target=privkeys_thread).start()
1941 filename = filename_e.text()
1946 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1947 except (IOError, os.error), reason:
1948 export_error_label = _("Electrum was unable to produce a private key-export.")
1949 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1951 except Exception as e:
1952 self.show_message(str(e))
1955 self.show_message(_("Private keys exported."))
1958 def do_export_privkeys(self, fileName, pklist, is_csv):
1959 with open(fileName, "w+") as f:
1961 transaction = csv.writer(f)
1962 transaction.writerow(["address", "private_key"])
1963 for addr, pk in pklist.items():
1964 transaction.writerow(["%34s"%addr,pk])
1967 f.write(json.dumps(pklist, indent = 4))
1970 def do_import_labels(self):
1971 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1972 if not labelsFile: return
1974 f = open(labelsFile, 'r')
1977 for key, value in json.loads(data).items():
1978 self.wallet.set_label(key, value)
1979 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1980 except (IOError, os.error), reason:
1981 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1984 def do_export_labels(self):
1985 labels = self.wallet.labels
1987 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1989 with open(fileName, 'w+') as f:
1990 json.dump(labels, f)
1991 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1992 except (IOError, os.error), reason:
1993 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1996 def export_history_dialog(self):
1999 d.setWindowTitle(_('Export History'))
2000 d.setMinimumSize(400, 200)
2001 vbox = QVBoxLayout(d)
2003 defaultname = os.path.expanduser('~/electrum-history.csv')
2004 select_msg = _('Select file to export your wallet transactions to')
2006 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2007 vbox.addLayout(hbox)
2011 h, b = ok_cancel_buttons2(d, _('Export'))
2016 filename = filename_e.text()
2021 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2022 except (IOError, os.error), reason:
2023 export_error_label = _("Electrum was unable to produce a transaction export.")
2024 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2027 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2030 def do_export_history(self, wallet, fileName, is_csv):
2031 history = wallet.get_tx_history()
2033 for item in history:
2034 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2036 if timestamp is not None:
2038 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2039 except [RuntimeError, TypeError, NameError] as reason:
2040 time_string = "unknown"
2043 time_string = "unknown"
2045 time_string = "pending"
2047 if value is not None:
2048 value_string = format_satoshis(value, True)
2053 fee_string = format_satoshis(fee, True)
2058 label, is_default_label = wallet.get_label(tx_hash)
2059 label = label.encode('utf-8')
2063 balance_string = format_satoshis(balance, False)
2065 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2067 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2069 with open(fileName, "w+") as f:
2071 transaction = csv.writer(f)
2072 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2074 transaction.writerow(line)
2077 f.write(json.dumps(lines, indent = 4))
2080 def sweep_key_dialog(self):
2082 d.setWindowTitle(_('Sweep private keys'))
2083 d.setMinimumSize(600, 300)
2085 vbox = QVBoxLayout(d)
2086 vbox.addWidget(QLabel(_("Enter private keys")))
2088 keys_e = QTextEdit()
2089 keys_e.setTabChangesFocus(True)
2090 vbox.addWidget(keys_e)
2092 h, address_e = address_field(self.wallet.addresses())
2096 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2097 vbox.addLayout(hbox)
2098 button.setEnabled(False)
2101 addr = str(address_e.text())
2102 if bitcoin.is_address(addr):
2106 pk = str(keys_e.toPlainText()).strip()
2107 if Wallet.is_private_key(pk):
2110 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2111 keys_e.textChanged.connect(f)
2112 address_e.textChanged.connect(f)
2116 fee = self.wallet.fee
2117 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2118 self.show_transaction(tx)
2122 def do_import_privkey(self, password):
2123 if not self.wallet.has_imported_keys():
2124 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2125 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2126 + _('Are you sure you understand what you are doing?'), 3, 4)
2129 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2132 text = str(text).split()
2137 addr = self.wallet.import_key(key, password)
2138 except Exception as e:
2144 addrlist.append(addr)
2146 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2148 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2149 self.update_receive_tab()
2150 self.update_history_tab()
2153 def settings_dialog(self):
2155 d.setWindowTitle(_('Electrum Settings'))
2157 vbox = QVBoxLayout()
2158 grid = QGridLayout()
2159 grid.setColumnStretch(0,1)
2161 nz_label = QLabel(_('Display zeros') + ':')
2162 grid.addWidget(nz_label, 0, 0)
2163 nz_e = AmountEdit(None,True)
2164 nz_e.setText("%d"% self.num_zeros)
2165 grid.addWidget(nz_e, 0, 1)
2166 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2167 grid.addWidget(HelpButton(msg), 0, 2)
2168 if not self.config.is_modifiable('num_zeros'):
2169 for w in [nz_e, nz_label]: w.setEnabled(False)
2171 lang_label=QLabel(_('Language') + ':')
2172 grid.addWidget(lang_label, 1, 0)
2173 lang_combo = QComboBox()
2174 from electrum.i18n import languages
2175 lang_combo.addItems(languages.values())
2177 index = languages.keys().index(self.config.get("language",''))
2180 lang_combo.setCurrentIndex(index)
2181 grid.addWidget(lang_combo, 1, 1)
2182 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2183 if not self.config.is_modifiable('language'):
2184 for w in [lang_combo, lang_label]: w.setEnabled(False)
2187 fee_label = QLabel(_('Transaction fee') + ':')
2188 grid.addWidget(fee_label, 2, 0)
2189 fee_e = AmountEdit(self.get_decimal_point)
2190 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2191 grid.addWidget(fee_e, 2, 1)
2192 msg = _('Fee per kilobyte of transaction.') + ' ' \
2193 + _('Recommended value') + ': ' + self.format_amount(20000)
2194 grid.addWidget(HelpButton(msg), 2, 2)
2195 if not self.config.is_modifiable('fee_per_kb'):
2196 for w in [fee_e, fee_label]: w.setEnabled(False)
2198 units = ['BTC', 'mBTC']
2199 unit_label = QLabel(_('Base unit') + ':')
2200 grid.addWidget(unit_label, 3, 0)
2201 unit_combo = QComboBox()
2202 unit_combo.addItems(units)
2203 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2204 grid.addWidget(unit_combo, 3, 1)
2205 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2206 + '\n1BTC=1000mBTC.\n' \
2207 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2209 usechange_cb = QCheckBox(_('Use change addresses'))
2210 usechange_cb.setChecked(self.wallet.use_change)
2211 grid.addWidget(usechange_cb, 4, 0)
2212 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2213 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2215 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2216 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2217 grid.addWidget(block_ex_label, 5, 0)
2218 block_ex_combo = QComboBox()
2219 block_ex_combo.addItems(block_explorers)
2220 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2221 grid.addWidget(block_ex_combo, 5, 1)
2222 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2224 show_tx = self.config.get('show_before_broadcast', False)
2225 showtx_cb = QCheckBox(_('Show before broadcast'))
2226 showtx_cb.setChecked(show_tx)
2227 grid.addWidget(showtx_cb, 6, 0)
2228 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2230 vbox.addLayout(grid)
2232 vbox.addLayout(ok_cancel_buttons(d))
2236 if not d.exec_(): return
2239 fee = self.fee_e.get_amount()
2241 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2244 self.wallet.set_fee(fee)
2246 nz = unicode(nz_e.text())
2251 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2254 if self.num_zeros != nz:
2256 self.config.set_key('num_zeros', nz, True)
2257 self.update_history_tab()
2258 self.update_receive_tab()
2260 usechange_result = usechange_cb.isChecked()
2261 if self.wallet.use_change != usechange_result:
2262 self.wallet.use_change = usechange_result
2263 self.wallet.storage.put('use_change', self.wallet.use_change)
2265 if showtx_cb.isChecked() != show_tx:
2266 self.config.set_key('show_before_broadcast', not show_tx)
2268 unit_result = units[unit_combo.currentIndex()]
2269 if self.base_unit() != unit_result:
2270 self.decimal_point = 8 if unit_result == 'BTC' else 5
2271 self.config.set_key('decimal_point', self.decimal_point, True)
2272 self.update_history_tab()
2273 self.update_status()
2275 need_restart = False
2277 lang_request = languages.keys()[lang_combo.currentIndex()]
2278 if lang_request != self.config.get('language'):
2279 self.config.set_key("language", lang_request, True)
2282 be_result = block_explorers[block_ex_combo.currentIndex()]
2283 self.config.set_key('block_explorer', be_result, True)
2285 run_hook('close_settings_dialog')
2288 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2291 def run_network_dialog(self):
2292 if not self.network:
2294 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2296 def closeEvent(self, event):
2298 self.config.set_key("is_maximized", self.isMaximized())
2299 if not self.isMaximized():
2301 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2302 self.save_column_widths()
2303 self.config.set_key("console-history", self.console.history[-50:], True)
2304 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2308 def plugins_dialog(self):
2309 from electrum.plugins import plugins
2312 d.setWindowTitle(_('Electrum Plugins'))
2315 vbox = QVBoxLayout(d)
2318 scroll = QScrollArea()
2319 scroll.setEnabled(True)
2320 scroll.setWidgetResizable(True)
2321 scroll.setMinimumSize(400,250)
2322 vbox.addWidget(scroll)
2326 w.setMinimumHeight(len(plugins)*35)
2328 grid = QGridLayout()
2329 grid.setColumnStretch(0,1)
2332 def do_toggle(cb, p, w):
2335 if w: w.setEnabled(r)
2337 def mk_toggle(cb, p, w):
2338 return lambda: do_toggle(cb,p,w)
2340 for i, p in enumerate(plugins):
2342 cb = QCheckBox(p.fullname())
2343 cb.setDisabled(not p.is_available())
2344 cb.setChecked(p.is_enabled())
2345 grid.addWidget(cb, i, 0)
2346 if p.requires_settings():
2347 w = p.settings_widget(self)
2348 w.setEnabled( p.is_enabled() )
2349 grid.addWidget(w, i, 1)
2352 cb.clicked.connect(mk_toggle(cb,p,w))
2353 grid.addWidget(HelpButton(p.description()), i, 2)
2355 print_msg(_("Error: cannot display plugin"), p)
2356 traceback.print_exc(file=sys.stdout)
2357 grid.setRowStretch(i+1,1)
2359 vbox.addLayout(close_button(d))
2364 def show_account_details(self, k):
2365 account = self.wallet.accounts[k]
2368 d.setWindowTitle(_('Account Details'))
2371 vbox = QVBoxLayout(d)
2372 name = self.wallet.get_account_name(k)
2373 label = QLabel('Name: ' + name)
2374 vbox.addWidget(label)
2376 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2378 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2380 vbox.addWidget(QLabel(_('Master Public Key:')))
2383 text.setReadOnly(True)
2384 text.setMaximumHeight(170)
2385 vbox.addWidget(text)
2387 mpk_text = '\n'.join( account.get_master_pubkeys() )
2388 text.setText(mpk_text)
2390 vbox.addLayout(close_button(d))