3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.tray = gui_object.tray
111 self.go_lite = gui_object.go_lite
114 self.create_status_bar()
115 self.need_update = threading.Event()
117 self.decimal_point = config.get('decimal_point', 5)
118 self.num_zeros = int(config.get('num_zeros',0))
120 set_language(config.get('language'))
122 self.funds_error = False
123 self.completions = QStringListModel()
125 self.tabs = tabs = QTabWidget(self)
126 self.column_widths = self.config.get("column_widths_2", default_column_widths )
127 tabs.addTab(self.create_history_tab(), _('History') )
128 tabs.addTab(self.create_send_tab(), _('Send') )
129 tabs.addTab(self.create_receive_tab(), _('Receive') )
130 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
131 tabs.addTab(self.create_console_tab(), _('Console') )
132 tabs.setMinimumSize(600, 400)
133 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
134 self.setCentralWidget(tabs)
136 g = self.config.get("winpos-qt",[100, 100, 840, 400])
137 self.setGeometry(g[0], g[1], g[2], g[3])
138 if self.config.get("is_maximized"):
141 self.setWindowIcon(QIcon(":icons/electrum.png"))
144 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
145 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
146 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
147 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
148 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
150 for i in range(tabs.count()):
151 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
153 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
154 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
155 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
156 self.connect(self, QtCore.SIGNAL('send_tx2'), self.send_tx2)
157 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
159 self.history_list.setFocus(True)
163 self.network.register_callback('updated', lambda: self.need_update.set())
164 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
165 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
166 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
167 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
169 # set initial message
170 self.console.showMessage(self.network.banner)
175 def update_account_selector(self):
177 accounts = self.wallet.get_account_names()
178 self.account_selector.clear()
179 if len(accounts) > 1:
180 self.account_selector.addItems([_("All accounts")] + accounts.values())
181 self.account_selector.setCurrentIndex(0)
182 self.account_selector.show()
184 self.account_selector.hide()
187 def load_wallet(self, wallet):
190 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
191 self.current_account = self.wallet.storage.get("current_account", None)
193 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
194 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
195 self.setWindowTitle( title )
197 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
198 self.notify_transactions()
199 self.update_account_selector()
201 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
202 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
203 self.password_menu.setEnabled(not self.wallet.is_watching_only())
204 self.seed_menu.setEnabled(self.wallet.has_seed())
205 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
207 self.update_lock_icon()
208 self.update_buttons_on_seed()
209 self.update_console()
211 run_hook('load_wallet', wallet)
214 def open_wallet(self):
215 wallet_folder = self.wallet.storage.path
216 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
220 storage = WalletStorage({'wallet_path': filename})
221 if not storage.file_exists:
222 self.show_message("file not found "+ filename)
225 self.wallet.stop_threads()
228 wallet = Wallet(storage)
229 wallet.start_threads(self.network)
231 self.load_wallet(wallet)
235 def backup_wallet(self):
237 path = self.wallet.storage.path
238 wallet_folder = os.path.dirname(path)
239 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
243 new_path = os.path.join(wallet_folder, filename)
246 shutil.copy2(path, new_path)
247 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
248 except (IOError, os.error), reason:
249 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
252 def new_wallet(self):
255 wallet_folder = os.path.dirname(self.wallet.storage.path)
256 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
259 filename = os.path.join(wallet_folder, filename)
261 storage = WalletStorage({'wallet_path': filename})
262 if storage.file_exists:
263 QMessageBox.critical(None, "Error", _("File exists"))
266 wizard = installwizard.InstallWizard(self.config, self.network, storage)
267 wallet = wizard.run('new')
269 self.load_wallet(wallet)
273 def init_menubar(self):
276 file_menu = menubar.addMenu(_("&File"))
277 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
278 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
279 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
280 file_menu.addAction(_("&Quit"), self.close)
282 wallet_menu = menubar.addMenu(_("&Wallet"))
283 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
284 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
286 wallet_menu.addSeparator()
288 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
289 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
290 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
292 wallet_menu.addSeparator()
293 labels_menu = wallet_menu.addMenu(_("&Labels"))
294 labels_menu.addAction(_("&Import"), self.do_import_labels)
295 labels_menu.addAction(_("&Export"), self.do_export_labels)
297 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
298 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
299 self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
300 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
302 wallet_menu.addAction(_("&Export History"), self.do_export_history)
304 tools_menu = menubar.addMenu(_("&Tools"))
306 # Settings / Preferences are all reserved keywords in OSX using this as work around
307 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
308 tools_menu.addAction(_("&Network"), self.run_network_dialog)
309 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
310 tools_menu.addSeparator()
311 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
312 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
313 tools_menu.addSeparator()
315 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
316 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
317 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
319 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
320 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
321 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
322 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
324 help_menu = menubar.addMenu(_("&Help"))
325 help_menu.addAction(_("&About"), self.show_about)
326 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
327 help_menu.addSeparator()
328 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
329 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
331 self.setMenuBar(menubar)
333 def show_about(self):
334 QMessageBox.about(self, "Electrum",
335 _("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."))
337 def show_report_bug(self):
338 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
339 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
342 def notify_transactions(self):
343 if not self.network or not self.network.is_connected():
346 print_error("Notifying GUI")
347 if len(self.network.pending_transactions_for_notifications) > 0:
348 # Combine the transactions if there are more then three
349 tx_amount = len(self.network.pending_transactions_for_notifications)
352 for tx in self.network.pending_transactions_for_notifications:
353 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
357 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
358 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
360 self.network.pending_transactions_for_notifications = []
362 for tx in self.network.pending_transactions_for_notifications:
364 self.network.pending_transactions_for_notifications.remove(tx)
365 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
367 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
369 def notify(self, message):
370 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
374 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
375 def getOpenFileName(self, title, filter = ""):
376 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
377 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
378 if fileName and directory != os.path.dirname(fileName):
379 self.config.set_key('io_dir', os.path.dirname(fileName), True)
382 def getSaveFileName(self, title, filename, filter = ""):
383 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
384 path = os.path.join( directory, filename )
385 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
386 if fileName and directory != os.path.dirname(fileName):
387 self.config.set_key('io_dir', os.path.dirname(fileName), True)
391 QMainWindow.close(self)
392 run_hook('close_main_window')
394 def connect_slots(self, sender):
395 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
396 self.previous_payto_e=''
398 def timer_actions(self):
399 if self.need_update.is_set():
401 self.need_update.clear()
402 run_hook('timer_actions')
404 def format_amount(self, x, is_diff=False, whitespaces=False):
405 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
407 def read_amount(self, x):
408 if x in['.', '']: return None
409 p = pow(10, self.decimal_point)
410 return int( p * Decimal(x) )
413 assert self.decimal_point in [5,8]
414 return "BTC" if self.decimal_point == 8 else "mBTC"
417 def update_status(self):
418 if self.network is None or not self.network.is_running():
420 icon = QIcon(":icons/status_disconnected.png")
422 elif self.network.is_connected():
423 if not self.wallet.up_to_date:
424 text = _("Synchronizing...")
425 icon = QIcon(":icons/status_waiting.png")
426 elif self.network.server_lag > 1:
427 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
428 icon = QIcon(":icons/status_lagging.png")
430 c, u = self.wallet.get_account_balance(self.current_account)
431 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
432 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
434 # append fiat balance and price from exchange rate plugin
436 run_hook('get_fiat_status_text', c+u, r)
441 self.tray.setToolTip(text)
442 icon = QIcon(":icons/status_connected.png")
444 text = _("Not connected")
445 icon = QIcon(":icons/status_disconnected.png")
447 self.balance_label.setText(text)
448 self.status_button.setIcon( icon )
451 def update_wallet(self):
453 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
454 self.update_history_tab()
455 self.update_receive_tab()
456 self.update_contacts_tab()
457 self.update_completions()
460 def create_history_tab(self):
461 self.history_list = l = MyTreeWidget(self)
463 for i,width in enumerate(self.column_widths['history']):
464 l.setColumnWidth(i, width)
465 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
466 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
467 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
469 l.customContextMenuRequested.connect(self.create_history_menu)
473 def create_history_menu(self, position):
474 self.history_list.selectedIndexes()
475 item = self.history_list.currentItem()
476 be = self.config.get('block_explorer', 'Blockchain.info')
477 if be == 'Blockchain.info':
478 block_explorer = 'https://blockchain.info/tx/'
479 elif be == 'Blockr.io':
480 block_explorer = 'https://blockr.io/tx/info/'
481 elif be == 'Insight.is':
482 block_explorer = 'http://live.insight.is/tx/'
484 tx_hash = str(item.data(0, Qt.UserRole).toString())
485 if not tx_hash: return
487 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
488 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
489 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
490 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
491 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
494 def show_transaction(self, tx):
495 import transaction_dialog
496 d = transaction_dialog.TxDialog(tx, self)
499 def tx_label_clicked(self, item, column):
500 if column==2 and item.isSelected():
502 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
503 self.history_list.editItem( item, column )
504 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
507 def tx_label_changed(self, item, column):
511 tx_hash = str(item.data(0, Qt.UserRole).toString())
512 tx = self.wallet.transactions.get(tx_hash)
513 text = unicode( item.text(2) )
514 self.wallet.set_label(tx_hash, text)
516 item.setForeground(2, QBrush(QColor('black')))
518 text = self.wallet.get_default_label(tx_hash)
519 item.setText(2, text)
520 item.setForeground(2, QBrush(QColor('gray')))
524 def edit_label(self, is_recv):
525 l = self.receive_list if is_recv else self.contacts_list
526 item = l.currentItem()
527 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
528 l.editItem( item, 1 )
529 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
533 def address_label_clicked(self, item, column, l, column_addr, column_label):
534 if column == column_label and item.isSelected():
535 is_editable = item.data(0, 32).toBool()
538 addr = unicode( item.text(column_addr) )
539 label = unicode( item.text(column_label) )
540 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
541 l.editItem( item, column )
542 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
545 def address_label_changed(self, item, column, l, column_addr, column_label):
546 if column == column_label:
547 addr = unicode( item.text(column_addr) )
548 text = unicode( item.text(column_label) )
549 is_editable = item.data(0, 32).toBool()
553 changed = self.wallet.set_label(addr, text)
555 self.update_history_tab()
556 self.update_completions()
558 self.current_item_changed(item)
560 run_hook('item_changed', item, column)
563 def current_item_changed(self, a):
564 run_hook('current_item_changed', a)
568 def update_history_tab(self):
570 self.history_list.clear()
571 for item in self.wallet.get_tx_history(self.current_account):
572 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
573 time_str = _("unknown")
576 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
578 time_str = _("error")
581 time_str = 'unverified'
582 icon = QIcon(":icons/unconfirmed.png")
585 icon = QIcon(":icons/unconfirmed.png")
587 icon = QIcon(":icons/clock%d.png"%conf)
589 icon = QIcon(":icons/confirmed.png")
591 if value is not None:
592 v_str = self.format_amount(value, True, whitespaces=True)
596 balance_str = self.format_amount(balance, whitespaces=True)
599 label, is_default_label = self.wallet.get_label(tx_hash)
601 label = _('Pruned transaction outputs')
602 is_default_label = False
604 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
605 item.setFont(2, QFont(MONOSPACE_FONT))
606 item.setFont(3, QFont(MONOSPACE_FONT))
607 item.setFont(4, QFont(MONOSPACE_FONT))
609 item.setForeground(3, QBrush(QColor("#BC1E1E")))
611 item.setData(0, Qt.UserRole, tx_hash)
612 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
614 item.setForeground(2, QBrush(QColor('grey')))
616 item.setIcon(0, icon)
617 self.history_list.insertTopLevelItem(0,item)
620 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
621 run_hook('history_tab_update')
624 def create_send_tab(self):
629 grid.setColumnMinimumWidth(3,300)
630 grid.setColumnStretch(5,1)
633 self.payto_e = QLineEdit()
634 grid.addWidget(QLabel(_('Pay to')), 1, 0)
635 grid.addWidget(self.payto_e, 1, 1, 1, 3)
637 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
639 completer = QCompleter()
640 completer.setCaseSensitivity(False)
641 self.payto_e.setCompleter(completer)
642 completer.setModel(self.completions)
644 self.message_e = QLineEdit()
645 grid.addWidget(QLabel(_('Description')), 2, 0)
646 grid.addWidget(self.message_e, 2, 1, 1, 3)
647 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
649 self.from_label = QLabel(_('From'))
650 grid.addWidget(self.from_label, 3, 0)
651 self.from_list = QTreeWidget(self)
652 self.from_list.setColumnCount(2)
653 self.from_list.setColumnWidth(0, 350)
654 self.from_list.setColumnWidth(1, 50)
655 self.from_list.setHeaderHidden (True)
656 self.from_list.setMaximumHeight(80)
657 grid.addWidget(self.from_list, 3, 1, 1, 3)
658 self.set_pay_from([])
660 self.amount_e = AmountEdit(self.base_unit)
661 grid.addWidget(QLabel(_('Amount')), 4, 0)
662 grid.addWidget(self.amount_e, 4, 1, 1, 2)
663 grid.addWidget(HelpButton(
664 _('Amount to be sent.') + '\n\n' \
665 + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') \
666 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
668 self.fee_e = AmountEdit(self.base_unit)
669 grid.addWidget(QLabel(_('Fee')), 5, 0)
670 grid.addWidget(self.fee_e, 5, 1, 1, 2)
671 grid.addWidget(HelpButton(
672 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
673 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
674 + _('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)
676 run_hook('exchange_rate_button', grid)
678 self.send_button = EnterButton(_("Send"), self.do_send)
679 grid.addWidget(self.send_button, 6, 1)
681 b = EnterButton(_("Clear"),self.do_clear)
682 grid.addWidget(b, 6, 2)
684 self.payto_sig = QLabel('')
685 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
687 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
688 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
697 def entry_changed( is_fee ):
698 self.funds_error = False
700 if self.amount_e.is_shortcut:
701 self.amount_e.is_shortcut = False
702 sendable = self.get_sendable_balance()
703 # there is only one output because we are completely spending inputs
704 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
705 fee = self.wallet.estimated_fee(inputs, 1)
707 self.amount_e.setText( self.format_amount(amount) )
708 self.fee_e.setText( self.format_amount( fee ) )
711 amount = self.read_amount(str(self.amount_e.text()))
712 fee = self.read_amount(str(self.fee_e.text()))
714 if not is_fee: fee = None
717 # assume that there will be 2 outputs (one for change)
718 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
720 self.fee_e.setText( self.format_amount( fee ) )
723 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
727 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
728 self.funds_error = True
729 text = _( "Not enough funds" )
730 c, u = self.wallet.get_frozen_balance()
731 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
733 self.statusBar().showMessage(text)
734 self.amount_e.setPalette(palette)
735 self.fee_e.setPalette(palette)
737 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
738 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
740 run_hook('create_send_tab', grid)
744 def set_pay_from(self, l):
746 self.from_list.clear()
747 self.from_label.setHidden(len(self.pay_from) == 0)
748 self.from_list.setHidden(len(self.pay_from) == 0)
749 for addr in self.pay_from:
750 c, u = self.wallet.get_addr_balance(addr)
751 balance = self.format_amount(c + u)
752 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
755 def update_completions(self):
757 for addr,label in self.wallet.labels.items():
758 if addr in self.wallet.addressbook:
759 l.append( label + ' <' + addr + '>')
761 run_hook('update_completions', l)
762 self.completions.setStringList(l)
766 return lambda s, *args: s.do_protect(func, args)
771 label = unicode( self.message_e.text() )
772 r = unicode( self.payto_e.text() )
775 # label or alias, with address in brackets
776 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
777 to_address = m.group(2) if m else r
779 if not is_valid(to_address):
780 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
784 amount = self.read_amount(unicode( self.amount_e.text()))
786 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
789 fee = self.read_amount(unicode( self.fee_e.text()))
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(to_address, amount, fee, label)
807 def waiting_dialog(self, message):
809 d.setWindowTitle('Please wait')
811 vbox = QVBoxLayout(d)
818 def send_tx(self, to_address, amount, fee, label, password):
820 # first, create an unsigned tx
821 domain = self.get_payment_sources()
822 outputs = [(to_address, amount)]
824 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
826 except Exception as e:
827 traceback.print_exc(file=sys.stdout)
828 self.show_message(str(e))
831 # call hook to see if plugin needs gui interaction
832 run_hook('send_tx', tx)
838 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
839 self.wallet.sign_transaction(tx, keypairs, password)
840 self.signed_tx_data = (tx, fee, label)
841 self.emit(SIGNAL('send_tx2'))
842 self.tx_wait_dialog = self.waiting_dialog('Signing..')
843 threading.Thread(target=sign_thread).start()
845 # add recipient to addressbook
846 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
847 self.wallet.addressbook.append(to_address)
851 tx, fee, label = self.signed_tx_data
852 self.tx_wait_dialog.accept()
855 self.show_message(tx.error)
858 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
859 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
863 self.wallet.set_label(tx.hash(), label)
865 if not tx.is_complete() or self.config.get('show_before_broadcast'):
866 self.show_transaction(tx)
870 def broadcast_thread():
871 self.tx_broadcast_result = self.wallet.sendtx(tx)
872 self.emit(SIGNAL('send_tx3'))
873 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
874 threading.Thread(target=broadcast_thread).start()
878 self.tx_broadcast_dialog.accept()
879 status, msg = self.tx_broadcast_result
881 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
883 self.update_contacts_tab()
885 QMessageBox.warning(self, _('Error'), msg, _('OK'))
893 def set_send(self, address, amount, label, message):
895 if label and self.wallet.labels.get(address) != label:
896 if self.question('Give label "%s" to address %s ?'%(label,address)):
897 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
898 self.wallet.addressbook.append(address)
899 self.wallet.set_label(address, label)
901 self.tabs.setCurrentIndex(1)
902 label = self.wallet.labels.get(address)
903 m_addr = label + ' <'+ address +'>' if label else address
904 self.payto_e.setText(m_addr)
906 self.message_e.setText(message)
908 self.amount_e.setText(amount)
912 self.payto_sig.setVisible(False)
913 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
915 self.set_frozen(e,False)
917 self.set_pay_from([])
920 def set_frozen(self,entry,frozen):
922 entry.setReadOnly(True)
923 entry.setFrame(False)
925 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
926 entry.setPalette(palette)
928 entry.setReadOnly(False)
931 palette.setColor(entry.backgroundRole(), QColor('white'))
932 entry.setPalette(palette)
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
1177 for i,width in enumerate(self.column_widths['receive']):
1178 l.setColumnWidth(i, width)
1180 accounts = self.wallet.get_accounts()
1181 if self.current_account is None:
1182 account_items = sorted(accounts.items())
1184 account_items = [(self.current_account, accounts.get(self.current_account))]
1187 for k, account in account_items:
1189 if len(accounts) > 1:
1190 name = self.wallet.get_account_name(k)
1191 c,u = self.wallet.get_account_balance(k)
1192 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1193 l.addTopLevelItem(account_item)
1194 account_item.setExpanded(self.accounts_expanded.get(k, True))
1195 account_item.setData(0, 32, k)
1199 sequences = [0,1] if account.has_change() else [0]
1200 for is_change in sequences:
1201 if len(sequences) > 1:
1202 name = _("Receiving") if not is_change else _("Change")
1203 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1204 account_item.addChild(seq_item)
1206 seq_item.setExpanded(True)
1208 seq_item = account_item
1210 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1216 for address in account.get_addresses(is_change):
1217 h = self.wallet.history.get(address,[])
1221 if gap > self.wallet.gap_limit:
1226 c, u = self.wallet.get_addr_balance(address)
1227 num_tx = '*' if h == ['*'] else "%d"%len(h)
1229 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1230 self.update_receive_item(item)
1232 item.setBackgroundColor(1, QColor('red'))
1233 if len(h) > 0 and c == -u:
1235 seq_item.insertChild(0,used_item)
1237 used_item.addChild(item)
1239 seq_item.addChild(item)
1241 # we use column 1 because column 0 may be hidden
1242 l.setCurrentItem(l.topLevelItem(0),1)
1245 def update_contacts_tab(self):
1246 l = self.contacts_list
1249 for address in self.wallet.addressbook:
1250 label = self.wallet.labels.get(address,'')
1251 n = self.wallet.get_num_tx(address)
1252 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1253 item.setFont(0, QFont(MONOSPACE_FONT))
1254 # 32 = label can be edited (bool)
1255 item.setData(0,32, True)
1257 item.setData(0,33, address)
1258 l.addTopLevelItem(item)
1260 run_hook('update_contacts_tab', l)
1261 l.setCurrentItem(l.topLevelItem(0))
1265 def create_console_tab(self):
1266 from console import Console
1267 self.console = console = Console()
1271 def update_console(self):
1272 console = self.console
1273 console.history = self.config.get("console-history",[])
1274 console.history_index = len(console.history)
1276 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1277 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1279 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1281 def mkfunc(f, method):
1282 return lambda *args: apply( f, (method, args, self.password_dialog ))
1284 if m[0]=='_' or m in ['network','wallet']: continue
1285 methods[m] = mkfunc(c._run, m)
1287 console.updateNamespace(methods)
1290 def change_account(self,s):
1291 if s == _("All accounts"):
1292 self.current_account = None
1294 accounts = self.wallet.get_account_names()
1295 for k, v in accounts.items():
1297 self.current_account = k
1298 self.update_history_tab()
1299 self.update_status()
1300 self.update_receive_tab()
1302 def create_status_bar(self):
1305 sb.setFixedHeight(35)
1306 qtVersion = qVersion()
1308 self.balance_label = QLabel("")
1309 sb.addWidget(self.balance_label)
1311 from version_getter import UpdateLabel
1312 self.updatelabel = UpdateLabel(self.config, sb)
1314 self.account_selector = QComboBox()
1315 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1316 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1317 sb.addPermanentWidget(self.account_selector)
1319 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1320 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1322 self.lock_icon = QIcon()
1323 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1324 sb.addPermanentWidget( self.password_button )
1326 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1327 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1328 sb.addPermanentWidget( self.seed_button )
1329 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1330 sb.addPermanentWidget( self.status_button )
1332 run_hook('create_status_bar', (sb,))
1334 self.setStatusBar(sb)
1337 def update_lock_icon(self):
1338 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1339 self.password_button.setIcon( icon )
1342 def update_buttons_on_seed(self):
1343 if self.wallet.has_seed():
1344 self.seed_button.show()
1346 self.seed_button.hide()
1348 if not self.wallet.is_watching_only():
1349 self.password_button.show()
1350 self.send_button.setText(_("Send"))
1352 self.password_button.hide()
1353 self.send_button.setText(_("Create unsigned transaction"))
1356 def change_password_dialog(self):
1357 from password_dialog import PasswordDialog
1358 d = PasswordDialog(self.wallet, self)
1360 self.update_lock_icon()
1363 def new_contact_dialog(self):
1366 d.setWindowTitle(_("New Contact"))
1367 vbox = QVBoxLayout(d)
1368 vbox.addWidget(QLabel(_('New Contact')+':'))
1370 grid = QGridLayout()
1373 grid.addWidget(QLabel(_("Address")), 1, 0)
1374 grid.addWidget(line1, 1, 1)
1375 grid.addWidget(QLabel(_("Name")), 2, 0)
1376 grid.addWidget(line2, 2, 1)
1378 vbox.addLayout(grid)
1379 vbox.addLayout(ok_cancel_buttons(d))
1384 address = str(line1.text())
1385 label = unicode(line2.text())
1387 if not is_valid(address):
1388 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1391 self.wallet.add_contact(address)
1393 self.wallet.set_label(address, label)
1395 self.update_contacts_tab()
1396 self.update_history_tab()
1397 self.update_completions()
1398 self.tabs.setCurrentIndex(3)
1402 def new_account_dialog(self, password):
1404 dialog = QDialog(self)
1406 dialog.setWindowTitle(_("New Account"))
1408 vbox = QVBoxLayout()
1409 vbox.addWidget(QLabel(_('Account name')+':'))
1412 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1413 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1418 vbox.addLayout(ok_cancel_buttons(dialog))
1419 dialog.setLayout(vbox)
1423 name = str(e.text())
1426 self.wallet.create_pending_account(name, password)
1427 self.update_receive_tab()
1428 self.tabs.setCurrentIndex(2)
1433 def show_master_public_keys(self):
1435 dialog = QDialog(self)
1437 dialog.setWindowTitle(_("Master Public Keys"))
1439 main_layout = QGridLayout()
1440 mpk_dict = self.wallet.get_master_public_keys()
1442 for key, value in mpk_dict.items():
1443 main_layout.addWidget(QLabel(key), i, 0)
1444 mpk_text = QTextEdit()
1445 mpk_text.setReadOnly(True)
1446 mpk_text.setMaximumHeight(170)
1447 mpk_text.setText(value)
1448 main_layout.addWidget(mpk_text, i + 1, 0)
1451 vbox = QVBoxLayout()
1452 vbox.addLayout(main_layout)
1453 vbox.addLayout(close_button(dialog))
1455 dialog.setLayout(vbox)
1460 def show_seed_dialog(self, password):
1461 if not self.wallet.has_seed():
1462 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1466 mnemonic = self.wallet.get_mnemonic(password)
1468 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1470 from seed_dialog import SeedDialog
1471 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1476 def show_qrcode(self, data, title = _("QR code")):
1480 d.setWindowTitle(title)
1481 d.setMinimumSize(270, 300)
1482 vbox = QVBoxLayout()
1483 qrw = QRCodeWidget(data)
1484 vbox.addWidget(qrw, 1)
1485 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1486 hbox = QHBoxLayout()
1489 filename = os.path.join(self.config.path, "qrcode.bmp")
1492 bmp.save_qrcode(qrw.qr, filename)
1493 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1495 def copy_to_clipboard():
1496 bmp.save_qrcode(qrw.qr, filename)
1497 self.app.clipboard().setImage(QImage(filename))
1498 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1500 b = QPushButton(_("Copy"))
1502 b.clicked.connect(copy_to_clipboard)
1504 b = QPushButton(_("Save"))
1506 b.clicked.connect(print_qr)
1508 b = QPushButton(_("Close"))
1510 b.clicked.connect(d.accept)
1513 vbox.addLayout(hbox)
1518 def do_protect(self, func, args):
1519 if self.wallet.use_encryption:
1520 password = self.password_dialog()
1526 if args != (False,):
1527 args = (self,) + args + (password,)
1529 args = (self,password)
1533 def show_public_keys(self, address):
1534 if not address: return
1536 pubkey_list = self.wallet.get_public_keys(address)
1537 except Exception as e:
1538 traceback.print_exc(file=sys.stdout)
1539 self.show_message(str(e))
1543 d.setMinimumSize(600, 200)
1545 vbox = QVBoxLayout()
1546 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1547 vbox.addWidget( QLabel(_("Public key") + ':'))
1549 keys.setReadOnly(True)
1550 keys.setText('\n'.join(pubkey_list))
1551 vbox.addWidget(keys)
1552 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1553 vbox.addLayout(close_button(d))
1558 def show_private_key(self, address, password):
1559 if not address: return
1561 pk_list = self.wallet.get_private_key(address, password)
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(_("Private key") + ':'))
1574 keys.setReadOnly(True)
1575 keys.setText('\n'.join(pk_list))
1576 vbox.addWidget(keys)
1577 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1578 vbox.addLayout(close_button(d))
1584 def do_sign(self, address, message, signature, password):
1585 message = unicode(message.toPlainText())
1586 message = message.encode('utf-8')
1588 sig = self.wallet.sign_message(str(address.text()), message, password)
1589 signature.setText(sig)
1590 except Exception as e:
1591 self.show_message(str(e))
1593 def do_verify(self, address, message, signature):
1594 message = unicode(message.toPlainText())
1595 message = message.encode('utf-8')
1596 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1597 self.show_message(_("Signature verified"))
1599 self.show_message(_("Error: wrong signature"))
1602 def sign_verify_message(self, address=''):
1605 d.setWindowTitle(_('Sign/verify Message'))
1606 d.setMinimumSize(410, 290)
1608 layout = QGridLayout(d)
1610 message_e = QTextEdit()
1611 layout.addWidget(QLabel(_('Message')), 1, 0)
1612 layout.addWidget(message_e, 1, 1)
1613 layout.setRowStretch(2,3)
1615 address_e = QLineEdit()
1616 address_e.setText(address)
1617 layout.addWidget(QLabel(_('Address')), 2, 0)
1618 layout.addWidget(address_e, 2, 1)
1620 signature_e = QTextEdit()
1621 layout.addWidget(QLabel(_('Signature')), 3, 0)
1622 layout.addWidget(signature_e, 3, 1)
1623 layout.setRowStretch(3,1)
1625 hbox = QHBoxLayout()
1627 b = QPushButton(_("Sign"))
1628 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1631 b = QPushButton(_("Verify"))
1632 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1635 b = QPushButton(_("Close"))
1636 b.clicked.connect(d.accept)
1638 layout.addLayout(hbox, 4, 1)
1643 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1645 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1646 message_e.setText(decrypted)
1647 except Exception as e:
1648 self.show_message(str(e))
1651 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1652 message = unicode(message_e.toPlainText())
1653 message = message.encode('utf-8')
1655 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1656 encrypted_e.setText(encrypted)
1657 except Exception as e:
1658 self.show_message(str(e))
1662 def encrypt_message(self, address = ''):
1665 d.setWindowTitle(_('Encrypt/decrypt Message'))
1666 d.setMinimumSize(610, 490)
1668 layout = QGridLayout(d)
1670 message_e = QTextEdit()
1671 layout.addWidget(QLabel(_('Message')), 1, 0)
1672 layout.addWidget(message_e, 1, 1)
1673 layout.setRowStretch(2,3)
1675 pubkey_e = QLineEdit()
1677 pubkey = self.wallet.getpubkeys(address)[0]
1678 pubkey_e.setText(pubkey)
1679 layout.addWidget(QLabel(_('Public key')), 2, 0)
1680 layout.addWidget(pubkey_e, 2, 1)
1682 encrypted_e = QTextEdit()
1683 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1684 layout.addWidget(encrypted_e, 3, 1)
1685 layout.setRowStretch(3,1)
1687 hbox = QHBoxLayout()
1688 b = QPushButton(_("Encrypt"))
1689 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1692 b = QPushButton(_("Decrypt"))
1693 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1696 b = QPushButton(_("Close"))
1697 b.clicked.connect(d.accept)
1700 layout.addLayout(hbox, 4, 1)
1704 def question(self, msg):
1705 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1707 def show_message(self, msg):
1708 QMessageBox.information(self, _('Message'), msg, _('OK'))
1710 def password_dialog(self ):
1713 d.setWindowTitle(_("Enter Password"))
1718 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 assert "complete" in tx_dict.keys()
1755 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1756 if not tx_dict["complete"]:
1757 assert "input_info" in tx_dict.keys()
1758 input_info = json.loads(tx_dict['input_info'])
1759 tx.add_input_info(input_info)
1764 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1768 def read_tx_from_file(self):
1769 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1773 with open(fileName, "r") as f:
1774 file_content = f.read()
1775 except (ValueError, IOError, os.error), reason:
1776 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1778 return self.tx_from_text(file_content)
1782 def sign_raw_transaction(self, tx, input_info, password):
1783 self.wallet.signrawtransaction(tx, input_info, [], password)
1785 def do_process_from_text(self):
1786 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1789 tx = self.tx_from_text(text)
1791 self.show_transaction(tx)
1793 def do_process_from_file(self):
1794 tx = self.read_tx_from_file()
1796 self.show_transaction(tx)
1798 def do_process_from_txid(self):
1799 from electrum import transaction
1800 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1802 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1804 tx = transaction.Transaction(r)
1806 self.show_transaction(tx)
1808 self.show_message("unknown transaction")
1810 def do_process_from_csvReader(self, csvReader):
1815 for position, row in enumerate(csvReader):
1817 if not is_valid(address):
1818 errors.append((position, address))
1820 amount = Decimal(row[1])
1821 amount = int(100000000*amount)
1822 outputs.append((address, amount))
1823 except (ValueError, IOError, os.error), reason:
1824 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1828 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1829 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1833 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1834 except Exception as e:
1835 self.show_message(str(e))
1838 self.show_transaction(tx)
1840 def do_process_from_csv_file(self):
1841 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1845 with open(fileName, "r") as f:
1846 csvReader = csv.reader(f)
1847 self.do_process_from_csvReader(csvReader)
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))
1852 def do_process_from_csv_text(self):
1853 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1854 + _("Format: address, amount. One output per line"), _("Load CSV"))
1857 f = StringIO.StringIO(text)
1858 csvReader = csv.reader(f)
1859 self.do_process_from_csvReader(csvReader)
1864 def export_privkeys_dialog(self, password):
1865 if self.wallet.is_watching_only():
1866 self.show_message(_("This is a watching-only wallet"))
1870 d.setWindowTitle(_('Private keys'))
1871 d.setMinimumSize(850, 300)
1872 vbox = QVBoxLayout(d)
1874 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1875 _("Exposing a single private key can compromise your entire wallet!"),
1876 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1877 vbox.addWidget(QLabel(msg))
1883 hbox = QHBoxLayout()
1884 vbox.addLayout(hbox)
1886 defaultname = 'electrum-private-keys.csv'
1887 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
1888 path = os.path.join( directory, defaultname )
1889 filename_e = QLineEdit()
1890 filename_e.setText(path)
1892 select_export = _('Select file to export your private keys to')
1893 p = self.getSaveFileName(select_export, defaultname, "*.csv")
1895 filename_e.setText(p)
1897 button = QPushButton(_('File'))
1898 button.clicked.connect(func)
1899 hbox.addWidget(button)
1900 hbox.addWidget(filename_e)
1902 h, b = ok_cancel_buttons2(d, _('Export'))
1907 addresses = self.wallet.addresses(True)
1909 def privkeys_thread():
1910 for addr in addresses:
1914 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1915 d.emit(SIGNAL('computing_privkeys'))
1916 d.emit(SIGNAL('show_privkeys'))
1918 def show_privkeys():
1919 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1923 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1924 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1925 threading.Thread(target=privkeys_thread).start()
1931 filename = filename_e.text()
1936 self.do_export_privkeys(filename, private_keys)
1937 except (IOError, os.error), reason:
1938 export_error_label = _("Electrum was unable to produce a private key-export.")
1939 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1941 except Exception as e:
1942 self.show_message(str(e))
1945 self.show_message(_("Private keys exported."))
1948 def do_export_privkeys(self, fileName, pklist):
1949 with open(fileName, "w+") as csvfile:
1950 transaction = csv.writer(csvfile)
1951 transaction.writerow(["address", "private_key"])
1952 for addr, pk in pklist.items():
1953 transaction.writerow(["%34s"%addr,pk])
1956 def do_import_labels(self):
1957 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1958 if not labelsFile: return
1960 f = open(labelsFile, 'r')
1963 for key, value in json.loads(data).items():
1964 self.wallet.set_label(key, value)
1965 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1966 except (IOError, os.error), reason:
1967 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1970 def do_export_labels(self):
1971 labels = self.wallet.labels
1973 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1975 with open(fileName, 'w+') as f:
1976 json.dump(labels, f)
1977 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1978 except (IOError, os.error), reason:
1979 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1982 def do_export_history(self):
1983 wallet = self.wallet
1984 select_export = _('Select file to export your wallet transactions to')
1985 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
1990 with open(fileName, "w+") as csvfile:
1991 transaction = csv.writer(csvfile)
1992 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
1993 for item in wallet.get_tx_history():
1994 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
1996 if timestamp is not None:
1998 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
1999 except [RuntimeError, TypeError, NameError] as reason:
2000 time_string = "unknown"
2003 time_string = "unknown"
2005 time_string = "pending"
2007 if value is not None:
2008 value_string = format_satoshis(value, True)
2013 fee_string = format_satoshis(fee, True)
2018 label, is_default_label = wallet.get_label(tx_hash)
2019 label = label.encode('utf-8')
2023 balance_string = format_satoshis(balance, False)
2024 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2025 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
2027 except (IOError, os.error), reason:
2028 export_error_label = _("Electrum was unable to produce a transaction export.")
2029 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
2032 def sweep_key_dialog(self):
2034 d.setWindowTitle(_('Sweep private keys'))
2035 d.setMinimumSize(600, 300)
2037 vbox = QVBoxLayout(d)
2038 vbox.addWidget(QLabel(_("Enter private keys")))
2040 keys_e = QTextEdit()
2041 keys_e.setTabChangesFocus(True)
2042 vbox.addWidget(keys_e)
2044 h, address_e = address_field(self.wallet.addresses())
2048 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2049 vbox.addLayout(hbox)
2050 button.setEnabled(False)
2053 addr = str(address_e.text())
2054 if bitcoin.is_address(addr):
2058 pk = str(keys_e.toPlainText()).strip()
2059 if Wallet.is_private_key(pk):
2062 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2063 keys_e.textChanged.connect(f)
2064 address_e.textChanged.connect(f)
2068 fee = self.wallet.fee
2069 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2070 self.show_transaction(tx)
2074 def do_import_privkey(self, password):
2075 if not self.wallet.imported_keys:
2076 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2077 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2078 + _('Are you sure you understand what you are doing?'), 3, 4)
2081 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2084 text = str(text).split()
2089 addr = self.wallet.import_key(key, password)
2090 except Exception as e:
2096 addrlist.append(addr)
2098 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2100 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2101 self.update_receive_tab()
2102 self.update_history_tab()
2105 def settings_dialog(self):
2107 d.setWindowTitle(_('Electrum Settings'))
2109 vbox = QVBoxLayout()
2110 grid = QGridLayout()
2111 grid.setColumnStretch(0,1)
2113 nz_label = QLabel(_('Display zeros') + ':')
2114 grid.addWidget(nz_label, 0, 0)
2115 nz_e = AmountEdit(None,True)
2116 nz_e.setText("%d"% self.num_zeros)
2117 grid.addWidget(nz_e, 0, 1)
2118 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2119 grid.addWidget(HelpButton(msg), 0, 2)
2120 if not self.config.is_modifiable('num_zeros'):
2121 for w in [nz_e, nz_label]: w.setEnabled(False)
2123 lang_label=QLabel(_('Language') + ':')
2124 grid.addWidget(lang_label, 1, 0)
2125 lang_combo = QComboBox()
2126 from electrum.i18n import languages
2127 lang_combo.addItems(languages.values())
2129 index = languages.keys().index(self.config.get("language",''))
2132 lang_combo.setCurrentIndex(index)
2133 grid.addWidget(lang_combo, 1, 1)
2134 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2135 if not self.config.is_modifiable('language'):
2136 for w in [lang_combo, lang_label]: w.setEnabled(False)
2139 fee_label = QLabel(_('Transaction fee') + ':')
2140 grid.addWidget(fee_label, 2, 0)
2141 fee_e = AmountEdit(self.base_unit)
2142 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2143 grid.addWidget(fee_e, 2, 1)
2144 msg = _('Fee per kilobyte of transaction.') + ' ' \
2145 + _('Recommended value') + ': ' + self.format_amount(20000)
2146 grid.addWidget(HelpButton(msg), 2, 2)
2147 if not self.config.is_modifiable('fee_per_kb'):
2148 for w in [fee_e, fee_label]: w.setEnabled(False)
2150 units = ['BTC', 'mBTC']
2151 unit_label = QLabel(_('Base unit') + ':')
2152 grid.addWidget(unit_label, 3, 0)
2153 unit_combo = QComboBox()
2154 unit_combo.addItems(units)
2155 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2156 grid.addWidget(unit_combo, 3, 1)
2157 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2158 + '\n1BTC=1000mBTC.\n' \
2159 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2161 usechange_cb = QCheckBox(_('Use change addresses'))
2162 usechange_cb.setChecked(self.wallet.use_change)
2163 grid.addWidget(usechange_cb, 4, 0)
2164 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2165 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2167 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2168 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2169 grid.addWidget(block_ex_label, 5, 0)
2170 block_ex_combo = QComboBox()
2171 block_ex_combo.addItems(block_explorers)
2172 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2173 grid.addWidget(block_ex_combo, 5, 1)
2174 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2176 show_tx = self.config.get('show_before_broadcast', False)
2177 showtx_cb = QCheckBox(_('Show before broadcast'))
2178 showtx_cb.setChecked(show_tx)
2179 grid.addWidget(showtx_cb, 6, 0)
2180 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2182 vbox.addLayout(grid)
2184 vbox.addLayout(ok_cancel_buttons(d))
2188 if not d.exec_(): return
2190 fee = unicode(fee_e.text())
2192 fee = self.read_amount(fee)
2194 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2197 self.wallet.set_fee(fee)
2199 nz = unicode(nz_e.text())
2204 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2207 if self.num_zeros != nz:
2209 self.config.set_key('num_zeros', nz, True)
2210 self.update_history_tab()
2211 self.update_receive_tab()
2213 usechange_result = usechange_cb.isChecked()
2214 if self.wallet.use_change != usechange_result:
2215 self.wallet.use_change = usechange_result
2216 self.wallet.storage.put('use_change', self.wallet.use_change)
2218 if showtx_cb.isChecked() != show_tx:
2219 self.config.set_key('show_before_broadcast', not show_tx)
2221 unit_result = units[unit_combo.currentIndex()]
2222 if self.base_unit() != unit_result:
2223 self.decimal_point = 8 if unit_result == 'BTC' else 5
2224 self.config.set_key('decimal_point', self.decimal_point, True)
2225 self.update_history_tab()
2226 self.update_status()
2228 need_restart = False
2230 lang_request = languages.keys()[lang_combo.currentIndex()]
2231 if lang_request != self.config.get('language'):
2232 self.config.set_key("language", lang_request, True)
2235 be_result = block_explorers[block_ex_combo.currentIndex()]
2236 self.config.set_key('block_explorer', be_result, True)
2238 run_hook('close_settings_dialog')
2241 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2244 def run_network_dialog(self):
2245 if not self.network:
2247 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2249 def closeEvent(self, event):
2251 self.config.set_key("is_maximized", self.isMaximized())
2252 if not self.isMaximized():
2254 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2255 self.save_column_widths()
2256 self.config.set_key("console-history", self.console.history[-50:], True)
2257 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2261 def plugins_dialog(self):
2262 from electrum.plugins import plugins
2265 d.setWindowTitle(_('Electrum Plugins'))
2268 vbox = QVBoxLayout(d)
2271 scroll = QScrollArea()
2272 scroll.setEnabled(True)
2273 scroll.setWidgetResizable(True)
2274 scroll.setMinimumSize(400,250)
2275 vbox.addWidget(scroll)
2279 w.setMinimumHeight(len(plugins)*35)
2281 grid = QGridLayout()
2282 grid.setColumnStretch(0,1)
2285 def do_toggle(cb, p, w):
2288 if w: w.setEnabled(r)
2290 def mk_toggle(cb, p, w):
2291 return lambda: do_toggle(cb,p,w)
2293 for i, p in enumerate(plugins):
2295 cb = QCheckBox(p.fullname())
2296 cb.setDisabled(not p.is_available())
2297 cb.setChecked(p.is_enabled())
2298 grid.addWidget(cb, i, 0)
2299 if p.requires_settings():
2300 w = p.settings_widget(self)
2301 w.setEnabled( p.is_enabled() )
2302 grid.addWidget(w, i, 1)
2305 cb.clicked.connect(mk_toggle(cb,p,w))
2306 grid.addWidget(HelpButton(p.description()), i, 2)
2308 print_msg(_("Error: cannot display plugin"), p)
2309 traceback.print_exc(file=sys.stdout)
2310 grid.setRowStretch(i+1,1)
2312 vbox.addLayout(close_button(d))
2317 def show_account_details(self, k):
2318 account = self.wallet.accounts[k]
2321 d.setWindowTitle(_('Account Details'))
2324 vbox = QVBoxLayout(d)
2325 name = self.wallet.get_account_name(k)
2326 label = QLabel('Name: ' + name)
2327 vbox.addWidget(label)
2329 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2331 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2333 vbox.addWidget(QLabel(_('Master Public Key:')))
2336 text.setReadOnly(True)
2337 text.setMaximumHeight(170)
2338 vbox.addWidget(text)
2340 mpk_text = '\n'.join( account.get_master_pubkeys() )
2341 text.setText(mpk_text)
2343 vbox.addLayout(close_button(d))