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 = QTreeWidget(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 grid.addWidget(self.from_list, 3, 1, 1, 3)
674 self.set_pay_from([])
676 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
677 + _('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.') \
678 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
679 grid.addWidget(QLabel(_('Amount')), 4, 0)
680 grid.addWidget(self.amount_e, 4, 1, 1, 2)
681 grid.addWidget(self.amount_help, 4, 3)
683 self.fee_e = AmountEdit(self.get_decimal_point)
684 grid.addWidget(QLabel(_('Fee')), 5, 0)
685 grid.addWidget(self.fee_e, 5, 1, 1, 2)
686 grid.addWidget(HelpButton(
687 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
688 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
689 + _('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)
691 run_hook('exchange_rate_button', grid)
693 self.send_button = EnterButton(_("Send"), self.do_send)
694 grid.addWidget(self.send_button, 6, 1)
696 b = EnterButton(_("Clear"), self.do_clear)
697 grid.addWidget(b, 6, 2)
699 self.payto_sig = QLabel('')
700 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
702 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
703 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
706 def entry_changed( is_fee ):
707 self.funds_error = False
709 if self.amount_e.is_shortcut:
710 self.amount_e.is_shortcut = False
711 sendable = self.get_sendable_balance()
712 # there is only one output because we are completely spending inputs
713 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
714 fee = self.wallet.estimated_fee(inputs, 1)
716 self.amount_e.setText( self.format_amount(amount) )
717 self.fee_e.setText( self.format_amount( fee ) )
720 amount = self.amount_e.get_amount()
721 fee = self.fee_e.get_amount()
723 if not is_fee: fee = None
726 # assume that there will be 2 outputs (one for change)
727 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
729 self.fee_e.setText( self.format_amount( fee ) )
732 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
736 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
737 self.funds_error = True
738 text = _( "Not enough funds" )
739 c, u = self.wallet.get_frozen_balance()
740 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
742 self.statusBar().showMessage(text)
743 self.amount_e.setPalette(palette)
744 self.fee_e.setPalette(palette)
746 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
747 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
749 run_hook('create_send_tab', grid)
753 def set_pay_from(self, l):
755 self.from_list.clear()
756 self.from_label.setHidden(len(self.pay_from) == 0)
757 self.from_list.setHidden(len(self.pay_from) == 0)
758 for addr in self.pay_from:
759 c, u = self.wallet.get_addr_balance(addr)
760 balance = self.format_amount(c + u)
761 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
764 def update_completions(self):
766 for addr,label in self.wallet.labels.items():
767 if addr in self.wallet.addressbook:
768 l.append( label + ' <' + addr + '>')
770 run_hook('update_completions', l)
771 self.completions.setStringList(l)
775 return lambda s, *args: s.do_protect(func, args)
779 label = unicode( self.message_e.text() )
781 if self.gui_object.payment_request:
782 outputs = self.gui_object.payment_request.outputs
783 amount = self.gui_object.payment_request.get_amount()
785 outputs = self.payto_e.get_outputs()
786 amount = sum(map(lambda x:x[1], outputs))
789 fee = self.fee_e.get_amount()
791 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
794 confirm_amount = self.config.get('confirm_amount', 100000000)
795 if amount >= confirm_amount:
796 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
799 confirm_fee = self.config.get('confirm_fee', 100000)
800 if fee >= confirm_fee:
801 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()}):
804 self.send_tx(outputs, fee, label)
809 def send_tx(self, outputs, fee, label, password):
810 self.send_button.setDisabled(True)
812 # first, create an unsigned tx
813 domain = self.get_payment_sources()
815 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
817 except Exception as e:
818 traceback.print_exc(file=sys.stdout)
819 self.show_message(str(e))
820 self.send_button.setDisabled(False)
823 # call hook to see if plugin needs gui interaction
824 run_hook('send_tx', tx)
830 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
831 self.wallet.sign_transaction(tx, keypairs, password)
832 return tx, fee, label
834 def sign_done(tx, fee, label):
836 self.show_message(tx.error)
837 self.send_button.setDisabled(False)
839 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
840 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
841 self.send_button.setDisabled(False)
844 self.wallet.set_label(tx.hash(), label)
846 if not self.gui_object.payment_request:
847 if not tx.is_complete() or self.config.get('show_before_broadcast'):
848 self.show_transaction(tx)
850 self.send_button.setDisabled(False)
853 self.broadcast_transaction(tx)
855 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
856 self.waiting_dialog.start()
860 def broadcast_transaction(self, tx):
862 def broadcast_thread():
863 if self.gui_object.payment_request:
864 refund_address = self.wallet.addresses()[0]
865 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
866 self.gui_object.payment_request = None
868 status, msg = self.wallet.sendtx(tx)
871 def broadcast_done(status, msg):
873 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
876 QMessageBox.warning(self, _('Error'), msg, _('OK'))
877 self.send_button.setDisabled(False)
879 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
880 self.waiting_dialog.start()
884 def prepare_for_payment_request(self):
885 self.tabs.setCurrentIndex(1)
886 for e in [self.payto_e, self.amount_e, self.message_e]:
888 for h in [self.payto_help, self.amount_help, self.message_help]:
890 self.payto_e.setText(_("please wait..."))
893 def payment_request_ok(self):
894 self.payto_e.setText(self.gui_object.payment_request.domain)
895 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
896 self.message_e.setText(self.gui_object.payment_request.memo)
898 def payment_request_error(self):
900 self.show_message(self.gui_object.payment_request.error)
901 self.gui_object.payment_request = None
903 def set_send(self, address, amount, label, message):
905 if label and self.wallet.labels.get(address) != label:
906 if self.question('Give label "%s" to address %s ?'%(label,address)):
907 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
908 self.wallet.addressbook.append(address)
909 self.wallet.set_label(address, label)
911 self.tabs.setCurrentIndex(1)
912 label = self.wallet.labels.get(address)
913 m_addr = label + ' <'+ address +'>' if label else address
914 self.payto_e.setText(m_addr)
916 self.message_e.setText(message)
918 self.amount_e.setText(amount)
922 self.payto_sig.setVisible(False)
923 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
927 for h in [self.payto_help, self.amount_help, self.message_help]:
930 self.set_pay_from([])
935 def set_addrs_frozen(self,addrs,freeze):
937 if not addr: continue
938 if addr in self.wallet.frozen_addresses and not freeze:
939 self.wallet.unfreeze(addr)
940 elif addr not in self.wallet.frozen_addresses and freeze:
941 self.wallet.freeze(addr)
942 self.update_receive_tab()
946 def create_list_tab(self, headers):
947 "generic tab creation method"
948 l = MyTreeWidget(self)
949 l.setColumnCount( len(headers) )
950 l.setHeaderLabels( headers )
960 vbox.addWidget(buttons)
965 buttons.setLayout(hbox)
970 def create_receive_tab(self):
971 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
972 l.setContextMenuPolicy(Qt.CustomContextMenu)
973 l.customContextMenuRequested.connect(self.create_receive_menu)
974 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
975 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
976 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
977 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
978 self.receive_list = l
979 self.receive_buttons_hbox = hbox
986 def save_column_widths(self):
987 self.column_widths["receive"] = []
988 for i in range(self.receive_list.columnCount() -1):
989 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
991 self.column_widths["history"] = []
992 for i in range(self.history_list.columnCount() - 1):
993 self.column_widths["history"].append(self.history_list.columnWidth(i))
995 self.column_widths["contacts"] = []
996 for i in range(self.contacts_list.columnCount() - 1):
997 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
999 self.config.set_key("column_widths_2", self.column_widths, True)
1002 def create_contacts_tab(self):
1003 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1004 l.setContextMenuPolicy(Qt.CustomContextMenu)
1005 l.customContextMenuRequested.connect(self.create_contact_menu)
1006 for i,width in enumerate(self.column_widths['contacts']):
1007 l.setColumnWidth(i, width)
1009 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1010 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1011 self.contacts_list = l
1012 self.contacts_buttons_hbox = hbox
1017 def delete_imported_key(self, addr):
1018 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1019 self.wallet.delete_imported_key(addr)
1020 self.update_receive_tab()
1021 self.update_history_tab()
1023 def edit_account_label(self, k):
1024 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1026 label = unicode(text)
1027 self.wallet.set_label(k,label)
1028 self.update_receive_tab()
1030 def account_set_expanded(self, item, k, b):
1032 self.accounts_expanded[k] = b
1034 def create_account_menu(self, position, k, item):
1036 if item.isExpanded():
1037 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1039 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1040 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1041 if self.wallet.seed_version > 4:
1042 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1043 if self.wallet.account_is_pending(k):
1044 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1045 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1047 def delete_pending_account(self, k):
1048 self.wallet.delete_pending_account(k)
1049 self.update_receive_tab()
1051 def create_receive_menu(self, position):
1052 # fixme: this function apparently has a side effect.
1053 # if it is not called the menu pops up several times
1054 #self.receive_list.selectedIndexes()
1056 selected = self.receive_list.selectedItems()
1057 multi_select = len(selected) > 1
1058 addrs = [unicode(item.text(0)) for item in selected]
1059 if not multi_select:
1060 item = self.receive_list.itemAt(position)
1064 if not is_valid(addr):
1065 k = str(item.data(0,32).toString())
1067 self.create_account_menu(position, k, item)
1069 item.setExpanded(not item.isExpanded())
1073 if not multi_select:
1074 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1075 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1076 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1077 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1078 if not self.wallet.is_watching_only():
1079 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1080 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1081 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1082 if self.wallet.is_imported(addr):
1083 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1085 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1086 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1087 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1088 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1090 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1091 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1093 run_hook('receive_menu', menu, addrs)
1094 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1097 def get_sendable_balance(self):
1098 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1101 def get_payment_sources(self):
1103 return self.pay_from
1105 return self.wallet.get_account_addresses(self.current_account)
1108 def send_from_addresses(self, addrs):
1109 self.set_pay_from( addrs )
1110 self.tabs.setCurrentIndex(1)
1113 def payto(self, addr):
1115 label = self.wallet.labels.get(addr)
1116 m_addr = label + ' <' + addr + '>' if label else addr
1117 self.tabs.setCurrentIndex(1)
1118 self.payto_e.setText(m_addr)
1119 self.amount_e.setFocus()
1122 def delete_contact(self, x):
1123 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1124 self.wallet.delete_contact(x)
1125 self.wallet.set_label(x, None)
1126 self.update_history_tab()
1127 self.update_contacts_tab()
1128 self.update_completions()
1131 def create_contact_menu(self, position):
1132 item = self.contacts_list.itemAt(position)
1135 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1137 addr = unicode(item.text(0))
1138 label = unicode(item.text(1))
1139 is_editable = item.data(0,32).toBool()
1140 payto_addr = item.data(0,33).toString()
1141 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1142 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1143 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1145 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1146 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1148 run_hook('create_contact_menu', menu, item)
1149 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1152 def update_receive_item(self, item):
1153 item.setFont(0, QFont(MONOSPACE_FONT))
1154 address = str(item.data(0,0).toString())
1155 label = self.wallet.labels.get(address,'')
1156 item.setData(1,0,label)
1157 item.setData(0,32, True) # is editable
1159 run_hook('update_receive_item', address, item)
1161 if not self.wallet.is_mine(address): return
1163 c, u = self.wallet.get_addr_balance(address)
1164 balance = self.format_amount(c + u)
1165 item.setData(2,0,balance)
1167 if address in self.wallet.frozen_addresses:
1168 item.setBackgroundColor(0, QColor('lightblue'))
1171 def update_receive_tab(self):
1172 l = self.receive_list
1173 # extend the syntax for consistency
1174 l.addChild = l.addTopLevelItem
1175 l.insertChild = l.insertTopLevelItem
1178 for i,width in enumerate(self.column_widths['receive']):
1179 l.setColumnWidth(i, width)
1181 accounts = self.wallet.get_accounts()
1182 if self.current_account is None:
1183 account_items = sorted(accounts.items())
1185 account_items = [(self.current_account, accounts.get(self.current_account))]
1188 for k, account in account_items:
1190 if len(accounts) > 1:
1191 name = self.wallet.get_account_name(k)
1192 c,u = self.wallet.get_account_balance(k)
1193 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1194 l.addTopLevelItem(account_item)
1195 account_item.setExpanded(self.accounts_expanded.get(k, True))
1196 account_item.setData(0, 32, k)
1200 sequences = [0,1] if account.has_change() else [0]
1201 for is_change in sequences:
1202 if len(sequences) > 1:
1203 name = _("Receiving") if not is_change else _("Change")
1204 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1205 account_item.addChild(seq_item)
1207 seq_item.setExpanded(True)
1209 seq_item = account_item
1211 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1217 for address in account.get_addresses(is_change):
1219 num, is_used = self.wallet.is_used(address)
1222 if gap > self.wallet.gap_limit:
1227 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1228 self.update_receive_item(item)
1230 item.setBackgroundColor(1, QColor('red'))
1234 seq_item.insertChild(0,used_item)
1236 used_item.addChild(item)
1238 seq_item.addChild(item)
1240 # we use column 1 because column 0 may be hidden
1241 l.setCurrentItem(l.topLevelItem(0),1)
1244 def update_contacts_tab(self):
1245 l = self.contacts_list
1248 for address in self.wallet.addressbook:
1249 label = self.wallet.labels.get(address,'')
1250 n = self.wallet.get_num_tx(address)
1251 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1252 item.setFont(0, QFont(MONOSPACE_FONT))
1253 # 32 = label can be edited (bool)
1254 item.setData(0,32, True)
1256 item.setData(0,33, address)
1257 l.addTopLevelItem(item)
1259 run_hook('update_contacts_tab', l)
1260 l.setCurrentItem(l.topLevelItem(0))
1264 def create_console_tab(self):
1265 from console import Console
1266 self.console = console = Console()
1270 def update_console(self):
1271 console = self.console
1272 console.history = self.config.get("console-history",[])
1273 console.history_index = len(console.history)
1275 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1276 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1278 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1280 def mkfunc(f, method):
1281 return lambda *args: apply( f, (method, args, self.password_dialog ))
1283 if m[0]=='_' or m in ['network','wallet']: continue
1284 methods[m] = mkfunc(c._run, m)
1286 console.updateNamespace(methods)
1289 def change_account(self,s):
1290 if s == _("All accounts"):
1291 self.current_account = None
1293 accounts = self.wallet.get_account_names()
1294 for k, v in accounts.items():
1296 self.current_account = k
1297 self.update_history_tab()
1298 self.update_status()
1299 self.update_receive_tab()
1301 def create_status_bar(self):
1304 sb.setFixedHeight(35)
1305 qtVersion = qVersion()
1307 self.balance_label = QLabel("")
1308 sb.addWidget(self.balance_label)
1310 from version_getter import UpdateLabel
1311 self.updatelabel = UpdateLabel(self.config, sb)
1313 self.account_selector = QComboBox()
1314 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1315 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1316 sb.addPermanentWidget(self.account_selector)
1318 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1319 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1321 self.lock_icon = QIcon()
1322 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1323 sb.addPermanentWidget( self.password_button )
1325 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1326 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1327 sb.addPermanentWidget( self.seed_button )
1328 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1329 sb.addPermanentWidget( self.status_button )
1331 run_hook('create_status_bar', (sb,))
1333 self.setStatusBar(sb)
1336 def update_lock_icon(self):
1337 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1338 self.password_button.setIcon( icon )
1341 def update_buttons_on_seed(self):
1342 if self.wallet.has_seed():
1343 self.seed_button.show()
1345 self.seed_button.hide()
1347 if not self.wallet.is_watching_only():
1348 self.password_button.show()
1349 self.send_button.setText(_("Send"))
1351 self.password_button.hide()
1352 self.send_button.setText(_("Create unsigned transaction"))
1355 def change_password_dialog(self):
1356 from password_dialog import PasswordDialog
1357 d = PasswordDialog(self.wallet, self)
1359 self.update_lock_icon()
1362 def new_contact_dialog(self):
1365 d.setWindowTitle(_("New Contact"))
1366 vbox = QVBoxLayout(d)
1367 vbox.addWidget(QLabel(_('New Contact')+':'))
1369 grid = QGridLayout()
1372 grid.addWidget(QLabel(_("Address")), 1, 0)
1373 grid.addWidget(line1, 1, 1)
1374 grid.addWidget(QLabel(_("Name")), 2, 0)
1375 grid.addWidget(line2, 2, 1)
1377 vbox.addLayout(grid)
1378 vbox.addLayout(ok_cancel_buttons(d))
1383 address = str(line1.text())
1384 label = unicode(line2.text())
1386 if not is_valid(address):
1387 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1390 self.wallet.add_contact(address)
1392 self.wallet.set_label(address, label)
1394 self.update_contacts_tab()
1395 self.update_history_tab()
1396 self.update_completions()
1397 self.tabs.setCurrentIndex(3)
1401 def new_account_dialog(self, password):
1403 dialog = QDialog(self)
1405 dialog.setWindowTitle(_("New Account"))
1407 vbox = QVBoxLayout()
1408 vbox.addWidget(QLabel(_('Account name')+':'))
1411 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1412 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1417 vbox.addLayout(ok_cancel_buttons(dialog))
1418 dialog.setLayout(vbox)
1422 name = str(e.text())
1425 self.wallet.create_pending_account(name, password)
1426 self.update_receive_tab()
1427 self.tabs.setCurrentIndex(2)
1432 def show_master_public_keys(self):
1434 dialog = QDialog(self)
1436 dialog.setWindowTitle(_("Master Public Keys"))
1438 main_layout = QGridLayout()
1439 mpk_dict = self.wallet.get_master_public_keys()
1441 for key, value in mpk_dict.items():
1442 main_layout.addWidget(QLabel(key), i, 0)
1443 mpk_text = QTextEdit()
1444 mpk_text.setReadOnly(True)
1445 mpk_text.setMaximumHeight(170)
1446 mpk_text.setText(value)
1447 main_layout.addWidget(mpk_text, i + 1, 0)
1450 vbox = QVBoxLayout()
1451 vbox.addLayout(main_layout)
1452 vbox.addLayout(close_button(dialog))
1454 dialog.setLayout(vbox)
1459 def show_seed_dialog(self, password):
1460 if not self.wallet.has_seed():
1461 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1465 mnemonic = self.wallet.get_mnemonic(password)
1467 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1469 from seed_dialog import SeedDialog
1470 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1475 def show_qrcode(self, data, title = _("QR code")):
1479 d.setWindowTitle(title)
1480 d.setMinimumSize(270, 300)
1481 vbox = QVBoxLayout()
1482 qrw = QRCodeWidget(data)
1483 vbox.addWidget(qrw, 1)
1484 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1485 hbox = QHBoxLayout()
1488 filename = os.path.join(self.config.path, "qrcode.bmp")
1491 bmp.save_qrcode(qrw.qr, filename)
1492 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1494 def copy_to_clipboard():
1495 bmp.save_qrcode(qrw.qr, filename)
1496 self.app.clipboard().setImage(QImage(filename))
1497 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1499 b = QPushButton(_("Copy"))
1501 b.clicked.connect(copy_to_clipboard)
1503 b = QPushButton(_("Save"))
1505 b.clicked.connect(print_qr)
1507 b = QPushButton(_("Close"))
1509 b.clicked.connect(d.accept)
1512 vbox.addLayout(hbox)
1517 def do_protect(self, func, args):
1518 if self.wallet.use_encryption:
1519 password = self.password_dialog()
1525 if args != (False,):
1526 args = (self,) + args + (password,)
1528 args = (self,password)
1532 def show_public_keys(self, address):
1533 if not address: return
1535 pubkey_list = self.wallet.get_public_keys(address)
1536 except Exception as e:
1537 traceback.print_exc(file=sys.stdout)
1538 self.show_message(str(e))
1542 d.setMinimumSize(600, 200)
1544 vbox = QVBoxLayout()
1545 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1546 vbox.addWidget( QLabel(_("Public key") + ':'))
1548 keys.setReadOnly(True)
1549 keys.setText('\n'.join(pubkey_list))
1550 vbox.addWidget(keys)
1551 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1552 vbox.addLayout(close_button(d))
1557 def show_private_key(self, address, password):
1558 if not address: return
1560 pk_list = self.wallet.get_private_key(address, password)
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(_("Private key") + ':'))
1573 keys.setReadOnly(True)
1574 keys.setText('\n'.join(pk_list))
1575 vbox.addWidget(keys)
1576 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1577 vbox.addLayout(close_button(d))
1583 def do_sign(self, address, message, signature, password):
1584 message = unicode(message.toPlainText())
1585 message = message.encode('utf-8')
1587 sig = self.wallet.sign_message(str(address.text()), message, password)
1588 signature.setText(sig)
1589 except Exception as e:
1590 self.show_message(str(e))
1592 def do_verify(self, address, message, signature):
1593 message = unicode(message.toPlainText())
1594 message = message.encode('utf-8')
1595 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1596 self.show_message(_("Signature verified"))
1598 self.show_message(_("Error: wrong signature"))
1601 def sign_verify_message(self, address=''):
1604 d.setWindowTitle(_('Sign/verify Message'))
1605 d.setMinimumSize(410, 290)
1607 layout = QGridLayout(d)
1609 message_e = QTextEdit()
1610 layout.addWidget(QLabel(_('Message')), 1, 0)
1611 layout.addWidget(message_e, 1, 1)
1612 layout.setRowStretch(2,3)
1614 address_e = QLineEdit()
1615 address_e.setText(address)
1616 layout.addWidget(QLabel(_('Address')), 2, 0)
1617 layout.addWidget(address_e, 2, 1)
1619 signature_e = QTextEdit()
1620 layout.addWidget(QLabel(_('Signature')), 3, 0)
1621 layout.addWidget(signature_e, 3, 1)
1622 layout.setRowStretch(3,1)
1624 hbox = QHBoxLayout()
1626 b = QPushButton(_("Sign"))
1627 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1630 b = QPushButton(_("Verify"))
1631 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1634 b = QPushButton(_("Close"))
1635 b.clicked.connect(d.accept)
1637 layout.addLayout(hbox, 4, 1)
1642 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1644 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1645 message_e.setText(decrypted)
1646 except Exception as e:
1647 self.show_message(str(e))
1650 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1651 message = unicode(message_e.toPlainText())
1652 message = message.encode('utf-8')
1654 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1655 encrypted_e.setText(encrypted)
1656 except Exception as e:
1657 self.show_message(str(e))
1661 def encrypt_message(self, address = ''):
1664 d.setWindowTitle(_('Encrypt/decrypt Message'))
1665 d.setMinimumSize(610, 490)
1667 layout = QGridLayout(d)
1669 message_e = QTextEdit()
1670 layout.addWidget(QLabel(_('Message')), 1, 0)
1671 layout.addWidget(message_e, 1, 1)
1672 layout.setRowStretch(2,3)
1674 pubkey_e = QLineEdit()
1676 pubkey = self.wallet.getpubkeys(address)[0]
1677 pubkey_e.setText(pubkey)
1678 layout.addWidget(QLabel(_('Public key')), 2, 0)
1679 layout.addWidget(pubkey_e, 2, 1)
1681 encrypted_e = QTextEdit()
1682 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1683 layout.addWidget(encrypted_e, 3, 1)
1684 layout.setRowStretch(3,1)
1686 hbox = QHBoxLayout()
1687 b = QPushButton(_("Encrypt"))
1688 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1691 b = QPushButton(_("Decrypt"))
1692 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1695 b = QPushButton(_("Close"))
1696 b.clicked.connect(d.accept)
1699 layout.addLayout(hbox, 4, 1)
1703 def question(self, msg):
1704 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1706 def show_message(self, msg):
1707 QMessageBox.information(self, _('Message'), msg, _('OK'))
1709 def password_dialog(self, msg=None):
1712 d.setWindowTitle(_("Enter Password"))
1717 vbox = QVBoxLayout()
1719 msg = _('Please enter your password')
1720 vbox.addWidget(QLabel(msg))
1722 grid = QGridLayout()
1724 grid.addWidget(QLabel(_('Password')), 1, 0)
1725 grid.addWidget(pw, 1, 1)
1726 vbox.addLayout(grid)
1728 vbox.addLayout(ok_cancel_buttons(d))
1731 run_hook('password_dialog', pw, grid, 1)
1732 if not d.exec_(): return
1733 return unicode(pw.text())
1742 def tx_from_text(self, txt):
1743 "json or raw hexadecimal"
1746 tx = Transaction(txt)
1752 tx_dict = json.loads(str(txt))
1753 assert "hex" in tx_dict.keys()
1754 tx = Transaction(tx_dict["hex"])
1755 if tx_dict.has_key("input_info"):
1756 input_info = json.loads(tx_dict['input_info'])
1757 tx.add_input_info(input_info)
1760 traceback.print_exc(file=sys.stdout)
1763 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1767 def read_tx_from_file(self):
1768 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1772 with open(fileName, "r") as f:
1773 file_content = f.read()
1774 except (ValueError, IOError, os.error), reason:
1775 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1777 return self.tx_from_text(file_content)
1781 def sign_raw_transaction(self, tx, input_info, password):
1782 self.wallet.signrawtransaction(tx, input_info, [], password)
1784 def do_process_from_text(self):
1785 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1788 tx = self.tx_from_text(text)
1790 self.show_transaction(tx)
1792 def do_process_from_file(self):
1793 tx = self.read_tx_from_file()
1795 self.show_transaction(tx)
1797 def do_process_from_txid(self):
1798 from electrum import transaction
1799 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1801 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1803 tx = transaction.Transaction(r)
1805 self.show_transaction(tx)
1807 self.show_message("unknown transaction")
1809 def do_process_from_csvReader(self, csvReader):
1814 for position, row in enumerate(csvReader):
1816 if not is_valid(address):
1817 errors.append((position, address))
1819 amount = Decimal(row[1])
1820 amount = int(100000000*amount)
1821 outputs.append((address, amount))
1822 except (ValueError, IOError, os.error), reason:
1823 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1827 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1828 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1832 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1833 except Exception as e:
1834 self.show_message(str(e))
1837 self.show_transaction(tx)
1839 def do_process_from_csv_file(self):
1840 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1844 with open(fileName, "r") as f:
1845 csvReader = csv.reader(f)
1846 self.do_process_from_csvReader(csvReader)
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))
1851 def do_process_from_csv_text(self):
1852 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1853 + _("Format: address, amount. One output per line"), _("Load CSV"))
1856 f = StringIO.StringIO(text)
1857 csvReader = csv.reader(f)
1858 self.do_process_from_csvReader(csvReader)
1863 def export_privkeys_dialog(self, password):
1864 if self.wallet.is_watching_only():
1865 self.show_message(_("This is a watching-only wallet"))
1869 d.setWindowTitle(_('Private keys'))
1870 d.setMinimumSize(850, 300)
1871 vbox = QVBoxLayout(d)
1873 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1874 _("Exposing a single private key can compromise your entire wallet!"),
1875 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1876 vbox.addWidget(QLabel(msg))
1882 defaultname = 'electrum-private-keys.csv'
1883 select_msg = _('Select file to export your private keys to')
1884 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1885 vbox.addLayout(hbox)
1887 h, b = ok_cancel_buttons2(d, _('Export'))
1892 addresses = self.wallet.addresses(True)
1894 def privkeys_thread():
1895 for addr in addresses:
1899 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1900 d.emit(SIGNAL('computing_privkeys'))
1901 d.emit(SIGNAL('show_privkeys'))
1903 def show_privkeys():
1904 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1908 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1909 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1910 threading.Thread(target=privkeys_thread).start()
1916 filename = filename_e.text()
1921 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1922 except (IOError, os.error), reason:
1923 export_error_label = _("Electrum was unable to produce a private key-export.")
1924 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1926 except Exception as e:
1927 self.show_message(str(e))
1930 self.show_message(_("Private keys exported."))
1933 def do_export_privkeys(self, fileName, pklist, is_csv):
1934 with open(fileName, "w+") as f:
1936 transaction = csv.writer(f)
1937 transaction.writerow(["address", "private_key"])
1938 for addr, pk in pklist.items():
1939 transaction.writerow(["%34s"%addr,pk])
1942 f.write(json.dumps(pklist, indent = 4))
1945 def do_import_labels(self):
1946 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1947 if not labelsFile: return
1949 f = open(labelsFile, 'r')
1952 for key, value in json.loads(data).items():
1953 self.wallet.set_label(key, value)
1954 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1955 except (IOError, os.error), reason:
1956 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1959 def do_export_labels(self):
1960 labels = self.wallet.labels
1962 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1964 with open(fileName, 'w+') as f:
1965 json.dump(labels, f)
1966 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1967 except (IOError, os.error), reason:
1968 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1971 def export_history_dialog(self):
1974 d.setWindowTitle(_('Export History'))
1975 d.setMinimumSize(400, 200)
1976 vbox = QVBoxLayout(d)
1978 defaultname = os.path.expanduser('~/electrum-history.csv')
1979 select_msg = _('Select file to export your wallet transactions to')
1981 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1982 vbox.addLayout(hbox)
1986 h, b = ok_cancel_buttons2(d, _('Export'))
1991 filename = filename_e.text()
1996 self.do_export_history(self.wallet, filename, csv_button.isChecked())
1997 except (IOError, os.error), reason:
1998 export_error_label = _("Electrum was unable to produce a transaction export.")
1999 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2002 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2005 def do_export_history(self, wallet, fileName, is_csv):
2006 history = wallet.get_tx_history()
2008 for item in history:
2009 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2011 if timestamp is not None:
2013 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2014 except [RuntimeError, TypeError, NameError] as reason:
2015 time_string = "unknown"
2018 time_string = "unknown"
2020 time_string = "pending"
2022 if value is not None:
2023 value_string = format_satoshis(value, True)
2028 fee_string = format_satoshis(fee, True)
2033 label, is_default_label = wallet.get_label(tx_hash)
2034 label = label.encode('utf-8')
2038 balance_string = format_satoshis(balance, False)
2040 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2042 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2044 with open(fileName, "w+") as f:
2046 transaction = csv.writer(f)
2047 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2049 transaction.writerow(line)
2052 f.write(json.dumps(lines, indent = 4))
2055 def sweep_key_dialog(self):
2057 d.setWindowTitle(_('Sweep private keys'))
2058 d.setMinimumSize(600, 300)
2060 vbox = QVBoxLayout(d)
2061 vbox.addWidget(QLabel(_("Enter private keys")))
2063 keys_e = QTextEdit()
2064 keys_e.setTabChangesFocus(True)
2065 vbox.addWidget(keys_e)
2067 h, address_e = address_field(self.wallet.addresses())
2071 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2072 vbox.addLayout(hbox)
2073 button.setEnabled(False)
2076 addr = str(address_e.text())
2077 if bitcoin.is_address(addr):
2081 pk = str(keys_e.toPlainText()).strip()
2082 if Wallet.is_private_key(pk):
2085 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2086 keys_e.textChanged.connect(f)
2087 address_e.textChanged.connect(f)
2091 fee = self.wallet.fee
2092 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2093 self.show_transaction(tx)
2097 def do_import_privkey(self, password):
2098 if not self.wallet.has_imported_keys():
2099 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2100 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2101 + _('Are you sure you understand what you are doing?'), 3, 4)
2104 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2107 text = str(text).split()
2112 addr = self.wallet.import_key(key, password)
2113 except Exception as e:
2119 addrlist.append(addr)
2121 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2123 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2124 self.update_receive_tab()
2125 self.update_history_tab()
2128 def settings_dialog(self):
2130 d.setWindowTitle(_('Electrum Settings'))
2132 vbox = QVBoxLayout()
2133 grid = QGridLayout()
2134 grid.setColumnStretch(0,1)
2136 nz_label = QLabel(_('Display zeros') + ':')
2137 grid.addWidget(nz_label, 0, 0)
2138 nz_e = AmountEdit(None,True)
2139 nz_e.setText("%d"% self.num_zeros)
2140 grid.addWidget(nz_e, 0, 1)
2141 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2142 grid.addWidget(HelpButton(msg), 0, 2)
2143 if not self.config.is_modifiable('num_zeros'):
2144 for w in [nz_e, nz_label]: w.setEnabled(False)
2146 lang_label=QLabel(_('Language') + ':')
2147 grid.addWidget(lang_label, 1, 0)
2148 lang_combo = QComboBox()
2149 from electrum.i18n import languages
2150 lang_combo.addItems(languages.values())
2152 index = languages.keys().index(self.config.get("language",''))
2155 lang_combo.setCurrentIndex(index)
2156 grid.addWidget(lang_combo, 1, 1)
2157 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2158 if not self.config.is_modifiable('language'):
2159 for w in [lang_combo, lang_label]: w.setEnabled(False)
2162 fee_label = QLabel(_('Transaction fee') + ':')
2163 grid.addWidget(fee_label, 2, 0)
2164 fee_e = AmountEdit(self.get_decimal_point)
2165 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2166 grid.addWidget(fee_e, 2, 1)
2167 msg = _('Fee per kilobyte of transaction.') + ' ' \
2168 + _('Recommended value') + ': ' + self.format_amount(20000)
2169 grid.addWidget(HelpButton(msg), 2, 2)
2170 if not self.config.is_modifiable('fee_per_kb'):
2171 for w in [fee_e, fee_label]: w.setEnabled(False)
2173 units = ['BTC', 'mBTC']
2174 unit_label = QLabel(_('Base unit') + ':')
2175 grid.addWidget(unit_label, 3, 0)
2176 unit_combo = QComboBox()
2177 unit_combo.addItems(units)
2178 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2179 grid.addWidget(unit_combo, 3, 1)
2180 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2181 + '\n1BTC=1000mBTC.\n' \
2182 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2184 usechange_cb = QCheckBox(_('Use change addresses'))
2185 usechange_cb.setChecked(self.wallet.use_change)
2186 grid.addWidget(usechange_cb, 4, 0)
2187 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2188 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2190 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2191 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2192 grid.addWidget(block_ex_label, 5, 0)
2193 block_ex_combo = QComboBox()
2194 block_ex_combo.addItems(block_explorers)
2195 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2196 grid.addWidget(block_ex_combo, 5, 1)
2197 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2199 show_tx = self.config.get('show_before_broadcast', False)
2200 showtx_cb = QCheckBox(_('Show before broadcast'))
2201 showtx_cb.setChecked(show_tx)
2202 grid.addWidget(showtx_cb, 6, 0)
2203 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2205 vbox.addLayout(grid)
2207 vbox.addLayout(ok_cancel_buttons(d))
2211 if not d.exec_(): return
2214 fee = self.fee_e.get_amount()
2216 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2219 self.wallet.set_fee(fee)
2221 nz = unicode(nz_e.text())
2226 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2229 if self.num_zeros != nz:
2231 self.config.set_key('num_zeros', nz, True)
2232 self.update_history_tab()
2233 self.update_receive_tab()
2235 usechange_result = usechange_cb.isChecked()
2236 if self.wallet.use_change != usechange_result:
2237 self.wallet.use_change = usechange_result
2238 self.wallet.storage.put('use_change', self.wallet.use_change)
2240 if showtx_cb.isChecked() != show_tx:
2241 self.config.set_key('show_before_broadcast', not show_tx)
2243 unit_result = units[unit_combo.currentIndex()]
2244 if self.base_unit() != unit_result:
2245 self.decimal_point = 8 if unit_result == 'BTC' else 5
2246 self.config.set_key('decimal_point', self.decimal_point, True)
2247 self.update_history_tab()
2248 self.update_status()
2250 need_restart = False
2252 lang_request = languages.keys()[lang_combo.currentIndex()]
2253 if lang_request != self.config.get('language'):
2254 self.config.set_key("language", lang_request, True)
2257 be_result = block_explorers[block_ex_combo.currentIndex()]
2258 self.config.set_key('block_explorer', be_result, True)
2260 run_hook('close_settings_dialog')
2263 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2266 def run_network_dialog(self):
2267 if not self.network:
2269 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2271 def closeEvent(self, event):
2273 self.config.set_key("is_maximized", self.isMaximized())
2274 if not self.isMaximized():
2276 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2277 self.save_column_widths()
2278 self.config.set_key("console-history", self.console.history[-50:], True)
2279 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2283 def plugins_dialog(self):
2284 from electrum.plugins import plugins
2287 d.setWindowTitle(_('Electrum Plugins'))
2290 vbox = QVBoxLayout(d)
2293 scroll = QScrollArea()
2294 scroll.setEnabled(True)
2295 scroll.setWidgetResizable(True)
2296 scroll.setMinimumSize(400,250)
2297 vbox.addWidget(scroll)
2301 w.setMinimumHeight(len(plugins)*35)
2303 grid = QGridLayout()
2304 grid.setColumnStretch(0,1)
2307 def do_toggle(cb, p, w):
2310 if w: w.setEnabled(r)
2312 def mk_toggle(cb, p, w):
2313 return lambda: do_toggle(cb,p,w)
2315 for i, p in enumerate(plugins):
2317 cb = QCheckBox(p.fullname())
2318 cb.setDisabled(not p.is_available())
2319 cb.setChecked(p.is_enabled())
2320 grid.addWidget(cb, i, 0)
2321 if p.requires_settings():
2322 w = p.settings_widget(self)
2323 w.setEnabled( p.is_enabled() )
2324 grid.addWidget(w, i, 1)
2327 cb.clicked.connect(mk_toggle(cb,p,w))
2328 grid.addWidget(HelpButton(p.description()), i, 2)
2330 print_msg(_("Error: cannot display plugin"), p)
2331 traceback.print_exc(file=sys.stdout)
2332 grid.setRowStretch(i+1,1)
2334 vbox.addLayout(close_button(d))
2339 def show_account_details(self, k):
2340 account = self.wallet.accounts[k]
2343 d.setWindowTitle(_('Account Details'))
2346 vbox = QVBoxLayout(d)
2347 name = self.wallet.get_account_name(k)
2348 label = QLabel('Name: ' + name)
2349 vbox.addWidget(label)
2351 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2353 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2355 vbox.addWidget(QLabel(_('Master Public Key:')))
2358 text.setReadOnly(True)
2359 text.setMaximumHeight(170)
2360 vbox.addWidget(text)
2362 mpk_text = '\n'.join( account.get_master_pubkeys() )
2363 text.setText(mpk_text)
2365 vbox.addLayout(close_button(d))