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 o = '\n'.join(map(lambda x:x[0], outputs))
814 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
817 confirm_fee = self.config.get('confirm_fee', 100000)
818 if fee >= confirm_fee:
819 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()}):
822 self.send_tx(outputs, fee, label)
827 def send_tx(self, outputs, fee, label, password):
828 self.send_button.setDisabled(True)
830 # first, create an unsigned tx
831 coins = self.get_coins()
833 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
835 except Exception as e:
836 traceback.print_exc(file=sys.stdout)
837 self.show_message(str(e))
838 self.send_button.setDisabled(False)
841 # call hook to see if plugin needs gui interaction
842 run_hook('send_tx', tx)
848 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
849 self.wallet.sign_transaction(tx, keypairs, password)
850 return tx, fee, label
852 def sign_done(tx, fee, label):
854 self.show_message(tx.error)
855 self.send_button.setDisabled(False)
857 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
858 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
859 self.send_button.setDisabled(False)
862 self.wallet.set_label(tx.hash(), label)
864 if not tx.is_complete() or self.config.get('show_before_broadcast'):
865 self.show_transaction(tx)
867 self.send_button.setDisabled(False)
870 self.broadcast_transaction(tx)
872 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
873 self.waiting_dialog.start()
877 def broadcast_transaction(self, tx):
879 def broadcast_thread():
880 if self.gui_object.payment_request:
881 refund_address = self.wallet.addresses()[0]
882 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
883 self.gui_object.payment_request = None
885 status, msg = self.wallet.sendtx(tx)
888 def broadcast_done(status, msg):
890 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
893 QMessageBox.warning(self, _('Error'), msg, _('OK'))
894 self.send_button.setDisabled(False)
896 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
897 self.waiting_dialog.start()
901 def prepare_for_payment_request(self):
902 self.tabs.setCurrentIndex(1)
903 for e in [self.payto_e, self.amount_e, self.message_e]:
905 for h in [self.payto_help, self.amount_help, self.message_help]:
907 self.payto_e.setText(_("please wait..."))
910 def payment_request_ok(self):
911 pr = self.gui_object.payment_request
912 self.payto_help.show()
913 self.payto_help.set_alt(pr.status)
914 self.payto_e.setGreen()
915 self.payto_e.setText(pr.domain)
916 self.amount_e.setText(self.format_amount(pr.get_amount()))
917 self.message_e.setText(pr.memo)
919 def payment_request_error(self):
921 self.show_message(self.gui_object.payment_request.error)
922 self.gui_object.payment_request = None
924 def set_send(self, address, amount, label, message):
926 if label and self.wallet.labels.get(address) != label:
927 if self.question('Give label "%s" to address %s ?'%(label,address)):
928 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
929 self.wallet.addressbook.append(address)
930 self.wallet.set_label(address, label)
932 self.tabs.setCurrentIndex(1)
933 label = self.wallet.labels.get(address)
934 m_addr = label + ' <'+ address +'>' if label else address
935 self.payto_e.setText(m_addr)
937 self.message_e.setText(message)
939 self.amount_e.setText(amount)
943 self.payto_sig.setVisible(False)
944 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
948 for h in [self.payto_help, self.amount_help, self.message_help]:
951 self.payto_help.set_alt(None)
953 self.set_pay_from([])
958 def set_addrs_frozen(self,addrs,freeze):
960 if not addr: continue
961 if addr in self.wallet.frozen_addresses and not freeze:
962 self.wallet.unfreeze(addr)
963 elif addr not in self.wallet.frozen_addresses and freeze:
964 self.wallet.freeze(addr)
965 self.update_receive_tab()
969 def create_list_tab(self, headers):
970 "generic tab creation method"
971 l = MyTreeWidget(self)
972 l.setColumnCount( len(headers) )
973 l.setHeaderLabels( headers )
983 vbox.addWidget(buttons)
988 buttons.setLayout(hbox)
993 def create_receive_tab(self):
994 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
995 l.setContextMenuPolicy(Qt.CustomContextMenu)
996 l.customContextMenuRequested.connect(self.create_receive_menu)
997 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
998 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
999 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1000 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1001 self.receive_list = l
1002 self.receive_buttons_hbox = hbox
1009 def save_column_widths(self):
1010 self.column_widths["receive"] = []
1011 for i in range(self.receive_list.columnCount() -1):
1012 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1014 self.column_widths["history"] = []
1015 for i in range(self.history_list.columnCount() - 1):
1016 self.column_widths["history"].append(self.history_list.columnWidth(i))
1018 self.column_widths["contacts"] = []
1019 for i in range(self.contacts_list.columnCount() - 1):
1020 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1022 self.config.set_key("column_widths_2", self.column_widths, True)
1025 def create_contacts_tab(self):
1026 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1027 l.setContextMenuPolicy(Qt.CustomContextMenu)
1028 l.customContextMenuRequested.connect(self.create_contact_menu)
1029 for i,width in enumerate(self.column_widths['contacts']):
1030 l.setColumnWidth(i, width)
1032 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1033 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1034 self.contacts_list = l
1035 self.contacts_buttons_hbox = hbox
1040 def delete_imported_key(self, addr):
1041 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1042 self.wallet.delete_imported_key(addr)
1043 self.update_receive_tab()
1044 self.update_history_tab()
1046 def edit_account_label(self, k):
1047 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1049 label = unicode(text)
1050 self.wallet.set_label(k,label)
1051 self.update_receive_tab()
1053 def account_set_expanded(self, item, k, b):
1055 self.accounts_expanded[k] = b
1057 def create_account_menu(self, position, k, item):
1059 if item.isExpanded():
1060 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1062 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1063 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1064 if self.wallet.seed_version > 4:
1065 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1066 if self.wallet.account_is_pending(k):
1067 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1068 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1070 def delete_pending_account(self, k):
1071 self.wallet.delete_pending_account(k)
1072 self.update_receive_tab()
1074 def create_receive_menu(self, position):
1075 # fixme: this function apparently has a side effect.
1076 # if it is not called the menu pops up several times
1077 #self.receive_list.selectedIndexes()
1079 selected = self.receive_list.selectedItems()
1080 multi_select = len(selected) > 1
1081 addrs = [unicode(item.text(0)) for item in selected]
1082 if not multi_select:
1083 item = self.receive_list.itemAt(position)
1087 if not is_valid(addr):
1088 k = str(item.data(0,32).toString())
1090 self.create_account_menu(position, k, item)
1092 item.setExpanded(not item.isExpanded())
1096 if not multi_select:
1097 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1098 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1099 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1100 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1101 if not self.wallet.is_watching_only():
1102 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1103 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1104 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1105 if self.wallet.is_imported(addr):
1106 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1108 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1109 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1110 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1111 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1113 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1114 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1116 run_hook('receive_menu', menu, addrs)
1117 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1120 def get_sendable_balance(self):
1121 return sum(map(lambda x:x['value'], self.get_coins()))
1124 def get_coins(self):
1126 return self.pay_from
1128 domain = self.wallet.get_account_addresses(self.current_account)
1129 for i in self.wallet.frozen_addresses:
1130 if i in domain: domain.remove(i)
1131 return self.wallet.get_unspent_coins(domain)
1134 def send_from_addresses(self, addrs):
1135 self.set_pay_from( addrs )
1136 self.tabs.setCurrentIndex(1)
1139 def payto(self, addr):
1141 label = self.wallet.labels.get(addr)
1142 m_addr = label + ' <' + addr + '>' if label else addr
1143 self.tabs.setCurrentIndex(1)
1144 self.payto_e.setText(m_addr)
1145 self.amount_e.setFocus()
1148 def delete_contact(self, x):
1149 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1150 self.wallet.delete_contact(x)
1151 self.wallet.set_label(x, None)
1152 self.update_history_tab()
1153 self.update_contacts_tab()
1154 self.update_completions()
1157 def create_contact_menu(self, position):
1158 item = self.contacts_list.itemAt(position)
1161 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1163 addr = unicode(item.text(0))
1164 label = unicode(item.text(1))
1165 is_editable = item.data(0,32).toBool()
1166 payto_addr = item.data(0,33).toString()
1167 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1168 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1169 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1171 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1172 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1174 run_hook('create_contact_menu', menu, item)
1175 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1178 def update_receive_item(self, item):
1179 item.setFont(0, QFont(MONOSPACE_FONT))
1180 address = str(item.data(0,0).toString())
1181 label = self.wallet.labels.get(address,'')
1182 item.setData(1,0,label)
1183 item.setData(0,32, True) # is editable
1185 run_hook('update_receive_item', address, item)
1187 if not self.wallet.is_mine(address): return
1189 c, u = self.wallet.get_addr_balance(address)
1190 balance = self.format_amount(c + u)
1191 item.setData(2,0,balance)
1193 if address in self.wallet.frozen_addresses:
1194 item.setBackgroundColor(0, QColor('lightblue'))
1197 def update_receive_tab(self):
1198 l = self.receive_list
1199 # extend the syntax for consistency
1200 l.addChild = l.addTopLevelItem
1201 l.insertChild = l.insertTopLevelItem
1204 for i,width in enumerate(self.column_widths['receive']):
1205 l.setColumnWidth(i, width)
1207 accounts = self.wallet.get_accounts()
1208 if self.current_account is None:
1209 account_items = sorted(accounts.items())
1211 account_items = [(self.current_account, accounts.get(self.current_account))]
1214 for k, account in account_items:
1216 if len(accounts) > 1:
1217 name = self.wallet.get_account_name(k)
1218 c,u = self.wallet.get_account_balance(k)
1219 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1220 l.addTopLevelItem(account_item)
1221 account_item.setExpanded(self.accounts_expanded.get(k, True))
1222 account_item.setData(0, 32, k)
1226 sequences = [0,1] if account.has_change() else [0]
1227 for is_change in sequences:
1228 if len(sequences) > 1:
1229 name = _("Receiving") if not is_change else _("Change")
1230 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1231 account_item.addChild(seq_item)
1233 seq_item.setExpanded(True)
1235 seq_item = account_item
1237 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1243 for address in account.get_addresses(is_change):
1245 num, is_used = self.wallet.is_used(address)
1248 if gap > self.wallet.gap_limit:
1253 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1254 self.update_receive_item(item)
1256 item.setBackgroundColor(1, QColor('red'))
1260 seq_item.insertChild(0,used_item)
1262 used_item.addChild(item)
1264 seq_item.addChild(item)
1266 # we use column 1 because column 0 may be hidden
1267 l.setCurrentItem(l.topLevelItem(0),1)
1270 def update_contacts_tab(self):
1271 l = self.contacts_list
1274 for address in self.wallet.addressbook:
1275 label = self.wallet.labels.get(address,'')
1276 n = self.wallet.get_num_tx(address)
1277 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1278 item.setFont(0, QFont(MONOSPACE_FONT))
1279 # 32 = label can be edited (bool)
1280 item.setData(0,32, True)
1282 item.setData(0,33, address)
1283 l.addTopLevelItem(item)
1285 run_hook('update_contacts_tab', l)
1286 l.setCurrentItem(l.topLevelItem(0))
1290 def create_console_tab(self):
1291 from console import Console
1292 self.console = console = Console()
1296 def update_console(self):
1297 console = self.console
1298 console.history = self.config.get("console-history",[])
1299 console.history_index = len(console.history)
1301 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1302 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1304 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1306 def mkfunc(f, method):
1307 return lambda *args: apply( f, (method, args, self.password_dialog ))
1309 if m[0]=='_' or m in ['network','wallet']: continue
1310 methods[m] = mkfunc(c._run, m)
1312 console.updateNamespace(methods)
1315 def change_account(self,s):
1316 if s == _("All accounts"):
1317 self.current_account = None
1319 accounts = self.wallet.get_account_names()
1320 for k, v in accounts.items():
1322 self.current_account = k
1323 self.update_history_tab()
1324 self.update_status()
1325 self.update_receive_tab()
1327 def create_status_bar(self):
1330 sb.setFixedHeight(35)
1331 qtVersion = qVersion()
1333 self.balance_label = QLabel("")
1334 sb.addWidget(self.balance_label)
1336 from version_getter import UpdateLabel
1337 self.updatelabel = UpdateLabel(self.config, sb)
1339 self.account_selector = QComboBox()
1340 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1341 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1342 sb.addPermanentWidget(self.account_selector)
1344 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1345 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1347 self.lock_icon = QIcon()
1348 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1349 sb.addPermanentWidget( self.password_button )
1351 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1352 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1353 sb.addPermanentWidget( self.seed_button )
1354 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1355 sb.addPermanentWidget( self.status_button )
1357 run_hook('create_status_bar', (sb,))
1359 self.setStatusBar(sb)
1362 def update_lock_icon(self):
1363 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1364 self.password_button.setIcon( icon )
1367 def update_buttons_on_seed(self):
1368 if self.wallet.has_seed():
1369 self.seed_button.show()
1371 self.seed_button.hide()
1373 if not self.wallet.is_watching_only():
1374 self.password_button.show()
1375 self.send_button.setText(_("Send"))
1377 self.password_button.hide()
1378 self.send_button.setText(_("Create unsigned transaction"))
1381 def change_password_dialog(self):
1382 from password_dialog import PasswordDialog
1383 d = PasswordDialog(self.wallet, self)
1385 self.update_lock_icon()
1388 def new_contact_dialog(self):
1391 d.setWindowTitle(_("New Contact"))
1392 vbox = QVBoxLayout(d)
1393 vbox.addWidget(QLabel(_('New Contact')+':'))
1395 grid = QGridLayout()
1398 grid.addWidget(QLabel(_("Address")), 1, 0)
1399 grid.addWidget(line1, 1, 1)
1400 grid.addWidget(QLabel(_("Name")), 2, 0)
1401 grid.addWidget(line2, 2, 1)
1403 vbox.addLayout(grid)
1404 vbox.addLayout(ok_cancel_buttons(d))
1409 address = str(line1.text())
1410 label = unicode(line2.text())
1412 if not is_valid(address):
1413 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1416 self.wallet.add_contact(address)
1418 self.wallet.set_label(address, label)
1420 self.update_contacts_tab()
1421 self.update_history_tab()
1422 self.update_completions()
1423 self.tabs.setCurrentIndex(3)
1427 def new_account_dialog(self, password):
1429 dialog = QDialog(self)
1431 dialog.setWindowTitle(_("New Account"))
1433 vbox = QVBoxLayout()
1434 vbox.addWidget(QLabel(_('Account name')+':'))
1437 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1438 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1443 vbox.addLayout(ok_cancel_buttons(dialog))
1444 dialog.setLayout(vbox)
1448 name = str(e.text())
1451 self.wallet.create_pending_account(name, password)
1452 self.update_receive_tab()
1453 self.tabs.setCurrentIndex(2)
1458 def show_master_public_keys(self):
1460 dialog = QDialog(self)
1462 dialog.setWindowTitle(_("Master Public Keys"))
1464 main_layout = QGridLayout()
1465 mpk_dict = self.wallet.get_master_public_keys()
1467 for key, value in mpk_dict.items():
1468 main_layout.addWidget(QLabel(key), i, 0)
1469 mpk_text = QTextEdit()
1470 mpk_text.setReadOnly(True)
1471 mpk_text.setMaximumHeight(170)
1472 mpk_text.setText(value)
1473 main_layout.addWidget(mpk_text, i + 1, 0)
1476 vbox = QVBoxLayout()
1477 vbox.addLayout(main_layout)
1478 vbox.addLayout(close_button(dialog))
1480 dialog.setLayout(vbox)
1485 def show_seed_dialog(self, password):
1486 if not self.wallet.has_seed():
1487 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1491 mnemonic = self.wallet.get_mnemonic(password)
1493 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1495 from seed_dialog import SeedDialog
1496 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1501 def show_qrcode(self, data, title = _("QR code")):
1505 d.setWindowTitle(title)
1506 d.setMinimumSize(270, 300)
1507 vbox = QVBoxLayout()
1508 qrw = QRCodeWidget(data)
1509 vbox.addWidget(qrw, 1)
1510 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1511 hbox = QHBoxLayout()
1514 filename = os.path.join(self.config.path, "qrcode.bmp")
1517 bmp.save_qrcode(qrw.qr, filename)
1518 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1520 def copy_to_clipboard():
1521 bmp.save_qrcode(qrw.qr, filename)
1522 self.app.clipboard().setImage(QImage(filename))
1523 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1525 b = QPushButton(_("Copy"))
1527 b.clicked.connect(copy_to_clipboard)
1529 b = QPushButton(_("Save"))
1531 b.clicked.connect(print_qr)
1533 b = QPushButton(_("Close"))
1535 b.clicked.connect(d.accept)
1538 vbox.addLayout(hbox)
1543 def do_protect(self, func, args):
1544 if self.wallet.use_encryption:
1545 password = self.password_dialog()
1551 if args != (False,):
1552 args = (self,) + args + (password,)
1554 args = (self,password)
1558 def show_public_keys(self, address):
1559 if not address: return
1561 pubkey_list = self.wallet.get_public_keys(address)
1562 except Exception as e:
1563 traceback.print_exc(file=sys.stdout)
1564 self.show_message(str(e))
1568 d.setMinimumSize(600, 200)
1570 vbox = QVBoxLayout()
1571 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1572 vbox.addWidget( QLabel(_("Public key") + ':'))
1574 keys.setReadOnly(True)
1575 keys.setText('\n'.join(pubkey_list))
1576 vbox.addWidget(keys)
1577 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1578 vbox.addLayout(close_button(d))
1583 def show_private_key(self, address, password):
1584 if not address: return
1586 pk_list = self.wallet.get_private_key(address, password)
1587 except Exception as e:
1588 traceback.print_exc(file=sys.stdout)
1589 self.show_message(str(e))
1593 d.setMinimumSize(600, 200)
1595 vbox = QVBoxLayout()
1596 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1597 vbox.addWidget( QLabel(_("Private key") + ':'))
1599 keys.setReadOnly(True)
1600 keys.setText('\n'.join(pk_list))
1601 vbox.addWidget(keys)
1602 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1603 vbox.addLayout(close_button(d))
1609 def do_sign(self, address, message, signature, password):
1610 message = unicode(message.toPlainText())
1611 message = message.encode('utf-8')
1613 sig = self.wallet.sign_message(str(address.text()), message, password)
1614 signature.setText(sig)
1615 except Exception as e:
1616 self.show_message(str(e))
1618 def do_verify(self, address, message, signature):
1619 message = unicode(message.toPlainText())
1620 message = message.encode('utf-8')
1621 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1622 self.show_message(_("Signature verified"))
1624 self.show_message(_("Error: wrong signature"))
1627 def sign_verify_message(self, address=''):
1630 d.setWindowTitle(_('Sign/verify Message'))
1631 d.setMinimumSize(410, 290)
1633 layout = QGridLayout(d)
1635 message_e = QTextEdit()
1636 layout.addWidget(QLabel(_('Message')), 1, 0)
1637 layout.addWidget(message_e, 1, 1)
1638 layout.setRowStretch(2,3)
1640 address_e = QLineEdit()
1641 address_e.setText(address)
1642 layout.addWidget(QLabel(_('Address')), 2, 0)
1643 layout.addWidget(address_e, 2, 1)
1645 signature_e = QTextEdit()
1646 layout.addWidget(QLabel(_('Signature')), 3, 0)
1647 layout.addWidget(signature_e, 3, 1)
1648 layout.setRowStretch(3,1)
1650 hbox = QHBoxLayout()
1652 b = QPushButton(_("Sign"))
1653 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1656 b = QPushButton(_("Verify"))
1657 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1660 b = QPushButton(_("Close"))
1661 b.clicked.connect(d.accept)
1663 layout.addLayout(hbox, 4, 1)
1668 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1670 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1671 message_e.setText(decrypted)
1672 except Exception as e:
1673 self.show_message(str(e))
1676 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1677 message = unicode(message_e.toPlainText())
1678 message = message.encode('utf-8')
1680 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1681 encrypted_e.setText(encrypted)
1682 except Exception as e:
1683 self.show_message(str(e))
1687 def encrypt_message(self, address = ''):
1690 d.setWindowTitle(_('Encrypt/decrypt Message'))
1691 d.setMinimumSize(610, 490)
1693 layout = QGridLayout(d)
1695 message_e = QTextEdit()
1696 layout.addWidget(QLabel(_('Message')), 1, 0)
1697 layout.addWidget(message_e, 1, 1)
1698 layout.setRowStretch(2,3)
1700 pubkey_e = QLineEdit()
1702 pubkey = self.wallet.getpubkeys(address)[0]
1703 pubkey_e.setText(pubkey)
1704 layout.addWidget(QLabel(_('Public key')), 2, 0)
1705 layout.addWidget(pubkey_e, 2, 1)
1707 encrypted_e = QTextEdit()
1708 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1709 layout.addWidget(encrypted_e, 3, 1)
1710 layout.setRowStretch(3,1)
1712 hbox = QHBoxLayout()
1713 b = QPushButton(_("Encrypt"))
1714 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1717 b = QPushButton(_("Decrypt"))
1718 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1721 b = QPushButton(_("Close"))
1722 b.clicked.connect(d.accept)
1725 layout.addLayout(hbox, 4, 1)
1729 def question(self, msg):
1730 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1732 def show_message(self, msg):
1733 QMessageBox.information(self, _('Message'), msg, _('OK'))
1735 def password_dialog(self, msg=None):
1738 d.setWindowTitle(_("Enter Password"))
1743 vbox = QVBoxLayout()
1745 msg = _('Please enter your password')
1746 vbox.addWidget(QLabel(msg))
1748 grid = QGridLayout()
1750 grid.addWidget(QLabel(_('Password')), 1, 0)
1751 grid.addWidget(pw, 1, 1)
1752 vbox.addLayout(grid)
1754 vbox.addLayout(ok_cancel_buttons(d))
1757 run_hook('password_dialog', pw, grid, 1)
1758 if not d.exec_(): return
1759 return unicode(pw.text())
1768 def tx_from_text(self, txt):
1769 "json or raw hexadecimal"
1772 tx = Transaction(txt)
1778 tx_dict = json.loads(str(txt))
1779 assert "hex" in tx_dict.keys()
1780 tx = Transaction(tx_dict["hex"])
1781 if tx_dict.has_key("input_info"):
1782 input_info = json.loads(tx_dict['input_info'])
1783 tx.add_input_info(input_info)
1786 traceback.print_exc(file=sys.stdout)
1789 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1793 def read_tx_from_file(self):
1794 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1798 with open(fileName, "r") as f:
1799 file_content = f.read()
1800 except (ValueError, IOError, os.error), reason:
1801 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1803 return self.tx_from_text(file_content)
1807 def sign_raw_transaction(self, tx, input_info, password):
1808 self.wallet.signrawtransaction(tx, input_info, [], password)
1810 def do_process_from_text(self):
1811 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1814 tx = self.tx_from_text(text)
1816 self.show_transaction(tx)
1818 def do_process_from_file(self):
1819 tx = self.read_tx_from_file()
1821 self.show_transaction(tx)
1823 def do_process_from_txid(self):
1824 from electrum import transaction
1825 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1827 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1829 tx = transaction.Transaction(r)
1831 self.show_transaction(tx)
1833 self.show_message("unknown transaction")
1835 def do_process_from_csvReader(self, csvReader):
1840 for position, row in enumerate(csvReader):
1842 if not is_valid(address):
1843 errors.append((position, address))
1845 amount = Decimal(row[1])
1846 amount = int(100000000*amount)
1847 outputs.append((address, amount))
1848 except (ValueError, IOError, os.error), reason:
1849 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1853 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1854 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1858 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1859 except Exception as e:
1860 self.show_message(str(e))
1863 self.show_transaction(tx)
1865 def do_process_from_csv_file(self):
1866 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1870 with open(fileName, "r") as f:
1871 csvReader = csv.reader(f)
1872 self.do_process_from_csvReader(csvReader)
1873 except (ValueError, IOError, os.error), reason:
1874 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1877 def do_process_from_csv_text(self):
1878 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1879 + _("Format: address, amount. One output per line"), _("Load CSV"))
1882 f = StringIO.StringIO(text)
1883 csvReader = csv.reader(f)
1884 self.do_process_from_csvReader(csvReader)
1889 def export_privkeys_dialog(self, password):
1890 if self.wallet.is_watching_only():
1891 self.show_message(_("This is a watching-only wallet"))
1895 d.setWindowTitle(_('Private keys'))
1896 d.setMinimumSize(850, 300)
1897 vbox = QVBoxLayout(d)
1899 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1900 _("Exposing a single private key can compromise your entire wallet!"),
1901 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1902 vbox.addWidget(QLabel(msg))
1908 defaultname = 'electrum-private-keys.csv'
1909 select_msg = _('Select file to export your private keys to')
1910 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1911 vbox.addLayout(hbox)
1913 h, b = ok_cancel_buttons2(d, _('Export'))
1918 addresses = self.wallet.addresses(True)
1920 def privkeys_thread():
1921 for addr in addresses:
1925 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1926 d.emit(SIGNAL('computing_privkeys'))
1927 d.emit(SIGNAL('show_privkeys'))
1929 def show_privkeys():
1930 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1934 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1935 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1936 threading.Thread(target=privkeys_thread).start()
1942 filename = filename_e.text()
1947 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1948 except (IOError, os.error), reason:
1949 export_error_label = _("Electrum was unable to produce a private key-export.")
1950 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1952 except Exception as e:
1953 self.show_message(str(e))
1956 self.show_message(_("Private keys exported."))
1959 def do_export_privkeys(self, fileName, pklist, is_csv):
1960 with open(fileName, "w+") as f:
1962 transaction = csv.writer(f)
1963 transaction.writerow(["address", "private_key"])
1964 for addr, pk in pklist.items():
1965 transaction.writerow(["%34s"%addr,pk])
1968 f.write(json.dumps(pklist, indent = 4))
1971 def do_import_labels(self):
1972 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1973 if not labelsFile: return
1975 f = open(labelsFile, 'r')
1978 for key, value in json.loads(data).items():
1979 self.wallet.set_label(key, value)
1980 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1981 except (IOError, os.error), reason:
1982 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1985 def do_export_labels(self):
1986 labels = self.wallet.labels
1988 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1990 with open(fileName, 'w+') as f:
1991 json.dump(labels, f)
1992 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1993 except (IOError, os.error), reason:
1994 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1997 def export_history_dialog(self):
2000 d.setWindowTitle(_('Export History'))
2001 d.setMinimumSize(400, 200)
2002 vbox = QVBoxLayout(d)
2004 defaultname = os.path.expanduser('~/electrum-history.csv')
2005 select_msg = _('Select file to export your wallet transactions to')
2007 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2008 vbox.addLayout(hbox)
2012 h, b = ok_cancel_buttons2(d, _('Export'))
2017 filename = filename_e.text()
2022 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2023 except (IOError, os.error), reason:
2024 export_error_label = _("Electrum was unable to produce a transaction export.")
2025 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2028 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2031 def do_export_history(self, wallet, fileName, is_csv):
2032 history = wallet.get_tx_history()
2034 for item in history:
2035 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2037 if timestamp is not None:
2039 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2040 except [RuntimeError, TypeError, NameError] as reason:
2041 time_string = "unknown"
2044 time_string = "unknown"
2046 time_string = "pending"
2048 if value is not None:
2049 value_string = format_satoshis(value, True)
2054 fee_string = format_satoshis(fee, True)
2059 label, is_default_label = wallet.get_label(tx_hash)
2060 label = label.encode('utf-8')
2064 balance_string = format_satoshis(balance, False)
2066 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2068 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2070 with open(fileName, "w+") as f:
2072 transaction = csv.writer(f)
2073 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2075 transaction.writerow(line)
2078 f.write(json.dumps(lines, indent = 4))
2081 def sweep_key_dialog(self):
2083 d.setWindowTitle(_('Sweep private keys'))
2084 d.setMinimumSize(600, 300)
2086 vbox = QVBoxLayout(d)
2087 vbox.addWidget(QLabel(_("Enter private keys")))
2089 keys_e = QTextEdit()
2090 keys_e.setTabChangesFocus(True)
2091 vbox.addWidget(keys_e)
2093 h, address_e = address_field(self.wallet.addresses())
2097 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2098 vbox.addLayout(hbox)
2099 button.setEnabled(False)
2102 addr = str(address_e.text())
2103 if bitcoin.is_address(addr):
2107 pk = str(keys_e.toPlainText()).strip()
2108 if Wallet.is_private_key(pk):
2111 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2112 keys_e.textChanged.connect(f)
2113 address_e.textChanged.connect(f)
2117 fee = self.wallet.fee
2118 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2119 self.show_transaction(tx)
2123 def do_import_privkey(self, password):
2124 if not self.wallet.has_imported_keys():
2125 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2126 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2127 + _('Are you sure you understand what you are doing?'), 3, 4)
2130 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2133 text = str(text).split()
2138 addr = self.wallet.import_key(key, password)
2139 except Exception as e:
2145 addrlist.append(addr)
2147 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2149 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2150 self.update_receive_tab()
2151 self.update_history_tab()
2154 def settings_dialog(self):
2156 d.setWindowTitle(_('Electrum Settings'))
2158 vbox = QVBoxLayout()
2159 grid = QGridLayout()
2160 grid.setColumnStretch(0,1)
2162 nz_label = QLabel(_('Display zeros') + ':')
2163 grid.addWidget(nz_label, 0, 0)
2164 nz_e = AmountEdit(None,True)
2165 nz_e.setText("%d"% self.num_zeros)
2166 grid.addWidget(nz_e, 0, 1)
2167 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2168 grid.addWidget(HelpButton(msg), 0, 2)
2169 if not self.config.is_modifiable('num_zeros'):
2170 for w in [nz_e, nz_label]: w.setEnabled(False)
2172 lang_label=QLabel(_('Language') + ':')
2173 grid.addWidget(lang_label, 1, 0)
2174 lang_combo = QComboBox()
2175 from electrum.i18n import languages
2176 lang_combo.addItems(languages.values())
2178 index = languages.keys().index(self.config.get("language",''))
2181 lang_combo.setCurrentIndex(index)
2182 grid.addWidget(lang_combo, 1, 1)
2183 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2184 if not self.config.is_modifiable('language'):
2185 for w in [lang_combo, lang_label]: w.setEnabled(False)
2188 fee_label = QLabel(_('Transaction fee') + ':')
2189 grid.addWidget(fee_label, 2, 0)
2190 fee_e = AmountEdit(self.get_decimal_point)
2191 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2192 grid.addWidget(fee_e, 2, 1)
2193 msg = _('Fee per kilobyte of transaction.') + ' ' \
2194 + _('Recommended value') + ': ' + self.format_amount(20000)
2195 grid.addWidget(HelpButton(msg), 2, 2)
2196 if not self.config.is_modifiable('fee_per_kb'):
2197 for w in [fee_e, fee_label]: w.setEnabled(False)
2199 units = ['BTC', 'mBTC']
2200 unit_label = QLabel(_('Base unit') + ':')
2201 grid.addWidget(unit_label, 3, 0)
2202 unit_combo = QComboBox()
2203 unit_combo.addItems(units)
2204 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2205 grid.addWidget(unit_combo, 3, 1)
2206 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2207 + '\n1BTC=1000mBTC.\n' \
2208 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2210 usechange_cb = QCheckBox(_('Use change addresses'))
2211 usechange_cb.setChecked(self.wallet.use_change)
2212 grid.addWidget(usechange_cb, 4, 0)
2213 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2214 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2216 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2217 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2218 grid.addWidget(block_ex_label, 5, 0)
2219 block_ex_combo = QComboBox()
2220 block_ex_combo.addItems(block_explorers)
2221 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2222 grid.addWidget(block_ex_combo, 5, 1)
2223 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2225 show_tx = self.config.get('show_before_broadcast', False)
2226 showtx_cb = QCheckBox(_('Show before broadcast'))
2227 showtx_cb.setChecked(show_tx)
2228 grid.addWidget(showtx_cb, 6, 0)
2229 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2231 vbox.addLayout(grid)
2233 vbox.addLayout(ok_cancel_buttons(d))
2237 if not d.exec_(): return
2240 fee = self.fee_e.get_amount()
2242 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2245 self.wallet.set_fee(fee)
2247 nz = unicode(nz_e.text())
2252 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2255 if self.num_zeros != nz:
2257 self.config.set_key('num_zeros', nz, True)
2258 self.update_history_tab()
2259 self.update_receive_tab()
2261 usechange_result = usechange_cb.isChecked()
2262 if self.wallet.use_change != usechange_result:
2263 self.wallet.use_change = usechange_result
2264 self.wallet.storage.put('use_change', self.wallet.use_change)
2266 if showtx_cb.isChecked() != show_tx:
2267 self.config.set_key('show_before_broadcast', not show_tx)
2269 unit_result = units[unit_combo.currentIndex()]
2270 if self.base_unit() != unit_result:
2271 self.decimal_point = 8 if unit_result == 'BTC' else 5
2272 self.config.set_key('decimal_point', self.decimal_point, True)
2273 self.update_history_tab()
2274 self.update_status()
2276 need_restart = False
2278 lang_request = languages.keys()[lang_combo.currentIndex()]
2279 if lang_request != self.config.get('language'):
2280 self.config.set_key("language", lang_request, True)
2283 be_result = block_explorers[block_ex_combo.currentIndex()]
2284 self.config.set_key('block_explorer', be_result, True)
2286 run_hook('close_settings_dialog')
2289 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2292 def run_network_dialog(self):
2293 if not self.network:
2295 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2297 def closeEvent(self, event):
2299 self.config.set_key("is_maximized", self.isMaximized())
2300 if not self.isMaximized():
2302 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2303 self.save_column_widths()
2304 self.config.set_key("console-history", self.console.history[-50:], True)
2305 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2309 def plugins_dialog(self):
2310 from electrum.plugins import plugins
2313 d.setWindowTitle(_('Electrum Plugins'))
2316 vbox = QVBoxLayout(d)
2319 scroll = QScrollArea()
2320 scroll.setEnabled(True)
2321 scroll.setWidgetResizable(True)
2322 scroll.setMinimumSize(400,250)
2323 vbox.addWidget(scroll)
2327 w.setMinimumHeight(len(plugins)*35)
2329 grid = QGridLayout()
2330 grid.setColumnStretch(0,1)
2333 def do_toggle(cb, p, w):
2336 if w: w.setEnabled(r)
2338 def mk_toggle(cb, p, w):
2339 return lambda: do_toggle(cb,p,w)
2341 for i, p in enumerate(plugins):
2343 cb = QCheckBox(p.fullname())
2344 cb.setDisabled(not p.is_available())
2345 cb.setChecked(p.is_enabled())
2346 grid.addWidget(cb, i, 0)
2347 if p.requires_settings():
2348 w = p.settings_widget(self)
2349 w.setEnabled( p.is_enabled() )
2350 grid.addWidget(w, i, 1)
2353 cb.clicked.connect(mk_toggle(cb,p,w))
2354 grid.addWidget(HelpButton(p.description()), i, 2)
2356 print_msg(_("Error: cannot display plugin"), p)
2357 traceback.print_exc(file=sys.stdout)
2358 grid.setRowStretch(i+1,1)
2360 vbox.addLayout(close_button(d))
2365 def show_account_details(self, k):
2366 account = self.wallet.accounts[k]
2369 d.setWindowTitle(_('Account Details'))
2372 vbox = QVBoxLayout(d)
2373 name = self.wallet.get_account_name(k)
2374 label = QLabel('Name: ' + name)
2375 vbox.addWidget(label)
2377 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2379 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2381 vbox.addWidget(QLabel(_('Master Public Key:')))
2384 text.setReadOnly(True)
2385 text.setMaximumHeight(170)
2386 vbox.addWidget(text)
2388 mpk_text = '\n'.join( account.get_master_pubkeys() )
2389 text.setText(mpk_text)
2391 vbox.addLayout(close_button(d))