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.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 = QLineEdit()
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()
786 r = unicode( self.payto_e.text() )
789 # label or alias, with address in brackets
790 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
791 to_address = m.group(2) if m else r
792 if not is_valid(to_address):
793 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
797 amount = self.amount_e.get_amount()
799 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
802 outputs = [(to_address, amount)]
805 fee = self.fee_e.get_amount()
807 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
810 confirm_amount = self.config.get('confirm_amount', 100000000)
811 if amount >= confirm_amount:
812 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
815 confirm_fee = self.config.get('confirm_fee', 100000)
816 if fee >= confirm_fee:
817 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()}):
820 self.send_tx(outputs, fee, label)
825 def send_tx(self, outputs, fee, label, password):
826 self.send_button.setDisabled(True)
828 # first, create an unsigned tx
829 domain = self.get_payment_sources()
831 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
833 except Exception as e:
834 traceback.print_exc(file=sys.stdout)
835 self.show_message(str(e))
836 self.send_button.setDisabled(False)
839 # call hook to see if plugin needs gui interaction
840 run_hook('send_tx', tx)
846 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
847 self.wallet.sign_transaction(tx, keypairs, password)
848 return tx, fee, label
850 def sign_done(tx, fee, label):
852 self.show_message(tx.error)
853 self.send_button.setDisabled(False)
855 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
856 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
857 self.send_button.setDisabled(False)
860 self.wallet.set_label(tx.hash(), label)
862 if not self.gui_object.payment_request:
863 if not tx.is_complete() or self.config.get('show_before_broadcast'):
864 self.show_transaction(tx)
866 self.send_button.setDisabled(False)
869 self.broadcast_transaction(tx)
871 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
872 self.waiting_dialog.start()
876 def broadcast_transaction(self, tx):
878 def broadcast_thread():
879 if self.gui_object.payment_request:
880 refund_address = self.wallet.addresses()[0]
881 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
882 self.gui_object.payment_request = None
884 status, msg = self.wallet.sendtx(tx)
887 def broadcast_done(status, msg):
889 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
892 QMessageBox.warning(self, _('Error'), msg, _('OK'))
893 self.send_button.setDisabled(False)
895 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
896 self.waiting_dialog.start()
900 def prepare_for_payment_request(self):
901 style = "QWidget { background-color:none;border:none;}"
902 self.tabs.setCurrentIndex(1)
903 for e in [self.payto_e, self.amount_e, self.message_e]:
905 e.setStyleSheet(style)
906 for h in [self.payto_help, self.amount_help, self.message_help]:
908 self.payto_e.setText(_("please wait..."))
911 def payment_request_ok(self):
912 self.payto_e.setText(self.gui_object.payment_request.domain)
913 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
914 self.message_e.setText(self.gui_object.payment_request.memo)
916 def payment_request_error(self):
918 self.show_message(self.gui_object.payment_request.error)
921 def set_send(self, address, amount, label, message):
923 if label and self.wallet.labels.get(address) != label:
924 if self.question('Give label "%s" to address %s ?'%(label,address)):
925 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
926 self.wallet.addressbook.append(address)
927 self.wallet.set_label(address, label)
929 self.tabs.setCurrentIndex(1)
930 label = self.wallet.labels.get(address)
931 m_addr = label + ' <'+ address +'>' if label else address
932 self.payto_e.setText(m_addr)
934 self.message_e.setText(message)
936 self.amount_e.setText(amount)
940 self.payto_sig.setVisible(False)
941 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
943 self.set_frozen(e,False)
945 for h in [self.payto_help, self.amount_help, self.message_help]:
948 self.set_pay_from([])
951 def set_frozen(self,entry,frozen):
953 entry.setReadOnly(True)
954 entry.setFrame(False)
956 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
957 entry.setPalette(palette)
959 entry.setReadOnly(False)
962 palette.setColor(entry.backgroundRole(), QColor('white'))
963 entry.setPalette(palette)
966 def set_addrs_frozen(self,addrs,freeze):
968 if not addr: continue
969 if addr in self.wallet.frozen_addresses and not freeze:
970 self.wallet.unfreeze(addr)
971 elif addr not in self.wallet.frozen_addresses and freeze:
972 self.wallet.freeze(addr)
973 self.update_receive_tab()
977 def create_list_tab(self, headers):
978 "generic tab creation method"
979 l = MyTreeWidget(self)
980 l.setColumnCount( len(headers) )
981 l.setHeaderLabels( headers )
991 vbox.addWidget(buttons)
996 buttons.setLayout(hbox)
1001 def create_receive_tab(self):
1002 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1003 l.setContextMenuPolicy(Qt.CustomContextMenu)
1004 l.customContextMenuRequested.connect(self.create_receive_menu)
1005 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1006 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1007 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1008 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1009 self.receive_list = l
1010 self.receive_buttons_hbox = hbox
1017 def save_column_widths(self):
1018 self.column_widths["receive"] = []
1019 for i in range(self.receive_list.columnCount() -1):
1020 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1022 self.column_widths["history"] = []
1023 for i in range(self.history_list.columnCount() - 1):
1024 self.column_widths["history"].append(self.history_list.columnWidth(i))
1026 self.column_widths["contacts"] = []
1027 for i in range(self.contacts_list.columnCount() - 1):
1028 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1030 self.config.set_key("column_widths_2", self.column_widths, True)
1033 def create_contacts_tab(self):
1034 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1035 l.setContextMenuPolicy(Qt.CustomContextMenu)
1036 l.customContextMenuRequested.connect(self.create_contact_menu)
1037 for i,width in enumerate(self.column_widths['contacts']):
1038 l.setColumnWidth(i, width)
1040 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1041 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1042 self.contacts_list = l
1043 self.contacts_buttons_hbox = hbox
1048 def delete_imported_key(self, addr):
1049 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1050 self.wallet.delete_imported_key(addr)
1051 self.update_receive_tab()
1052 self.update_history_tab()
1054 def edit_account_label(self, k):
1055 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1057 label = unicode(text)
1058 self.wallet.set_label(k,label)
1059 self.update_receive_tab()
1061 def account_set_expanded(self, item, k, b):
1063 self.accounts_expanded[k] = b
1065 def create_account_menu(self, position, k, item):
1067 if item.isExpanded():
1068 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1070 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1071 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1072 if self.wallet.seed_version > 4:
1073 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1074 if self.wallet.account_is_pending(k):
1075 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1076 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1078 def delete_pending_account(self, k):
1079 self.wallet.delete_pending_account(k)
1080 self.update_receive_tab()
1082 def create_receive_menu(self, position):
1083 # fixme: this function apparently has a side effect.
1084 # if it is not called the menu pops up several times
1085 #self.receive_list.selectedIndexes()
1087 selected = self.receive_list.selectedItems()
1088 multi_select = len(selected) > 1
1089 addrs = [unicode(item.text(0)) for item in selected]
1090 if not multi_select:
1091 item = self.receive_list.itemAt(position)
1095 if not is_valid(addr):
1096 k = str(item.data(0,32).toString())
1098 self.create_account_menu(position, k, item)
1100 item.setExpanded(not item.isExpanded())
1104 if not multi_select:
1105 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1106 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1107 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1108 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1109 if not self.wallet.is_watching_only():
1110 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1111 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1112 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1113 if self.wallet.is_imported(addr):
1114 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1116 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1117 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1118 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1119 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1121 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1122 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1124 run_hook('receive_menu', menu, addrs)
1125 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1128 def get_sendable_balance(self):
1129 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1132 def get_payment_sources(self):
1134 return self.pay_from
1136 return self.wallet.get_account_addresses(self.current_account)
1139 def send_from_addresses(self, addrs):
1140 self.set_pay_from( addrs )
1141 self.tabs.setCurrentIndex(1)
1144 def payto(self, addr):
1146 label = self.wallet.labels.get(addr)
1147 m_addr = label + ' <' + addr + '>' if label else addr
1148 self.tabs.setCurrentIndex(1)
1149 self.payto_e.setText(m_addr)
1150 self.amount_e.setFocus()
1153 def delete_contact(self, x):
1154 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1155 self.wallet.delete_contact(x)
1156 self.wallet.set_label(x, None)
1157 self.update_history_tab()
1158 self.update_contacts_tab()
1159 self.update_completions()
1162 def create_contact_menu(self, position):
1163 item = self.contacts_list.itemAt(position)
1166 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1168 addr = unicode(item.text(0))
1169 label = unicode(item.text(1))
1170 is_editable = item.data(0,32).toBool()
1171 payto_addr = item.data(0,33).toString()
1172 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1173 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1174 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1176 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1177 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1179 run_hook('create_contact_menu', menu, item)
1180 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1183 def update_receive_item(self, item):
1184 item.setFont(0, QFont(MONOSPACE_FONT))
1185 address = str(item.data(0,0).toString())
1186 label = self.wallet.labels.get(address,'')
1187 item.setData(1,0,label)
1188 item.setData(0,32, True) # is editable
1190 run_hook('update_receive_item', address, item)
1192 if not self.wallet.is_mine(address): return
1194 c, u = self.wallet.get_addr_balance(address)
1195 balance = self.format_amount(c + u)
1196 item.setData(2,0,balance)
1198 if address in self.wallet.frozen_addresses:
1199 item.setBackgroundColor(0, QColor('lightblue'))
1202 def update_receive_tab(self):
1203 l = self.receive_list
1204 # extend the syntax for consistency
1205 l.addChild = l.addTopLevelItem
1206 l.insertChild = l.insertTopLevelItem
1209 for i,width in enumerate(self.column_widths['receive']):
1210 l.setColumnWidth(i, width)
1212 accounts = self.wallet.get_accounts()
1213 if self.current_account is None:
1214 account_items = sorted(accounts.items())
1216 account_items = [(self.current_account, accounts.get(self.current_account))]
1219 for k, account in account_items:
1221 if len(accounts) > 1:
1222 name = self.wallet.get_account_name(k)
1223 c,u = self.wallet.get_account_balance(k)
1224 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1225 l.addTopLevelItem(account_item)
1226 account_item.setExpanded(self.accounts_expanded.get(k, True))
1227 account_item.setData(0, 32, k)
1231 sequences = [0,1] if account.has_change() else [0]
1232 for is_change in sequences:
1233 if len(sequences) > 1:
1234 name = _("Receiving") if not is_change else _("Change")
1235 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1236 account_item.addChild(seq_item)
1238 seq_item.setExpanded(True)
1240 seq_item = account_item
1242 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1248 for address in account.get_addresses(is_change):
1250 num, is_used = self.wallet.is_used(address)
1253 if gap > self.wallet.gap_limit:
1258 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1259 self.update_receive_item(item)
1261 item.setBackgroundColor(1, QColor('red'))
1265 seq_item.insertChild(0,used_item)
1267 used_item.addChild(item)
1269 seq_item.addChild(item)
1271 # we use column 1 because column 0 may be hidden
1272 l.setCurrentItem(l.topLevelItem(0),1)
1275 def update_contacts_tab(self):
1276 l = self.contacts_list
1279 for address in self.wallet.addressbook:
1280 label = self.wallet.labels.get(address,'')
1281 n = self.wallet.get_num_tx(address)
1282 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1283 item.setFont(0, QFont(MONOSPACE_FONT))
1284 # 32 = label can be edited (bool)
1285 item.setData(0,32, True)
1287 item.setData(0,33, address)
1288 l.addTopLevelItem(item)
1290 run_hook('update_contacts_tab', l)
1291 l.setCurrentItem(l.topLevelItem(0))
1295 def create_console_tab(self):
1296 from console import Console
1297 self.console = console = Console()
1301 def update_console(self):
1302 console = self.console
1303 console.history = self.config.get("console-history",[])
1304 console.history_index = len(console.history)
1306 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1307 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1309 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1311 def mkfunc(f, method):
1312 return lambda *args: apply( f, (method, args, self.password_dialog ))
1314 if m[0]=='_' or m in ['network','wallet']: continue
1315 methods[m] = mkfunc(c._run, m)
1317 console.updateNamespace(methods)
1320 def change_account(self,s):
1321 if s == _("All accounts"):
1322 self.current_account = None
1324 accounts = self.wallet.get_account_names()
1325 for k, v in accounts.items():
1327 self.current_account = k
1328 self.update_history_tab()
1329 self.update_status()
1330 self.update_receive_tab()
1332 def create_status_bar(self):
1335 sb.setFixedHeight(35)
1336 qtVersion = qVersion()
1338 self.balance_label = QLabel("")
1339 sb.addWidget(self.balance_label)
1341 from version_getter import UpdateLabel
1342 self.updatelabel = UpdateLabel(self.config, sb)
1344 self.account_selector = QComboBox()
1345 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1346 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1347 sb.addPermanentWidget(self.account_selector)
1349 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1350 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1352 self.lock_icon = QIcon()
1353 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1354 sb.addPermanentWidget( self.password_button )
1356 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1357 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1358 sb.addPermanentWidget( self.seed_button )
1359 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1360 sb.addPermanentWidget( self.status_button )
1362 run_hook('create_status_bar', (sb,))
1364 self.setStatusBar(sb)
1367 def update_lock_icon(self):
1368 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1369 self.password_button.setIcon( icon )
1372 def update_buttons_on_seed(self):
1373 if self.wallet.has_seed():
1374 self.seed_button.show()
1376 self.seed_button.hide()
1378 if not self.wallet.is_watching_only():
1379 self.password_button.show()
1380 self.send_button.setText(_("Send"))
1382 self.password_button.hide()
1383 self.send_button.setText(_("Create unsigned transaction"))
1386 def change_password_dialog(self):
1387 from password_dialog import PasswordDialog
1388 d = PasswordDialog(self.wallet, self)
1390 self.update_lock_icon()
1393 def new_contact_dialog(self):
1396 d.setWindowTitle(_("New Contact"))
1397 vbox = QVBoxLayout(d)
1398 vbox.addWidget(QLabel(_('New Contact')+':'))
1400 grid = QGridLayout()
1403 grid.addWidget(QLabel(_("Address")), 1, 0)
1404 grid.addWidget(line1, 1, 1)
1405 grid.addWidget(QLabel(_("Name")), 2, 0)
1406 grid.addWidget(line2, 2, 1)
1408 vbox.addLayout(grid)
1409 vbox.addLayout(ok_cancel_buttons(d))
1414 address = str(line1.text())
1415 label = unicode(line2.text())
1417 if not is_valid(address):
1418 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1421 self.wallet.add_contact(address)
1423 self.wallet.set_label(address, label)
1425 self.update_contacts_tab()
1426 self.update_history_tab()
1427 self.update_completions()
1428 self.tabs.setCurrentIndex(3)
1432 def new_account_dialog(self, password):
1434 dialog = QDialog(self)
1436 dialog.setWindowTitle(_("New Account"))
1438 vbox = QVBoxLayout()
1439 vbox.addWidget(QLabel(_('Account name')+':'))
1442 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1443 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1448 vbox.addLayout(ok_cancel_buttons(dialog))
1449 dialog.setLayout(vbox)
1453 name = str(e.text())
1456 self.wallet.create_pending_account(name, password)
1457 self.update_receive_tab()
1458 self.tabs.setCurrentIndex(2)
1463 def show_master_public_keys(self):
1465 dialog = QDialog(self)
1467 dialog.setWindowTitle(_("Master Public Keys"))
1469 main_layout = QGridLayout()
1470 mpk_dict = self.wallet.get_master_public_keys()
1472 for key, value in mpk_dict.items():
1473 main_layout.addWidget(QLabel(key), i, 0)
1474 mpk_text = QTextEdit()
1475 mpk_text.setReadOnly(True)
1476 mpk_text.setMaximumHeight(170)
1477 mpk_text.setText(value)
1478 main_layout.addWidget(mpk_text, i + 1, 0)
1481 vbox = QVBoxLayout()
1482 vbox.addLayout(main_layout)
1483 vbox.addLayout(close_button(dialog))
1485 dialog.setLayout(vbox)
1490 def show_seed_dialog(self, password):
1491 if not self.wallet.has_seed():
1492 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1496 mnemonic = self.wallet.get_mnemonic(password)
1498 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1500 from seed_dialog import SeedDialog
1501 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1506 def show_qrcode(self, data, title = _("QR code")):
1510 d.setWindowTitle(title)
1511 d.setMinimumSize(270, 300)
1512 vbox = QVBoxLayout()
1513 qrw = QRCodeWidget(data)
1514 vbox.addWidget(qrw, 1)
1515 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1516 hbox = QHBoxLayout()
1519 filename = os.path.join(self.config.path, "qrcode.bmp")
1522 bmp.save_qrcode(qrw.qr, filename)
1523 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1525 def copy_to_clipboard():
1526 bmp.save_qrcode(qrw.qr, filename)
1527 self.app.clipboard().setImage(QImage(filename))
1528 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1530 b = QPushButton(_("Copy"))
1532 b.clicked.connect(copy_to_clipboard)
1534 b = QPushButton(_("Save"))
1536 b.clicked.connect(print_qr)
1538 b = QPushButton(_("Close"))
1540 b.clicked.connect(d.accept)
1543 vbox.addLayout(hbox)
1548 def do_protect(self, func, args):
1549 if self.wallet.use_encryption:
1550 password = self.password_dialog()
1556 if args != (False,):
1557 args = (self,) + args + (password,)
1559 args = (self,password)
1563 def show_public_keys(self, address):
1564 if not address: return
1566 pubkey_list = self.wallet.get_public_keys(address)
1567 except Exception as e:
1568 traceback.print_exc(file=sys.stdout)
1569 self.show_message(str(e))
1573 d.setMinimumSize(600, 200)
1575 vbox = QVBoxLayout()
1576 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1577 vbox.addWidget( QLabel(_("Public key") + ':'))
1579 keys.setReadOnly(True)
1580 keys.setText('\n'.join(pubkey_list))
1581 vbox.addWidget(keys)
1582 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1583 vbox.addLayout(close_button(d))
1588 def show_private_key(self, address, password):
1589 if not address: return
1591 pk_list = self.wallet.get_private_key(address, password)
1592 except Exception as e:
1593 traceback.print_exc(file=sys.stdout)
1594 self.show_message(str(e))
1598 d.setMinimumSize(600, 200)
1600 vbox = QVBoxLayout()
1601 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1602 vbox.addWidget( QLabel(_("Private key") + ':'))
1604 keys.setReadOnly(True)
1605 keys.setText('\n'.join(pk_list))
1606 vbox.addWidget(keys)
1607 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1608 vbox.addLayout(close_button(d))
1614 def do_sign(self, address, message, signature, password):
1615 message = unicode(message.toPlainText())
1616 message = message.encode('utf-8')
1618 sig = self.wallet.sign_message(str(address.text()), message, password)
1619 signature.setText(sig)
1620 except Exception as e:
1621 self.show_message(str(e))
1623 def do_verify(self, address, message, signature):
1624 message = unicode(message.toPlainText())
1625 message = message.encode('utf-8')
1626 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1627 self.show_message(_("Signature verified"))
1629 self.show_message(_("Error: wrong signature"))
1632 def sign_verify_message(self, address=''):
1635 d.setWindowTitle(_('Sign/verify Message'))
1636 d.setMinimumSize(410, 290)
1638 layout = QGridLayout(d)
1640 message_e = QTextEdit()
1641 layout.addWidget(QLabel(_('Message')), 1, 0)
1642 layout.addWidget(message_e, 1, 1)
1643 layout.setRowStretch(2,3)
1645 address_e = QLineEdit()
1646 address_e.setText(address)
1647 layout.addWidget(QLabel(_('Address')), 2, 0)
1648 layout.addWidget(address_e, 2, 1)
1650 signature_e = QTextEdit()
1651 layout.addWidget(QLabel(_('Signature')), 3, 0)
1652 layout.addWidget(signature_e, 3, 1)
1653 layout.setRowStretch(3,1)
1655 hbox = QHBoxLayout()
1657 b = QPushButton(_("Sign"))
1658 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1661 b = QPushButton(_("Verify"))
1662 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1665 b = QPushButton(_("Close"))
1666 b.clicked.connect(d.accept)
1668 layout.addLayout(hbox, 4, 1)
1673 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1675 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1676 message_e.setText(decrypted)
1677 except Exception as e:
1678 self.show_message(str(e))
1681 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1682 message = unicode(message_e.toPlainText())
1683 message = message.encode('utf-8')
1685 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1686 encrypted_e.setText(encrypted)
1687 except Exception as e:
1688 self.show_message(str(e))
1692 def encrypt_message(self, address = ''):
1695 d.setWindowTitle(_('Encrypt/decrypt Message'))
1696 d.setMinimumSize(610, 490)
1698 layout = QGridLayout(d)
1700 message_e = QTextEdit()
1701 layout.addWidget(QLabel(_('Message')), 1, 0)
1702 layout.addWidget(message_e, 1, 1)
1703 layout.setRowStretch(2,3)
1705 pubkey_e = QLineEdit()
1707 pubkey = self.wallet.getpubkeys(address)[0]
1708 pubkey_e.setText(pubkey)
1709 layout.addWidget(QLabel(_('Public key')), 2, 0)
1710 layout.addWidget(pubkey_e, 2, 1)
1712 encrypted_e = QTextEdit()
1713 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1714 layout.addWidget(encrypted_e, 3, 1)
1715 layout.setRowStretch(3,1)
1717 hbox = QHBoxLayout()
1718 b = QPushButton(_("Encrypt"))
1719 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1722 b = QPushButton(_("Decrypt"))
1723 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1726 b = QPushButton(_("Close"))
1727 b.clicked.connect(d.accept)
1730 layout.addLayout(hbox, 4, 1)
1734 def question(self, msg):
1735 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1737 def show_message(self, msg):
1738 QMessageBox.information(self, _('Message'), msg, _('OK'))
1740 def password_dialog(self, msg=None):
1743 d.setWindowTitle(_("Enter Password"))
1748 vbox = QVBoxLayout()
1750 msg = _('Please enter your password')
1751 vbox.addWidget(QLabel(msg))
1753 grid = QGridLayout()
1755 grid.addWidget(QLabel(_('Password')), 1, 0)
1756 grid.addWidget(pw, 1, 1)
1757 vbox.addLayout(grid)
1759 vbox.addLayout(ok_cancel_buttons(d))
1762 run_hook('password_dialog', pw, grid, 1)
1763 if not d.exec_(): return
1764 return unicode(pw.text())
1773 def tx_from_text(self, txt):
1774 "json or raw hexadecimal"
1777 tx = Transaction(txt)
1783 tx_dict = json.loads(str(txt))
1784 assert "hex" in tx_dict.keys()
1785 tx = Transaction(tx_dict["hex"])
1786 if tx_dict.has_key("input_info"):
1787 input_info = json.loads(tx_dict['input_info'])
1788 tx.add_input_info(input_info)
1791 traceback.print_exc(file=sys.stdout)
1794 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1798 def read_tx_from_file(self):
1799 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1803 with open(fileName, "r") as f:
1804 file_content = f.read()
1805 except (ValueError, IOError, os.error), reason:
1806 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1808 return self.tx_from_text(file_content)
1812 def sign_raw_transaction(self, tx, input_info, password):
1813 self.wallet.signrawtransaction(tx, input_info, [], password)
1815 def do_process_from_text(self):
1816 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1819 tx = self.tx_from_text(text)
1821 self.show_transaction(tx)
1823 def do_process_from_file(self):
1824 tx = self.read_tx_from_file()
1826 self.show_transaction(tx)
1828 def do_process_from_txid(self):
1829 from electrum import transaction
1830 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1832 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1834 tx = transaction.Transaction(r)
1836 self.show_transaction(tx)
1838 self.show_message("unknown transaction")
1840 def do_process_from_csvReader(self, csvReader):
1845 for position, row in enumerate(csvReader):
1847 if not is_valid(address):
1848 errors.append((position, address))
1850 amount = Decimal(row[1])
1851 amount = int(100000000*amount)
1852 outputs.append((address, amount))
1853 except (ValueError, IOError, os.error), reason:
1854 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1858 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1859 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1863 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1864 except Exception as e:
1865 self.show_message(str(e))
1868 self.show_transaction(tx)
1870 def do_process_from_csv_file(self):
1871 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1875 with open(fileName, "r") as f:
1876 csvReader = csv.reader(f)
1877 self.do_process_from_csvReader(csvReader)
1878 except (ValueError, IOError, os.error), reason:
1879 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1882 def do_process_from_csv_text(self):
1883 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1884 + _("Format: address, amount. One output per line"), _("Load CSV"))
1887 f = StringIO.StringIO(text)
1888 csvReader = csv.reader(f)
1889 self.do_process_from_csvReader(csvReader)
1894 def export_privkeys_dialog(self, password):
1895 if self.wallet.is_watching_only():
1896 self.show_message(_("This is a watching-only wallet"))
1900 d.setWindowTitle(_('Private keys'))
1901 d.setMinimumSize(850, 300)
1902 vbox = QVBoxLayout(d)
1904 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1905 _("Exposing a single private key can compromise your entire wallet!"),
1906 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1907 vbox.addWidget(QLabel(msg))
1913 defaultname = 'electrum-private-keys.csv'
1914 select_msg = _('Select file to export your private keys to')
1915 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1916 vbox.addLayout(hbox)
1918 h, b = ok_cancel_buttons2(d, _('Export'))
1923 addresses = self.wallet.addresses(True)
1925 def privkeys_thread():
1926 for addr in addresses:
1930 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1931 d.emit(SIGNAL('computing_privkeys'))
1932 d.emit(SIGNAL('show_privkeys'))
1934 def show_privkeys():
1935 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1939 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1940 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1941 threading.Thread(target=privkeys_thread).start()
1947 filename = filename_e.text()
1952 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1953 except (IOError, os.error), reason:
1954 export_error_label = _("Electrum was unable to produce a private key-export.")
1955 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1957 except Exception as e:
1958 self.show_message(str(e))
1961 self.show_message(_("Private keys exported."))
1964 def do_export_privkeys(self, fileName, pklist, is_csv):
1965 with open(fileName, "w+") as f:
1967 transaction = csv.writer(f)
1968 transaction.writerow(["address", "private_key"])
1969 for addr, pk in pklist.items():
1970 transaction.writerow(["%34s"%addr,pk])
1973 f.write(json.dumps(pklist, indent = 4))
1976 def do_import_labels(self):
1977 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1978 if not labelsFile: return
1980 f = open(labelsFile, 'r')
1983 for key, value in json.loads(data).items():
1984 self.wallet.set_label(key, value)
1985 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1986 except (IOError, os.error), reason:
1987 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1990 def do_export_labels(self):
1991 labels = self.wallet.labels
1993 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1995 with open(fileName, 'w+') as f:
1996 json.dump(labels, f)
1997 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1998 except (IOError, os.error), reason:
1999 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2002 def export_history_dialog(self):
2005 d.setWindowTitle(_('Export History'))
2006 d.setMinimumSize(400, 200)
2007 vbox = QVBoxLayout(d)
2009 defaultname = os.path.expanduser('~/electrum-history.csv')
2010 select_msg = _('Select file to export your wallet transactions to')
2012 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2013 vbox.addLayout(hbox)
2017 h, b = ok_cancel_buttons2(d, _('Export'))
2022 filename = filename_e.text()
2027 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2028 except (IOError, os.error), reason:
2029 export_error_label = _("Electrum was unable to produce a transaction export.")
2030 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2033 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2036 def do_export_history(self, wallet, fileName, is_csv):
2037 history = wallet.get_tx_history()
2039 for item in history:
2040 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2042 if timestamp is not None:
2044 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2045 except [RuntimeError, TypeError, NameError] as reason:
2046 time_string = "unknown"
2049 time_string = "unknown"
2051 time_string = "pending"
2053 if value is not None:
2054 value_string = format_satoshis(value, True)
2059 fee_string = format_satoshis(fee, True)
2064 label, is_default_label = wallet.get_label(tx_hash)
2065 label = label.encode('utf-8')
2069 balance_string = format_satoshis(balance, False)
2071 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2073 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2075 with open(fileName, "w+") as f:
2077 transaction = csv.writer(f)
2078 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2080 transaction.writerow(line)
2083 f.write(json.dumps(lines, indent = 4))
2086 def sweep_key_dialog(self):
2088 d.setWindowTitle(_('Sweep private keys'))
2089 d.setMinimumSize(600, 300)
2091 vbox = QVBoxLayout(d)
2092 vbox.addWidget(QLabel(_("Enter private keys")))
2094 keys_e = QTextEdit()
2095 keys_e.setTabChangesFocus(True)
2096 vbox.addWidget(keys_e)
2098 h, address_e = address_field(self.wallet.addresses())
2102 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2103 vbox.addLayout(hbox)
2104 button.setEnabled(False)
2107 addr = str(address_e.text())
2108 if bitcoin.is_address(addr):
2112 pk = str(keys_e.toPlainText()).strip()
2113 if Wallet.is_private_key(pk):
2116 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2117 keys_e.textChanged.connect(f)
2118 address_e.textChanged.connect(f)
2122 fee = self.wallet.fee
2123 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2124 self.show_transaction(tx)
2128 def do_import_privkey(self, password):
2129 if not self.wallet.has_imported_keys():
2130 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2131 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2132 + _('Are you sure you understand what you are doing?'), 3, 4)
2135 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2138 text = str(text).split()
2143 addr = self.wallet.import_key(key, password)
2144 except Exception as e:
2150 addrlist.append(addr)
2152 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2154 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2155 self.update_receive_tab()
2156 self.update_history_tab()
2159 def settings_dialog(self):
2161 d.setWindowTitle(_('Electrum Settings'))
2163 vbox = QVBoxLayout()
2164 grid = QGridLayout()
2165 grid.setColumnStretch(0,1)
2167 nz_label = QLabel(_('Display zeros') + ':')
2168 grid.addWidget(nz_label, 0, 0)
2169 nz_e = AmountEdit(None,True)
2170 nz_e.setText("%d"% self.num_zeros)
2171 grid.addWidget(nz_e, 0, 1)
2172 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2173 grid.addWidget(HelpButton(msg), 0, 2)
2174 if not self.config.is_modifiable('num_zeros'):
2175 for w in [nz_e, nz_label]: w.setEnabled(False)
2177 lang_label=QLabel(_('Language') + ':')
2178 grid.addWidget(lang_label, 1, 0)
2179 lang_combo = QComboBox()
2180 from electrum.i18n import languages
2181 lang_combo.addItems(languages.values())
2183 index = languages.keys().index(self.config.get("language",''))
2186 lang_combo.setCurrentIndex(index)
2187 grid.addWidget(lang_combo, 1, 1)
2188 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2189 if not self.config.is_modifiable('language'):
2190 for w in [lang_combo, lang_label]: w.setEnabled(False)
2193 fee_label = QLabel(_('Transaction fee') + ':')
2194 grid.addWidget(fee_label, 2, 0)
2195 fee_e = AmountEdit(self.get_decimal_point)
2196 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2197 grid.addWidget(fee_e, 2, 1)
2198 msg = _('Fee per kilobyte of transaction.') + ' ' \
2199 + _('Recommended value') + ': ' + self.format_amount(20000)
2200 grid.addWidget(HelpButton(msg), 2, 2)
2201 if not self.config.is_modifiable('fee_per_kb'):
2202 for w in [fee_e, fee_label]: w.setEnabled(False)
2204 units = ['BTC', 'mBTC']
2205 unit_label = QLabel(_('Base unit') + ':')
2206 grid.addWidget(unit_label, 3, 0)
2207 unit_combo = QComboBox()
2208 unit_combo.addItems(units)
2209 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2210 grid.addWidget(unit_combo, 3, 1)
2211 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2212 + '\n1BTC=1000mBTC.\n' \
2213 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2215 usechange_cb = QCheckBox(_('Use change addresses'))
2216 usechange_cb.setChecked(self.wallet.use_change)
2217 grid.addWidget(usechange_cb, 4, 0)
2218 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2219 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2221 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2222 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2223 grid.addWidget(block_ex_label, 5, 0)
2224 block_ex_combo = QComboBox()
2225 block_ex_combo.addItems(block_explorers)
2226 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2227 grid.addWidget(block_ex_combo, 5, 1)
2228 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2230 show_tx = self.config.get('show_before_broadcast', False)
2231 showtx_cb = QCheckBox(_('Show before broadcast'))
2232 showtx_cb.setChecked(show_tx)
2233 grid.addWidget(showtx_cb, 6, 0)
2234 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2236 vbox.addLayout(grid)
2238 vbox.addLayout(ok_cancel_buttons(d))
2242 if not d.exec_(): return
2245 fee = self.fee_e.get_amount()
2247 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2250 self.wallet.set_fee(fee)
2252 nz = unicode(nz_e.text())
2257 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2260 if self.num_zeros != nz:
2262 self.config.set_key('num_zeros', nz, True)
2263 self.update_history_tab()
2264 self.update_receive_tab()
2266 usechange_result = usechange_cb.isChecked()
2267 if self.wallet.use_change != usechange_result:
2268 self.wallet.use_change = usechange_result
2269 self.wallet.storage.put('use_change', self.wallet.use_change)
2271 if showtx_cb.isChecked() != show_tx:
2272 self.config.set_key('show_before_broadcast', not show_tx)
2274 unit_result = units[unit_combo.currentIndex()]
2275 if self.base_unit() != unit_result:
2276 self.decimal_point = 8 if unit_result == 'BTC' else 5
2277 self.config.set_key('decimal_point', self.decimal_point, True)
2278 self.update_history_tab()
2279 self.update_status()
2281 need_restart = False
2283 lang_request = languages.keys()[lang_combo.currentIndex()]
2284 if lang_request != self.config.get('language'):
2285 self.config.set_key("language", lang_request, True)
2288 be_result = block_explorers[block_ex_combo.currentIndex()]
2289 self.config.set_key('block_explorer', be_result, True)
2291 run_hook('close_settings_dialog')
2294 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2297 def run_network_dialog(self):
2298 if not self.network:
2300 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2302 def closeEvent(self, event):
2304 self.config.set_key("is_maximized", self.isMaximized())
2305 if not self.isMaximized():
2307 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2308 self.save_column_widths()
2309 self.config.set_key("console-history", self.console.history[-50:], True)
2310 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2314 def plugins_dialog(self):
2315 from electrum.plugins import plugins
2318 d.setWindowTitle(_('Electrum Plugins'))
2321 vbox = QVBoxLayout(d)
2324 scroll = QScrollArea()
2325 scroll.setEnabled(True)
2326 scroll.setWidgetResizable(True)
2327 scroll.setMinimumSize(400,250)
2328 vbox.addWidget(scroll)
2332 w.setMinimumHeight(len(plugins)*35)
2334 grid = QGridLayout()
2335 grid.setColumnStretch(0,1)
2338 def do_toggle(cb, p, w):
2341 if w: w.setEnabled(r)
2343 def mk_toggle(cb, p, w):
2344 return lambda: do_toggle(cb,p,w)
2346 for i, p in enumerate(plugins):
2348 cb = QCheckBox(p.fullname())
2349 cb.setDisabled(not p.is_available())
2350 cb.setChecked(p.is_enabled())
2351 grid.addWidget(cb, i, 0)
2352 if p.requires_settings():
2353 w = p.settings_widget(self)
2354 w.setEnabled( p.is_enabled() )
2355 grid.addWidget(w, i, 1)
2358 cb.clicked.connect(mk_toggle(cb,p,w))
2359 grid.addWidget(HelpButton(p.description()), i, 2)
2361 print_msg(_("Error: cannot display plugin"), p)
2362 traceback.print_exc(file=sys.stdout)
2363 grid.setRowStretch(i+1,1)
2365 vbox.addLayout(close_button(d))
2370 def show_account_details(self, k):
2371 account = self.wallet.accounts[k]
2374 d.setWindowTitle(_('Account Details'))
2377 vbox = QVBoxLayout(d)
2378 name = self.wallet.get_account_name(k)
2379 label = QLabel('Name: ' + name)
2380 vbox.addWidget(label)
2382 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2384 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2386 vbox.addWidget(QLabel(_('Master Public Key:')))
2389 text.setReadOnly(True)
2390 text.setMaximumHeight(170)
2391 vbox.addWidget(text)
2393 mpk_text = '\n'.join( account.get_master_pubkeys() )
2394 text.setText(mpk_text)
2396 vbox.addLayout(close_button(d))