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('send_tx2'), self.send_tx2)
158 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
159 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
160 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
162 self.history_list.setFocus(True)
166 self.network.register_callback('updated', lambda: self.need_update.set())
167 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
168 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
169 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
170 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
172 # set initial message
173 self.console.showMessage(self.network.banner)
178 def update_account_selector(self):
180 accounts = self.wallet.get_account_names()
181 self.account_selector.clear()
182 if len(accounts) > 1:
183 self.account_selector.addItems([_("All accounts")] + accounts.values())
184 self.account_selector.setCurrentIndex(0)
185 self.account_selector.show()
187 self.account_selector.hide()
190 def load_wallet(self, wallet):
193 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
194 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 open_wallet(self):
219 wallet_folder = self.wallet.storage.path
220 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
224 storage = WalletStorage({'wallet_path': filename})
225 if not storage.file_exists:
226 self.show_message("file not found "+ filename)
229 self.wallet.stop_threads()
232 wallet = Wallet(storage)
233 wallet.start_threads(self.network)
235 self.load_wallet(wallet)
239 def backup_wallet(self):
241 path = self.wallet.storage.path
242 wallet_folder = os.path.dirname(path)
243 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
247 new_path = os.path.join(wallet_folder, filename)
250 shutil.copy2(path, new_path)
251 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
252 except (IOError, os.error), reason:
253 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
256 def new_wallet(self):
259 wallet_folder = os.path.dirname(self.wallet.storage.path)
260 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
263 filename = os.path.join(wallet_folder, filename)
265 storage = WalletStorage({'wallet_path': filename})
266 if storage.file_exists:
267 QMessageBox.critical(None, "Error", _("File exists"))
270 wizard = installwizard.InstallWizard(self.config, self.network, storage)
271 wallet = wizard.run('new')
273 self.load_wallet(wallet)
277 def init_menubar(self):
280 file_menu = menubar.addMenu(_("&File"))
281 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
282 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
283 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
284 file_menu.addAction(_("&Quit"), self.close)
286 wallet_menu = menubar.addMenu(_("&Wallet"))
287 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
288 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
290 wallet_menu.addSeparator()
292 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
293 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
294 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
296 wallet_menu.addSeparator()
297 labels_menu = wallet_menu.addMenu(_("&Labels"))
298 labels_menu.addAction(_("&Import"), self.do_import_labels)
299 labels_menu.addAction(_("&Export"), self.do_export_labels)
301 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
302 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
303 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
304 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
305 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
307 tools_menu = menubar.addMenu(_("&Tools"))
309 # Settings / Preferences are all reserved keywords in OSX using this as work around
310 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
311 tools_menu.addAction(_("&Network"), self.run_network_dialog)
312 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
313 tools_menu.addSeparator()
314 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
315 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
316 tools_menu.addSeparator()
318 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
319 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
320 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
322 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
323 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
324 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
325 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
327 help_menu = menubar.addMenu(_("&Help"))
328 help_menu.addAction(_("&About"), self.show_about)
329 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
330 help_menu.addSeparator()
331 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
332 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
334 self.setMenuBar(menubar)
336 def show_about(self):
337 QMessageBox.about(self, "Electrum",
338 _("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."))
340 def show_report_bug(self):
341 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
342 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
345 def notify_transactions(self):
346 if not self.network or not self.network.is_connected():
349 print_error("Notifying GUI")
350 if len(self.network.pending_transactions_for_notifications) > 0:
351 # Combine the transactions if there are more then three
352 tx_amount = len(self.network.pending_transactions_for_notifications)
355 for tx in self.network.pending_transactions_for_notifications:
356 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
360 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
361 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
363 self.network.pending_transactions_for_notifications = []
365 for tx in self.network.pending_transactions_for_notifications:
367 self.network.pending_transactions_for_notifications.remove(tx)
368 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
370 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
372 def notify(self, message):
373 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
377 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
378 def getOpenFileName(self, title, filter = ""):
379 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
380 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
381 if fileName and directory != os.path.dirname(fileName):
382 self.config.set_key('io_dir', os.path.dirname(fileName), True)
385 def getSaveFileName(self, title, filename, filter = ""):
386 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
387 path = os.path.join( directory, filename )
388 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
389 if fileName and directory != os.path.dirname(fileName):
390 self.config.set_key('io_dir', os.path.dirname(fileName), True)
394 QMainWindow.close(self)
395 run_hook('close_main_window')
397 def connect_slots(self, sender):
398 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
399 self.previous_payto_e=''
401 def timer_actions(self):
402 if self.need_update.is_set():
404 self.need_update.clear()
405 run_hook('timer_actions')
407 def format_amount(self, x, is_diff=False, whitespaces=False):
408 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
410 def read_amount(self, x):
411 if x in['.', '']: return None
412 p = pow(10, self.decimal_point)
413 return int( p * Decimal(x) )
416 assert self.decimal_point in [5,8]
417 return "BTC" if self.decimal_point == 8 else "mBTC"
420 def update_status(self):
421 if self.network is None or not self.network.is_running():
423 icon = QIcon(":icons/status_disconnected.png")
425 elif self.network.is_connected():
426 if not self.wallet.up_to_date:
427 text = _("Synchronizing...")
428 icon = QIcon(":icons/status_waiting.png")
429 elif self.network.server_lag > 1:
430 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
431 icon = QIcon(":icons/status_lagging.png")
433 c, u = self.wallet.get_account_balance(self.current_account)
434 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
435 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
437 # append fiat balance and price from exchange rate plugin
439 run_hook('get_fiat_status_text', c+u, r)
444 self.tray.setToolTip(text)
445 icon = QIcon(":icons/status_connected.png")
447 text = _("Not connected")
448 icon = QIcon(":icons/status_disconnected.png")
450 self.balance_label.setText(text)
451 self.status_button.setIcon( icon )
454 def update_wallet(self):
456 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
457 self.update_history_tab()
458 self.update_receive_tab()
459 self.update_contacts_tab()
460 self.update_completions()
463 def create_history_tab(self):
464 self.history_list = l = MyTreeWidget(self)
466 for i,width in enumerate(self.column_widths['history']):
467 l.setColumnWidth(i, width)
468 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
469 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
470 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
472 l.customContextMenuRequested.connect(self.create_history_menu)
476 def create_history_menu(self, position):
477 self.history_list.selectedIndexes()
478 item = self.history_list.currentItem()
479 be = self.config.get('block_explorer', 'Blockchain.info')
480 if be == 'Blockchain.info':
481 block_explorer = 'https://blockchain.info/tx/'
482 elif be == 'Blockr.io':
483 block_explorer = 'https://blockr.io/tx/info/'
484 elif be == 'Insight.is':
485 block_explorer = 'http://live.insight.is/tx/'
487 tx_hash = str(item.data(0, Qt.UserRole).toString())
488 if not tx_hash: return
490 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
491 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
492 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
493 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
494 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
497 def show_transaction(self, tx):
498 import transaction_dialog
499 d = transaction_dialog.TxDialog(tx, self)
502 def tx_label_clicked(self, item, column):
503 if column==2 and item.isSelected():
505 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
506 self.history_list.editItem( item, column )
507 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
510 def tx_label_changed(self, item, column):
514 tx_hash = str(item.data(0, Qt.UserRole).toString())
515 tx = self.wallet.transactions.get(tx_hash)
516 text = unicode( item.text(2) )
517 self.wallet.set_label(tx_hash, text)
519 item.setForeground(2, QBrush(QColor('black')))
521 text = self.wallet.get_default_label(tx_hash)
522 item.setText(2, text)
523 item.setForeground(2, QBrush(QColor('gray')))
527 def edit_label(self, is_recv):
528 l = self.receive_list if is_recv else self.contacts_list
529 item = l.currentItem()
530 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
531 l.editItem( item, 1 )
532 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
536 def address_label_clicked(self, item, column, l, column_addr, column_label):
537 if column == column_label and item.isSelected():
538 is_editable = item.data(0, 32).toBool()
541 addr = unicode( item.text(column_addr) )
542 label = unicode( item.text(column_label) )
543 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
544 l.editItem( item, column )
545 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
548 def address_label_changed(self, item, column, l, column_addr, column_label):
549 if column == column_label:
550 addr = unicode( item.text(column_addr) )
551 text = unicode( item.text(column_label) )
552 is_editable = item.data(0, 32).toBool()
556 changed = self.wallet.set_label(addr, text)
558 self.update_history_tab()
559 self.update_completions()
561 self.current_item_changed(item)
563 run_hook('item_changed', item, column)
566 def current_item_changed(self, a):
567 run_hook('current_item_changed', a)
571 def update_history_tab(self):
573 self.history_list.clear()
574 for item in self.wallet.get_tx_history(self.current_account):
575 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
576 time_str = _("unknown")
579 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
581 time_str = _("error")
584 time_str = 'unverified'
585 icon = QIcon(":icons/unconfirmed.png")
588 icon = QIcon(":icons/unconfirmed.png")
590 icon = QIcon(":icons/clock%d.png"%conf)
592 icon = QIcon(":icons/confirmed.png")
594 if value is not None:
595 v_str = self.format_amount(value, True, whitespaces=True)
599 balance_str = self.format_amount(balance, whitespaces=True)
602 label, is_default_label = self.wallet.get_label(tx_hash)
604 label = _('Pruned transaction outputs')
605 is_default_label = False
607 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
608 item.setFont(2, QFont(MONOSPACE_FONT))
609 item.setFont(3, QFont(MONOSPACE_FONT))
610 item.setFont(4, QFont(MONOSPACE_FONT))
612 item.setForeground(3, QBrush(QColor("#BC1E1E")))
614 item.setData(0, Qt.UserRole, tx_hash)
615 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
617 item.setForeground(2, QBrush(QColor('grey')))
619 item.setIcon(0, icon)
620 self.history_list.insertTopLevelItem(0,item)
623 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
624 run_hook('history_tab_update')
627 def create_send_tab(self):
632 grid.setColumnMinimumWidth(3,300)
633 grid.setColumnStretch(5,1)
636 self.payto_e = QLineEdit()
637 grid.addWidget(QLabel(_('Pay to')), 1, 0)
638 grid.addWidget(self.payto_e, 1, 1, 1, 3)
640 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)
642 completer = QCompleter()
643 completer.setCaseSensitivity(False)
644 self.payto_e.setCompleter(completer)
645 completer.setModel(self.completions)
647 self.message_e = QLineEdit()
648 grid.addWidget(QLabel(_('Description')), 2, 0)
649 grid.addWidget(self.message_e, 2, 1, 1, 3)
650 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)
652 self.from_label = QLabel(_('From'))
653 grid.addWidget(self.from_label, 3, 0)
654 self.from_list = QTreeWidget(self)
655 self.from_list.setColumnCount(2)
656 self.from_list.setColumnWidth(0, 350)
657 self.from_list.setColumnWidth(1, 50)
658 self.from_list.setHeaderHidden (True)
659 self.from_list.setMaximumHeight(80)
660 grid.addWidget(self.from_list, 3, 1, 1, 3)
661 self.set_pay_from([])
663 self.amount_e = AmountEdit(self.base_unit)
664 grid.addWidget(QLabel(_('Amount')), 4, 0)
665 grid.addWidget(self.amount_e, 4, 1, 1, 2)
666 grid.addWidget(HelpButton(
667 _('Amount to be sent.') + '\n\n' \
668 + _('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.') \
669 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
671 self.fee_e = AmountEdit(self.base_unit)
672 grid.addWidget(QLabel(_('Fee')), 5, 0)
673 grid.addWidget(self.fee_e, 5, 1, 1, 2)
674 grid.addWidget(HelpButton(
675 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
676 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
677 + _('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)
679 run_hook('exchange_rate_button', grid)
681 self.send_button = EnterButton(_("Send"), self.do_send)
682 grid.addWidget(self.send_button, 6, 1)
684 b = EnterButton(_("Clear"),self.do_clear)
685 grid.addWidget(b, 6, 2)
687 self.payto_sig = QLabel('')
688 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
690 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
691 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
700 def entry_changed( is_fee ):
701 self.funds_error = False
703 if self.amount_e.is_shortcut:
704 self.amount_e.is_shortcut = False
705 sendable = self.get_sendable_balance()
706 # there is only one output because we are completely spending inputs
707 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
708 fee = self.wallet.estimated_fee(inputs, 1)
710 self.amount_e.setText( self.format_amount(amount) )
711 self.fee_e.setText( self.format_amount( fee ) )
714 amount = self.read_amount(str(self.amount_e.text()))
715 fee = self.read_amount(str(self.fee_e.text()))
717 if not is_fee: fee = None
720 # assume that there will be 2 outputs (one for change)
721 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
723 self.fee_e.setText( self.format_amount( fee ) )
726 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
730 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
731 self.funds_error = True
732 text = _( "Not enough funds" )
733 c, u = self.wallet.get_frozen_balance()
734 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
736 self.statusBar().showMessage(text)
737 self.amount_e.setPalette(palette)
738 self.fee_e.setPalette(palette)
740 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
741 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
743 run_hook('create_send_tab', grid)
747 def set_pay_from(self, l):
749 self.from_list.clear()
750 self.from_label.setHidden(len(self.pay_from) == 0)
751 self.from_list.setHidden(len(self.pay_from) == 0)
752 for addr in self.pay_from:
753 c, u = self.wallet.get_addr_balance(addr)
754 balance = self.format_amount(c + u)
755 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
758 def update_completions(self):
760 for addr,label in self.wallet.labels.items():
761 if addr in self.wallet.addressbook:
762 l.append( label + ' <' + addr + '>')
764 run_hook('update_completions', l)
765 self.completions.setStringList(l)
769 return lambda s, *args: s.do_protect(func, args)
773 label = unicode( self.message_e.text() )
775 if self.gui_object.payment_request:
776 outputs = self.gui_object.payment_request.outputs
777 amount = self.gui_object.payment_request.get_amount()
780 r = unicode( self.payto_e.text() )
783 # label or alias, with address in brackets
784 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
785 to_address = m.group(2) if m else r
786 if not is_valid(to_address):
787 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
791 amount = self.read_amount(unicode( self.amount_e.text()))
793 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
796 outputs = [(to_address, amount)]
799 fee = self.read_amount(unicode( self.fee_e.text()))
801 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
804 confirm_amount = self.config.get('confirm_amount', 100000000)
805 if amount >= confirm_amount:
806 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
809 confirm_fee = self.config.get('confirm_fee', 100000)
810 if fee >= confirm_fee:
811 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()}):
814 self.send_tx(outputs, fee, label)
817 def waiting_dialog(self, message):
819 d.setWindowTitle('Please wait')
821 vbox = QVBoxLayout(d)
828 def send_tx(self, outputs, fee, label, password):
830 # first, create an unsigned tx
831 domain = self.get_payment_sources()
833 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
835 except Exception as e:
836 traceback.print_exc(file=sys.stdout)
837 self.show_message(str(e))
840 # call hook to see if plugin needs gui interaction
841 run_hook('send_tx', tx)
847 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
848 self.wallet.sign_transaction(tx, keypairs, password)
849 self.signed_tx_data = (tx, fee, label)
850 self.emit(SIGNAL('send_tx2'))
851 self.tx_wait_dialog = self.waiting_dialog('Signing..')
852 threading.Thread(target=sign_thread).start()
857 tx, fee, label = self.signed_tx_data
858 self.tx_wait_dialog.accept()
861 self.show_message(tx.error)
864 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
865 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
869 self.wallet.set_label(tx.hash(), label)
871 if not tx.is_complete() or self.config.get('show_before_broadcast'):
872 self.show_transaction(tx)
875 def broadcast_thread():
876 if self.gui_object.payment_request:
878 refund_address = self.wallet.addresses()[0]
879 self.gui_object.payment_request.send_ack(str(tx), refund_address)
880 self.gui_object.payment_request = None
881 # note: BIP 70 recommends not broadcasting the tx to the network and letting the merchant do that
882 self.tx_broadcast_result = self.wallet.sendtx(tx)
883 self.emit(SIGNAL('send_tx3'))
885 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
886 threading.Thread(target=broadcast_thread).start()
891 self.tx_broadcast_dialog.accept()
892 status, msg = self.tx_broadcast_result
894 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
897 QMessageBox.warning(self, _('Error'), msg, _('OK'))
901 def prepare_for_payment_request(self):
902 style = "QWidget { background-color:none;border:none;}"
903 self.tabs.setCurrentIndex(1)
904 self.payto_e.setReadOnly(True)
905 self.payto_e.setStyleSheet(style)
906 self.amount_e.setReadOnly(True)
907 self.payto_e.setText(_("please wait..."))
908 self.amount_e.setStyleSheet(style)
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()))
915 def payment_request_error(self):
916 self.payto_e.setText(self.gui_object.payment_request.error)
919 def set_send(self, address, amount, label, message):
921 if label and self.wallet.labels.get(address) != label:
922 if self.question('Give label "%s" to address %s ?'%(label,address)):
923 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
924 self.wallet.addressbook.append(address)
925 self.wallet.set_label(address, label)
927 self.tabs.setCurrentIndex(1)
928 label = self.wallet.labels.get(address)
929 m_addr = label + ' <'+ address +'>' if label else address
930 self.payto_e.setText(m_addr)
932 self.message_e.setText(message)
934 self.amount_e.setText(amount)
938 self.payto_sig.setVisible(False)
939 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
941 self.set_frozen(e,False)
944 self.set_pay_from([])
947 def set_frozen(self,entry,frozen):
949 entry.setReadOnly(True)
950 entry.setFrame(False)
952 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
953 entry.setPalette(palette)
955 entry.setReadOnly(False)
958 palette.setColor(entry.backgroundRole(), QColor('white'))
959 entry.setPalette(palette)
962 def set_addrs_frozen(self,addrs,freeze):
964 if not addr: continue
965 if addr in self.wallet.frozen_addresses and not freeze:
966 self.wallet.unfreeze(addr)
967 elif addr not in self.wallet.frozen_addresses and freeze:
968 self.wallet.freeze(addr)
969 self.update_receive_tab()
973 def create_list_tab(self, headers):
974 "generic tab creation method"
975 l = MyTreeWidget(self)
976 l.setColumnCount( len(headers) )
977 l.setHeaderLabels( headers )
987 vbox.addWidget(buttons)
992 buttons.setLayout(hbox)
997 def create_receive_tab(self):
998 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
999 l.setContextMenuPolicy(Qt.CustomContextMenu)
1000 l.customContextMenuRequested.connect(self.create_receive_menu)
1001 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1002 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1003 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1004 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1005 self.receive_list = l
1006 self.receive_buttons_hbox = hbox
1013 def save_column_widths(self):
1014 self.column_widths["receive"] = []
1015 for i in range(self.receive_list.columnCount() -1):
1016 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1018 self.column_widths["history"] = []
1019 for i in range(self.history_list.columnCount() - 1):
1020 self.column_widths["history"].append(self.history_list.columnWidth(i))
1022 self.column_widths["contacts"] = []
1023 for i in range(self.contacts_list.columnCount() - 1):
1024 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1026 self.config.set_key("column_widths_2", self.column_widths, True)
1029 def create_contacts_tab(self):
1030 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1031 l.setContextMenuPolicy(Qt.CustomContextMenu)
1032 l.customContextMenuRequested.connect(self.create_contact_menu)
1033 for i,width in enumerate(self.column_widths['contacts']):
1034 l.setColumnWidth(i, width)
1036 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1037 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1038 self.contacts_list = l
1039 self.contacts_buttons_hbox = hbox
1044 def delete_imported_key(self, addr):
1045 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1046 self.wallet.delete_imported_key(addr)
1047 self.update_receive_tab()
1048 self.update_history_tab()
1050 def edit_account_label(self, k):
1051 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1053 label = unicode(text)
1054 self.wallet.set_label(k,label)
1055 self.update_receive_tab()
1057 def account_set_expanded(self, item, k, b):
1059 self.accounts_expanded[k] = b
1061 def create_account_menu(self, position, k, item):
1063 if item.isExpanded():
1064 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1066 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1067 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1068 if self.wallet.seed_version > 4:
1069 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1070 if self.wallet.account_is_pending(k):
1071 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1072 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1074 def delete_pending_account(self, k):
1075 self.wallet.delete_pending_account(k)
1076 self.update_receive_tab()
1078 def create_receive_menu(self, position):
1079 # fixme: this function apparently has a side effect.
1080 # if it is not called the menu pops up several times
1081 #self.receive_list.selectedIndexes()
1083 selected = self.receive_list.selectedItems()
1084 multi_select = len(selected) > 1
1085 addrs = [unicode(item.text(0)) for item in selected]
1086 if not multi_select:
1087 item = self.receive_list.itemAt(position)
1091 if not is_valid(addr):
1092 k = str(item.data(0,32).toString())
1094 self.create_account_menu(position, k, item)
1096 item.setExpanded(not item.isExpanded())
1100 if not multi_select:
1101 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1102 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1103 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1104 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1105 if not self.wallet.is_watching_only():
1106 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1107 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1108 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1109 if self.wallet.is_imported(addr):
1110 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1112 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1113 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1114 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1115 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1117 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1118 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1120 run_hook('receive_menu', menu, addrs)
1121 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1124 def get_sendable_balance(self):
1125 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1128 def get_payment_sources(self):
1130 return self.pay_from
1132 return self.wallet.get_account_addresses(self.current_account)
1135 def send_from_addresses(self, addrs):
1136 self.set_pay_from( addrs )
1137 self.tabs.setCurrentIndex(1)
1140 def payto(self, addr):
1142 label = self.wallet.labels.get(addr)
1143 m_addr = label + ' <' + addr + '>' if label else addr
1144 self.tabs.setCurrentIndex(1)
1145 self.payto_e.setText(m_addr)
1146 self.amount_e.setFocus()
1149 def delete_contact(self, x):
1150 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1151 self.wallet.delete_contact(x)
1152 self.wallet.set_label(x, None)
1153 self.update_history_tab()
1154 self.update_contacts_tab()
1155 self.update_completions()
1158 def create_contact_menu(self, position):
1159 item = self.contacts_list.itemAt(position)
1162 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1164 addr = unicode(item.text(0))
1165 label = unicode(item.text(1))
1166 is_editable = item.data(0,32).toBool()
1167 payto_addr = item.data(0,33).toString()
1168 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1169 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1170 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1172 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1173 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1175 run_hook('create_contact_menu', menu, item)
1176 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1179 def update_receive_item(self, item):
1180 item.setFont(0, QFont(MONOSPACE_FONT))
1181 address = str(item.data(0,0).toString())
1182 label = self.wallet.labels.get(address,'')
1183 item.setData(1,0,label)
1184 item.setData(0,32, True) # is editable
1186 run_hook('update_receive_item', address, item)
1188 if not self.wallet.is_mine(address): return
1190 c, u = self.wallet.get_addr_balance(address)
1191 balance = self.format_amount(c + u)
1192 item.setData(2,0,balance)
1194 if address in self.wallet.frozen_addresses:
1195 item.setBackgroundColor(0, QColor('lightblue'))
1198 def update_receive_tab(self):
1199 l = self.receive_list
1200 # extend the syntax for consistency
1201 l.addChild = l.addTopLevelItem
1204 for i,width in enumerate(self.column_widths['receive']):
1205 l.setColumnWidth(i, width)
1207 accounts = self.wallet.get_accounts()
1208 if self.current_account is None:
1209 account_items = sorted(accounts.items())
1211 account_items = [(self.current_account, accounts.get(self.current_account))]
1214 for k, account in account_items:
1216 if len(accounts) > 1:
1217 name = self.wallet.get_account_name(k)
1218 c,u = self.wallet.get_account_balance(k)
1219 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1220 l.addTopLevelItem(account_item)
1221 account_item.setExpanded(self.accounts_expanded.get(k, True))
1222 account_item.setData(0, 32, k)
1226 sequences = [0,1] if account.has_change() else [0]
1227 for is_change in sequences:
1228 if len(sequences) > 1:
1229 name = _("Receiving") if not is_change else _("Change")
1230 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1231 account_item.addChild(seq_item)
1233 seq_item.setExpanded(True)
1235 seq_item = account_item
1237 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1243 for address in account.get_addresses(is_change):
1244 h = self.wallet.history.get(address,[])
1248 if gap > self.wallet.gap_limit:
1253 c, u = self.wallet.get_addr_balance(address)
1254 num_tx = '*' if h == ['*'] else "%d"%len(h)
1256 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1257 self.update_receive_item(item)
1259 item.setBackgroundColor(1, QColor('red'))
1260 if len(h) > 0 and c == -u:
1262 seq_item.insertChild(0,used_item)
1264 used_item.addChild(item)
1266 seq_item.addChild(item)
1268 # we use column 1 because column 0 may be hidden
1269 l.setCurrentItem(l.topLevelItem(0),1)
1272 def update_contacts_tab(self):
1273 l = self.contacts_list
1276 for address in self.wallet.addressbook:
1277 label = self.wallet.labels.get(address,'')
1278 n = self.wallet.get_num_tx(address)
1279 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1280 item.setFont(0, QFont(MONOSPACE_FONT))
1281 # 32 = label can be edited (bool)
1282 item.setData(0,32, True)
1284 item.setData(0,33, address)
1285 l.addTopLevelItem(item)
1287 run_hook('update_contacts_tab', l)
1288 l.setCurrentItem(l.topLevelItem(0))
1292 def create_console_tab(self):
1293 from console import Console
1294 self.console = console = Console()
1298 def update_console(self):
1299 console = self.console
1300 console.history = self.config.get("console-history",[])
1301 console.history_index = len(console.history)
1303 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1304 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1306 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1308 def mkfunc(f, method):
1309 return lambda *args: apply( f, (method, args, self.password_dialog ))
1311 if m[0]=='_' or m in ['network','wallet']: continue
1312 methods[m] = mkfunc(c._run, m)
1314 console.updateNamespace(methods)
1317 def change_account(self,s):
1318 if s == _("All accounts"):
1319 self.current_account = None
1321 accounts = self.wallet.get_account_names()
1322 for k, v in accounts.items():
1324 self.current_account = k
1325 self.update_history_tab()
1326 self.update_status()
1327 self.update_receive_tab()
1329 def create_status_bar(self):
1332 sb.setFixedHeight(35)
1333 qtVersion = qVersion()
1335 self.balance_label = QLabel("")
1336 sb.addWidget(self.balance_label)
1338 from version_getter import UpdateLabel
1339 self.updatelabel = UpdateLabel(self.config, sb)
1341 self.account_selector = QComboBox()
1342 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1343 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1344 sb.addPermanentWidget(self.account_selector)
1346 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1347 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1349 self.lock_icon = QIcon()
1350 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1351 sb.addPermanentWidget( self.password_button )
1353 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1354 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1355 sb.addPermanentWidget( self.seed_button )
1356 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1357 sb.addPermanentWidget( self.status_button )
1359 run_hook('create_status_bar', (sb,))
1361 self.setStatusBar(sb)
1364 def update_lock_icon(self):
1365 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1366 self.password_button.setIcon( icon )
1369 def update_buttons_on_seed(self):
1370 if self.wallet.has_seed():
1371 self.seed_button.show()
1373 self.seed_button.hide()
1375 if not self.wallet.is_watching_only():
1376 self.password_button.show()
1377 self.send_button.setText(_("Send"))
1379 self.password_button.hide()
1380 self.send_button.setText(_("Create unsigned transaction"))
1383 def change_password_dialog(self):
1384 from password_dialog import PasswordDialog
1385 d = PasswordDialog(self.wallet, self)
1387 self.update_lock_icon()
1390 def new_contact_dialog(self):
1393 d.setWindowTitle(_("New Contact"))
1394 vbox = QVBoxLayout(d)
1395 vbox.addWidget(QLabel(_('New Contact')+':'))
1397 grid = QGridLayout()
1400 grid.addWidget(QLabel(_("Address")), 1, 0)
1401 grid.addWidget(line1, 1, 1)
1402 grid.addWidget(QLabel(_("Name")), 2, 0)
1403 grid.addWidget(line2, 2, 1)
1405 vbox.addLayout(grid)
1406 vbox.addLayout(ok_cancel_buttons(d))
1411 address = str(line1.text())
1412 label = unicode(line2.text())
1414 if not is_valid(address):
1415 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1418 self.wallet.add_contact(address)
1420 self.wallet.set_label(address, label)
1422 self.update_contacts_tab()
1423 self.update_history_tab()
1424 self.update_completions()
1425 self.tabs.setCurrentIndex(3)
1429 def new_account_dialog(self, password):
1431 dialog = QDialog(self)
1433 dialog.setWindowTitle(_("New Account"))
1435 vbox = QVBoxLayout()
1436 vbox.addWidget(QLabel(_('Account name')+':'))
1439 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1440 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1445 vbox.addLayout(ok_cancel_buttons(dialog))
1446 dialog.setLayout(vbox)
1450 name = str(e.text())
1453 self.wallet.create_pending_account(name, password)
1454 self.update_receive_tab()
1455 self.tabs.setCurrentIndex(2)
1460 def show_master_public_keys(self):
1462 dialog = QDialog(self)
1464 dialog.setWindowTitle(_("Master Public Keys"))
1466 main_layout = QGridLayout()
1467 mpk_dict = self.wallet.get_master_public_keys()
1469 for key, value in mpk_dict.items():
1470 main_layout.addWidget(QLabel(key), i, 0)
1471 mpk_text = QTextEdit()
1472 mpk_text.setReadOnly(True)
1473 mpk_text.setMaximumHeight(170)
1474 mpk_text.setText(value)
1475 main_layout.addWidget(mpk_text, i + 1, 0)
1478 vbox = QVBoxLayout()
1479 vbox.addLayout(main_layout)
1480 vbox.addLayout(close_button(dialog))
1482 dialog.setLayout(vbox)
1487 def show_seed_dialog(self, password):
1488 if not self.wallet.has_seed():
1489 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1493 mnemonic = self.wallet.get_mnemonic(password)
1495 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1497 from seed_dialog import SeedDialog
1498 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1503 def show_qrcode(self, data, title = _("QR code")):
1507 d.setWindowTitle(title)
1508 d.setMinimumSize(270, 300)
1509 vbox = QVBoxLayout()
1510 qrw = QRCodeWidget(data)
1511 vbox.addWidget(qrw, 1)
1512 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1513 hbox = QHBoxLayout()
1516 filename = os.path.join(self.config.path, "qrcode.bmp")
1519 bmp.save_qrcode(qrw.qr, filename)
1520 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1522 def copy_to_clipboard():
1523 bmp.save_qrcode(qrw.qr, filename)
1524 self.app.clipboard().setImage(QImage(filename))
1525 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1527 b = QPushButton(_("Copy"))
1529 b.clicked.connect(copy_to_clipboard)
1531 b = QPushButton(_("Save"))
1533 b.clicked.connect(print_qr)
1535 b = QPushButton(_("Close"))
1537 b.clicked.connect(d.accept)
1540 vbox.addLayout(hbox)
1545 def do_protect(self, func, args):
1546 if self.wallet.use_encryption:
1547 password = self.password_dialog()
1553 if args != (False,):
1554 args = (self,) + args + (password,)
1556 args = (self,password)
1560 def show_public_keys(self, address):
1561 if not address: return
1563 pubkey_list = self.wallet.get_public_keys(address)
1564 except Exception as e:
1565 traceback.print_exc(file=sys.stdout)
1566 self.show_message(str(e))
1570 d.setMinimumSize(600, 200)
1572 vbox = QVBoxLayout()
1573 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1574 vbox.addWidget( QLabel(_("Public key") + ':'))
1576 keys.setReadOnly(True)
1577 keys.setText('\n'.join(pubkey_list))
1578 vbox.addWidget(keys)
1579 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1580 vbox.addLayout(close_button(d))
1585 def show_private_key(self, address, password):
1586 if not address: return
1588 pk_list = self.wallet.get_private_key(address, password)
1589 except Exception as e:
1590 traceback.print_exc(file=sys.stdout)
1591 self.show_message(str(e))
1595 d.setMinimumSize(600, 200)
1597 vbox = QVBoxLayout()
1598 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1599 vbox.addWidget( QLabel(_("Private key") + ':'))
1601 keys.setReadOnly(True)
1602 keys.setText('\n'.join(pk_list))
1603 vbox.addWidget(keys)
1604 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1605 vbox.addLayout(close_button(d))
1611 def do_sign(self, address, message, signature, password):
1612 message = unicode(message.toPlainText())
1613 message = message.encode('utf-8')
1615 sig = self.wallet.sign_message(str(address.text()), message, password)
1616 signature.setText(sig)
1617 except Exception as e:
1618 self.show_message(str(e))
1620 def do_verify(self, address, message, signature):
1621 message = unicode(message.toPlainText())
1622 message = message.encode('utf-8')
1623 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1624 self.show_message(_("Signature verified"))
1626 self.show_message(_("Error: wrong signature"))
1629 def sign_verify_message(self, address=''):
1632 d.setWindowTitle(_('Sign/verify Message'))
1633 d.setMinimumSize(410, 290)
1635 layout = QGridLayout(d)
1637 message_e = QTextEdit()
1638 layout.addWidget(QLabel(_('Message')), 1, 0)
1639 layout.addWidget(message_e, 1, 1)
1640 layout.setRowStretch(2,3)
1642 address_e = QLineEdit()
1643 address_e.setText(address)
1644 layout.addWidget(QLabel(_('Address')), 2, 0)
1645 layout.addWidget(address_e, 2, 1)
1647 signature_e = QTextEdit()
1648 layout.addWidget(QLabel(_('Signature')), 3, 0)
1649 layout.addWidget(signature_e, 3, 1)
1650 layout.setRowStretch(3,1)
1652 hbox = QHBoxLayout()
1654 b = QPushButton(_("Sign"))
1655 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1658 b = QPushButton(_("Verify"))
1659 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1662 b = QPushButton(_("Close"))
1663 b.clicked.connect(d.accept)
1665 layout.addLayout(hbox, 4, 1)
1670 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1672 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1673 message_e.setText(decrypted)
1674 except Exception as e:
1675 self.show_message(str(e))
1678 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1679 message = unicode(message_e.toPlainText())
1680 message = message.encode('utf-8')
1682 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1683 encrypted_e.setText(encrypted)
1684 except Exception as e:
1685 self.show_message(str(e))
1689 def encrypt_message(self, address = ''):
1692 d.setWindowTitle(_('Encrypt/decrypt Message'))
1693 d.setMinimumSize(610, 490)
1695 layout = QGridLayout(d)
1697 message_e = QTextEdit()
1698 layout.addWidget(QLabel(_('Message')), 1, 0)
1699 layout.addWidget(message_e, 1, 1)
1700 layout.setRowStretch(2,3)
1702 pubkey_e = QLineEdit()
1704 pubkey = self.wallet.getpubkeys(address)[0]
1705 pubkey_e.setText(pubkey)
1706 layout.addWidget(QLabel(_('Public key')), 2, 0)
1707 layout.addWidget(pubkey_e, 2, 1)
1709 encrypted_e = QTextEdit()
1710 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1711 layout.addWidget(encrypted_e, 3, 1)
1712 layout.setRowStretch(3,1)
1714 hbox = QHBoxLayout()
1715 b = QPushButton(_("Encrypt"))
1716 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1719 b = QPushButton(_("Decrypt"))
1720 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1723 b = QPushButton(_("Close"))
1724 b.clicked.connect(d.accept)
1727 layout.addLayout(hbox, 4, 1)
1731 def question(self, msg):
1732 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1734 def show_message(self, msg):
1735 QMessageBox.information(self, _('Message'), msg, _('OK'))
1737 def password_dialog(self ):
1740 d.setWindowTitle(_("Enter Password"))
1745 vbox = QVBoxLayout()
1746 msg = _('Please enter your password')
1747 vbox.addWidget(QLabel(msg))
1749 grid = QGridLayout()
1751 grid.addWidget(QLabel(_('Password')), 1, 0)
1752 grid.addWidget(pw, 1, 1)
1753 vbox.addLayout(grid)
1755 vbox.addLayout(ok_cancel_buttons(d))
1758 run_hook('password_dialog', pw, grid, 1)
1759 if not d.exec_(): return
1760 return unicode(pw.text())
1769 def tx_from_text(self, txt):
1770 "json or raw hexadecimal"
1773 tx = Transaction(txt)
1779 tx_dict = json.loads(str(txt))
1780 assert "hex" in tx_dict.keys()
1781 assert "complete" in tx_dict.keys()
1782 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1783 if not tx_dict["complete"]:
1784 assert "input_info" in tx_dict.keys()
1785 input_info = json.loads(tx_dict['input_info'])
1786 tx.add_input_info(input_info)
1791 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1795 def read_tx_from_file(self):
1796 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1800 with open(fileName, "r") as f:
1801 file_content = f.read()
1802 except (ValueError, IOError, os.error), reason:
1803 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1805 return self.tx_from_text(file_content)
1809 def sign_raw_transaction(self, tx, input_info, password):
1810 self.wallet.signrawtransaction(tx, input_info, [], password)
1812 def do_process_from_text(self):
1813 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1816 tx = self.tx_from_text(text)
1818 self.show_transaction(tx)
1820 def do_process_from_file(self):
1821 tx = self.read_tx_from_file()
1823 self.show_transaction(tx)
1825 def do_process_from_txid(self):
1826 from electrum import transaction
1827 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1829 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1831 tx = transaction.Transaction(r)
1833 self.show_transaction(tx)
1835 self.show_message("unknown transaction")
1837 def do_process_from_csvReader(self, csvReader):
1842 for position, row in enumerate(csvReader):
1844 if not is_valid(address):
1845 errors.append((position, address))
1847 amount = Decimal(row[1])
1848 amount = int(100000000*amount)
1849 outputs.append((address, amount))
1850 except (ValueError, IOError, os.error), reason:
1851 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1855 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1856 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1860 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1861 except Exception as e:
1862 self.show_message(str(e))
1865 self.show_transaction(tx)
1867 def do_process_from_csv_file(self):
1868 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1872 with open(fileName, "r") as f:
1873 csvReader = csv.reader(f)
1874 self.do_process_from_csvReader(csvReader)
1875 except (ValueError, IOError, os.error), reason:
1876 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1879 def do_process_from_csv_text(self):
1880 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1881 + _("Format: address, amount. One output per line"), _("Load CSV"))
1884 f = StringIO.StringIO(text)
1885 csvReader = csv.reader(f)
1886 self.do_process_from_csvReader(csvReader)
1891 def export_privkeys_dialog(self, password):
1892 if self.wallet.is_watching_only():
1893 self.show_message(_("This is a watching-only wallet"))
1897 d.setWindowTitle(_('Private keys'))
1898 d.setMinimumSize(850, 300)
1899 vbox = QVBoxLayout(d)
1901 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1902 _("Exposing a single private key can compromise your entire wallet!"),
1903 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1904 vbox.addWidget(QLabel(msg))
1910 defaultname = 'electrum-private-keys.csv'
1911 select_msg = _('Select file to export your private keys to')
1912 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1913 vbox.addLayout(hbox)
1915 h, b = ok_cancel_buttons2(d, _('Export'))
1920 addresses = self.wallet.addresses(True)
1922 def privkeys_thread():
1923 for addr in addresses:
1927 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1928 d.emit(SIGNAL('computing_privkeys'))
1929 d.emit(SIGNAL('show_privkeys'))
1931 def show_privkeys():
1932 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1936 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1937 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1938 threading.Thread(target=privkeys_thread).start()
1944 filename = filename_e.text()
1949 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1950 except (IOError, os.error), reason:
1951 export_error_label = _("Electrum was unable to produce a private key-export.")
1952 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1954 except Exception as e:
1955 self.show_message(str(e))
1958 self.show_message(_("Private keys exported."))
1961 def do_export_privkeys(self, fileName, pklist, is_csv):
1962 with open(fileName, "w+") as f:
1964 transaction = csv.writer(f)
1965 transaction.writerow(["address", "private_key"])
1966 for addr, pk in pklist.items():
1967 transaction.writerow(["%34s"%addr,pk])
1970 f.write(json.dumps(pklist, indent = 4))
1973 def do_import_labels(self):
1974 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1975 if not labelsFile: return
1977 f = open(labelsFile, 'r')
1980 for key, value in json.loads(data).items():
1981 self.wallet.set_label(key, value)
1982 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1983 except (IOError, os.error), reason:
1984 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1987 def do_export_labels(self):
1988 labels = self.wallet.labels
1990 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1992 with open(fileName, 'w+') as f:
1993 json.dump(labels, f)
1994 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1995 except (IOError, os.error), reason:
1996 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1999 def export_history_dialog(self):
2002 d.setWindowTitle(_('Export History'))
2003 d.setMinimumSize(400, 200)
2004 vbox = QVBoxLayout(d)
2006 defaultname = os.path.expanduser('~/electrum-history.csv')
2007 select_msg = _('Select file to export your wallet transactions to')
2009 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2010 vbox.addLayout(hbox)
2014 h, b = ok_cancel_buttons2(d, _('Export'))
2019 filename = filename_e.text()
2024 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2025 except (IOError, os.error), reason:
2026 export_error_label = _("Electrum was unable to produce a transaction export.")
2027 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2030 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2033 def do_export_history(self, wallet, fileName, is_csv):
2034 history = wallet.get_tx_history()
2036 for item in history:
2037 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2039 if timestamp is not None:
2041 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2042 except [RuntimeError, TypeError, NameError] as reason:
2043 time_string = "unknown"
2046 time_string = "unknown"
2048 time_string = "pending"
2050 if value is not None:
2051 value_string = format_satoshis(value, True)
2056 fee_string = format_satoshis(fee, True)
2061 label, is_default_label = wallet.get_label(tx_hash)
2062 label = label.encode('utf-8')
2066 balance_string = format_satoshis(balance, False)
2068 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2070 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2072 with open(fileName, "w+") as f:
2074 transaction = csv.writer(f)
2075 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2077 transaction.writerow(line)
2080 f.write(json.dumps(lines, indent = 4))
2083 def sweep_key_dialog(self):
2085 d.setWindowTitle(_('Sweep private keys'))
2086 d.setMinimumSize(600, 300)
2088 vbox = QVBoxLayout(d)
2089 vbox.addWidget(QLabel(_("Enter private keys")))
2091 keys_e = QTextEdit()
2092 keys_e.setTabChangesFocus(True)
2093 vbox.addWidget(keys_e)
2095 h, address_e = address_field(self.wallet.addresses())
2099 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2100 vbox.addLayout(hbox)
2101 button.setEnabled(False)
2104 addr = str(address_e.text())
2105 if bitcoin.is_address(addr):
2109 pk = str(keys_e.toPlainText()).strip()
2110 if Wallet.is_private_key(pk):
2113 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2114 keys_e.textChanged.connect(f)
2115 address_e.textChanged.connect(f)
2119 fee = self.wallet.fee
2120 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2121 self.show_transaction(tx)
2125 def do_import_privkey(self, password):
2126 if not self.wallet.imported_keys:
2127 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2128 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2129 + _('Are you sure you understand what you are doing?'), 3, 4)
2132 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2135 text = str(text).split()
2140 addr = self.wallet.import_key(key, password)
2141 except Exception as e:
2147 addrlist.append(addr)
2149 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2151 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2152 self.update_receive_tab()
2153 self.update_history_tab()
2156 def settings_dialog(self):
2158 d.setWindowTitle(_('Electrum Settings'))
2160 vbox = QVBoxLayout()
2161 grid = QGridLayout()
2162 grid.setColumnStretch(0,1)
2164 nz_label = QLabel(_('Display zeros') + ':')
2165 grid.addWidget(nz_label, 0, 0)
2166 nz_e = AmountEdit(None,True)
2167 nz_e.setText("%d"% self.num_zeros)
2168 grid.addWidget(nz_e, 0, 1)
2169 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2170 grid.addWidget(HelpButton(msg), 0, 2)
2171 if not self.config.is_modifiable('num_zeros'):
2172 for w in [nz_e, nz_label]: w.setEnabled(False)
2174 lang_label=QLabel(_('Language') + ':')
2175 grid.addWidget(lang_label, 1, 0)
2176 lang_combo = QComboBox()
2177 from electrum.i18n import languages
2178 lang_combo.addItems(languages.values())
2180 index = languages.keys().index(self.config.get("language",''))
2183 lang_combo.setCurrentIndex(index)
2184 grid.addWidget(lang_combo, 1, 1)
2185 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2186 if not self.config.is_modifiable('language'):
2187 for w in [lang_combo, lang_label]: w.setEnabled(False)
2190 fee_label = QLabel(_('Transaction fee') + ':')
2191 grid.addWidget(fee_label, 2, 0)
2192 fee_e = AmountEdit(self.base_unit)
2193 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2194 grid.addWidget(fee_e, 2, 1)
2195 msg = _('Fee per kilobyte of transaction.') + ' ' \
2196 + _('Recommended value') + ': ' + self.format_amount(20000)
2197 grid.addWidget(HelpButton(msg), 2, 2)
2198 if not self.config.is_modifiable('fee_per_kb'):
2199 for w in [fee_e, fee_label]: w.setEnabled(False)
2201 units = ['BTC', 'mBTC']
2202 unit_label = QLabel(_('Base unit') + ':')
2203 grid.addWidget(unit_label, 3, 0)
2204 unit_combo = QComboBox()
2205 unit_combo.addItems(units)
2206 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2207 grid.addWidget(unit_combo, 3, 1)
2208 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2209 + '\n1BTC=1000mBTC.\n' \
2210 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2212 usechange_cb = QCheckBox(_('Use change addresses'))
2213 usechange_cb.setChecked(self.wallet.use_change)
2214 grid.addWidget(usechange_cb, 4, 0)
2215 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2216 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2218 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2219 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2220 grid.addWidget(block_ex_label, 5, 0)
2221 block_ex_combo = QComboBox()
2222 block_ex_combo.addItems(block_explorers)
2223 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2224 grid.addWidget(block_ex_combo, 5, 1)
2225 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2227 show_tx = self.config.get('show_before_broadcast', False)
2228 showtx_cb = QCheckBox(_('Show before broadcast'))
2229 showtx_cb.setChecked(show_tx)
2230 grid.addWidget(showtx_cb, 6, 0)
2231 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2233 vbox.addLayout(grid)
2235 vbox.addLayout(ok_cancel_buttons(d))
2239 if not d.exec_(): return
2241 fee = unicode(fee_e.text())
2243 fee = self.read_amount(fee)
2245 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2248 self.wallet.set_fee(fee)
2250 nz = unicode(nz_e.text())
2255 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2258 if self.num_zeros != nz:
2260 self.config.set_key('num_zeros', nz, True)
2261 self.update_history_tab()
2262 self.update_receive_tab()
2264 usechange_result = usechange_cb.isChecked()
2265 if self.wallet.use_change != usechange_result:
2266 self.wallet.use_change = usechange_result
2267 self.wallet.storage.put('use_change', self.wallet.use_change)
2269 if showtx_cb.isChecked() != show_tx:
2270 self.config.set_key('show_before_broadcast', not show_tx)
2272 unit_result = units[unit_combo.currentIndex()]
2273 if self.base_unit() != unit_result:
2274 self.decimal_point = 8 if unit_result == 'BTC' else 5
2275 self.config.set_key('decimal_point', self.decimal_point, True)
2276 self.update_history_tab()
2277 self.update_status()
2279 need_restart = False
2281 lang_request = languages.keys()[lang_combo.currentIndex()]
2282 if lang_request != self.config.get('language'):
2283 self.config.set_key("language", lang_request, True)
2286 be_result = block_explorers[block_ex_combo.currentIndex()]
2287 self.config.set_key('block_explorer', be_result, True)
2289 run_hook('close_settings_dialog')
2292 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2295 def run_network_dialog(self):
2296 if not self.network:
2298 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2300 def closeEvent(self, event):
2302 self.config.set_key("is_maximized", self.isMaximized())
2303 if not self.isMaximized():
2305 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2306 self.save_column_widths()
2307 self.config.set_key("console-history", self.console.history[-50:], True)
2308 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2312 def plugins_dialog(self):
2313 from electrum.plugins import plugins
2316 d.setWindowTitle(_('Electrum Plugins'))
2319 vbox = QVBoxLayout(d)
2322 scroll = QScrollArea()
2323 scroll.setEnabled(True)
2324 scroll.setWidgetResizable(True)
2325 scroll.setMinimumSize(400,250)
2326 vbox.addWidget(scroll)
2330 w.setMinimumHeight(len(plugins)*35)
2332 grid = QGridLayout()
2333 grid.setColumnStretch(0,1)
2336 def do_toggle(cb, p, w):
2339 if w: w.setEnabled(r)
2341 def mk_toggle(cb, p, w):
2342 return lambda: do_toggle(cb,p,w)
2344 for i, p in enumerate(plugins):
2346 cb = QCheckBox(p.fullname())
2347 cb.setDisabled(not p.is_available())
2348 cb.setChecked(p.is_enabled())
2349 grid.addWidget(cb, i, 0)
2350 if p.requires_settings():
2351 w = p.settings_widget(self)
2352 w.setEnabled( p.is_enabled() )
2353 grid.addWidget(w, i, 1)
2356 cb.clicked.connect(mk_toggle(cb,p,w))
2357 grid.addWidget(HelpButton(p.description()), i, 2)
2359 print_msg(_("Error: cannot display plugin"), p)
2360 traceback.print_exc(file=sys.stdout)
2361 grid.setRowStretch(i+1,1)
2363 vbox.addLayout(close_button(d))
2368 def show_account_details(self, k):
2369 account = self.wallet.accounts[k]
2372 d.setWindowTitle(_('Account Details'))
2375 vbox = QVBoxLayout(d)
2376 name = self.wallet.get_account_name(k)
2377 label = QLabel('Name: ' + name)
2378 vbox.addWidget(label)
2380 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2382 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2384 vbox.addWidget(QLabel(_('Master Public Key:')))
2387 text.setReadOnly(True)
2388 text.setMaximumHeight(170)
2389 vbox.addWidget(text)
2391 mpk_text = '\n'.join( account.get_master_pubkeys() )
2392 text.setText(mpk_text)
2394 vbox.addLayout(close_button(d))