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):
191 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
192 self.current_account = self.wallet.storage.get("current_account", None)
194 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
195 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
196 self.setWindowTitle( title )
198 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
199 self.notify_transactions()
200 self.update_account_selector()
202 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
203 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
204 self.password_menu.setEnabled(not self.wallet.is_watching_only())
205 self.seed_menu.setEnabled(self.wallet.has_seed())
206 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
207 self.import_menu.setEnabled(self.wallet.can_import())
209 self.update_lock_icon()
210 self.update_buttons_on_seed()
211 self.update_console()
213 run_hook('load_wallet', wallet)
216 def open_wallet(self):
217 wallet_folder = self.wallet.storage.path
218 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
222 storage = WalletStorage({'wallet_path': filename})
223 if not storage.file_exists:
224 self.show_message("file not found "+ filename)
227 self.wallet.stop_threads()
230 wallet = Wallet(storage)
231 wallet.start_threads(self.network)
233 self.load_wallet(wallet)
237 def backup_wallet(self):
239 path = self.wallet.storage.path
240 wallet_folder = os.path.dirname(path)
241 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
245 new_path = os.path.join(wallet_folder, filename)
248 shutil.copy2(path, new_path)
249 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
250 except (IOError, os.error), reason:
251 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
254 def new_wallet(self):
257 wallet_folder = os.path.dirname(self.wallet.storage.path)
258 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
261 filename = os.path.join(wallet_folder, filename)
263 storage = WalletStorage({'wallet_path': filename})
264 if storage.file_exists:
265 QMessageBox.critical(None, "Error", _("File exists"))
268 wizard = installwizard.InstallWizard(self.config, self.network, storage)
269 wallet = wizard.run('new')
271 self.load_wallet(wallet)
275 def init_menubar(self):
278 file_menu = menubar.addMenu(_("&File"))
279 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
280 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
281 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
282 file_menu.addAction(_("&Quit"), self.close)
284 wallet_menu = menubar.addMenu(_("&Wallet"))
285 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
286 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
288 wallet_menu.addSeparator()
290 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
291 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
292 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
294 wallet_menu.addSeparator()
295 labels_menu = wallet_menu.addMenu(_("&Labels"))
296 labels_menu.addAction(_("&Import"), self.do_import_labels)
297 labels_menu.addAction(_("&Export"), self.do_export_labels)
299 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
300 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
301 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
302 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
303 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
305 tools_menu = menubar.addMenu(_("&Tools"))
307 # Settings / Preferences are all reserved keywords in OSX using this as work around
308 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
309 tools_menu.addAction(_("&Network"), self.run_network_dialog)
310 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
311 tools_menu.addSeparator()
312 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
313 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
314 tools_menu.addSeparator()
316 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
317 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
318 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
320 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
321 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
322 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
323 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
325 help_menu = menubar.addMenu(_("&Help"))
326 help_menu.addAction(_("&About"), self.show_about)
327 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
328 help_menu.addSeparator()
329 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
330 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
332 self.setMenuBar(menubar)
334 def show_about(self):
335 QMessageBox.about(self, "Electrum",
336 _("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."))
338 def show_report_bug(self):
339 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
340 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
343 def notify_transactions(self):
344 if not self.network or not self.network.is_connected():
347 print_error("Notifying GUI")
348 if len(self.network.pending_transactions_for_notifications) > 0:
349 # Combine the transactions if there are more then three
350 tx_amount = len(self.network.pending_transactions_for_notifications)
353 for tx in self.network.pending_transactions_for_notifications:
354 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
358 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
359 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
361 self.network.pending_transactions_for_notifications = []
363 for tx in self.network.pending_transactions_for_notifications:
365 self.network.pending_transactions_for_notifications.remove(tx)
366 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
368 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
370 def notify(self, message):
371 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
375 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
376 def getOpenFileName(self, title, filter = ""):
377 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
378 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
379 if fileName and directory != os.path.dirname(fileName):
380 self.config.set_key('io_dir', os.path.dirname(fileName), True)
383 def getSaveFileName(self, title, filename, filter = ""):
384 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
385 path = os.path.join( directory, filename )
386 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
387 if fileName and directory != os.path.dirname(fileName):
388 self.config.set_key('io_dir', os.path.dirname(fileName), True)
392 QMainWindow.close(self)
393 run_hook('close_main_window')
395 def connect_slots(self, sender):
396 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
397 self.previous_payto_e=''
399 def timer_actions(self):
400 if self.need_update.is_set():
402 self.need_update.clear()
403 run_hook('timer_actions')
405 def format_amount(self, x, is_diff=False, whitespaces=False):
406 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
408 def read_amount(self, x):
409 if x in['.', '']: return None
410 p = pow(10, self.decimal_point)
411 return int( p * Decimal(x) )
414 assert self.decimal_point in [5,8]
415 return "BTC" if self.decimal_point == 8 else "mBTC"
418 def update_status(self):
419 if self.network is None or not self.network.is_running():
421 icon = QIcon(":icons/status_disconnected.png")
423 elif self.network.is_connected():
424 if not self.wallet.up_to_date:
425 text = _("Synchronizing...")
426 icon = QIcon(":icons/status_waiting.png")
427 elif self.network.server_lag > 1:
428 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
429 icon = QIcon(":icons/status_lagging.png")
431 c, u = self.wallet.get_account_balance(self.current_account)
432 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
433 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
435 # append fiat balance and price from exchange rate plugin
437 run_hook('get_fiat_status_text', c+u, r)
442 self.tray.setToolTip(text)
443 icon = QIcon(":icons/status_connected.png")
445 text = _("Not connected")
446 icon = QIcon(":icons/status_disconnected.png")
448 self.balance_label.setText(text)
449 self.status_button.setIcon( icon )
452 def update_wallet(self):
454 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
455 self.update_history_tab()
456 self.update_receive_tab()
457 self.update_contacts_tab()
458 self.update_completions()
461 def create_history_tab(self):
462 self.history_list = l = MyTreeWidget(self)
464 for i,width in enumerate(self.column_widths['history']):
465 l.setColumnWidth(i, width)
466 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
467 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
468 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
470 l.customContextMenuRequested.connect(self.create_history_menu)
474 def create_history_menu(self, position):
475 self.history_list.selectedIndexes()
476 item = self.history_list.currentItem()
477 be = self.config.get('block_explorer', 'Blockchain.info')
478 if be == 'Blockchain.info':
479 block_explorer = 'https://blockchain.info/tx/'
480 elif be == 'Blockr.io':
481 block_explorer = 'https://blockr.io/tx/info/'
482 elif be == 'Insight.is':
483 block_explorer = 'http://live.insight.is/tx/'
485 tx_hash = str(item.data(0, Qt.UserRole).toString())
486 if not tx_hash: return
488 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
489 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
490 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
491 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
492 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
495 def show_transaction(self, tx):
496 import transaction_dialog
497 d = transaction_dialog.TxDialog(tx, self)
500 def tx_label_clicked(self, item, column):
501 if column==2 and item.isSelected():
503 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
504 self.history_list.editItem( item, column )
505 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
508 def tx_label_changed(self, item, column):
512 tx_hash = str(item.data(0, Qt.UserRole).toString())
513 tx = self.wallet.transactions.get(tx_hash)
514 text = unicode( item.text(2) )
515 self.wallet.set_label(tx_hash, text)
517 item.setForeground(2, QBrush(QColor('black')))
519 text = self.wallet.get_default_label(tx_hash)
520 item.setText(2, text)
521 item.setForeground(2, QBrush(QColor('gray')))
525 def edit_label(self, is_recv):
526 l = self.receive_list if is_recv else self.contacts_list
527 item = l.currentItem()
528 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
529 l.editItem( item, 1 )
530 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
534 def address_label_clicked(self, item, column, l, column_addr, column_label):
535 if column == column_label and item.isSelected():
536 is_editable = item.data(0, 32).toBool()
539 addr = unicode( item.text(column_addr) )
540 label = unicode( item.text(column_label) )
541 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
542 l.editItem( item, column )
543 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
546 def address_label_changed(self, item, column, l, column_addr, column_label):
547 if column == column_label:
548 addr = unicode( item.text(column_addr) )
549 text = unicode( item.text(column_label) )
550 is_editable = item.data(0, 32).toBool()
554 changed = self.wallet.set_label(addr, text)
556 self.update_history_tab()
557 self.update_completions()
559 self.current_item_changed(item)
561 run_hook('item_changed', item, column)
564 def current_item_changed(self, a):
565 run_hook('current_item_changed', a)
569 def update_history_tab(self):
571 self.history_list.clear()
572 for item in self.wallet.get_tx_history(self.current_account):
573 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
574 time_str = _("unknown")
577 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
579 time_str = _("error")
582 time_str = 'unverified'
583 icon = QIcon(":icons/unconfirmed.png")
586 icon = QIcon(":icons/unconfirmed.png")
588 icon = QIcon(":icons/clock%d.png"%conf)
590 icon = QIcon(":icons/confirmed.png")
592 if value is not None:
593 v_str = self.format_amount(value, True, whitespaces=True)
597 balance_str = self.format_amount(balance, whitespaces=True)
600 label, is_default_label = self.wallet.get_label(tx_hash)
602 label = _('Pruned transaction outputs')
603 is_default_label = False
605 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
606 item.setFont(2, QFont(MONOSPACE_FONT))
607 item.setFont(3, QFont(MONOSPACE_FONT))
608 item.setFont(4, QFont(MONOSPACE_FONT))
610 item.setForeground(3, QBrush(QColor("#BC1E1E")))
612 item.setData(0, Qt.UserRole, tx_hash)
613 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
615 item.setForeground(2, QBrush(QColor('grey')))
617 item.setIcon(0, icon)
618 self.history_list.insertTopLevelItem(0,item)
621 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
622 run_hook('history_tab_update')
625 def create_send_tab(self):
630 grid.setColumnMinimumWidth(3,300)
631 grid.setColumnStretch(5,1)
634 self.payto_e = QLineEdit()
635 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)'))
636 grid.addWidget(QLabel(_('Pay to')), 1, 0)
637 grid.addWidget(self.payto_e, 1, 1, 1, 3)
638 grid.addWidget(self.payto_help, 1, 4)
640 completer = QCompleter()
641 completer.setCaseSensitivity(False)
642 self.payto_e.setCompleter(completer)
643 completer.setModel(self.completions)
645 self.message_e = QLineEdit()
646 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.'))
647 grid.addWidget(QLabel(_('Description')), 2, 0)
648 grid.addWidget(self.message_e, 2, 1, 1, 3)
649 grid.addWidget(self.message_help, 2, 4)
651 self.from_label = QLabel(_('From'))
652 grid.addWidget(self.from_label, 3, 0)
653 self.from_list = QTreeWidget(self)
654 self.from_list.setColumnCount(2)
655 self.from_list.setColumnWidth(0, 350)
656 self.from_list.setColumnWidth(1, 50)
657 self.from_list.setHeaderHidden (True)
658 self.from_list.setMaximumHeight(80)
659 grid.addWidget(self.from_list, 3, 1, 1, 3)
660 self.set_pay_from([])
662 self.amount_e = AmountEdit(self.base_unit)
663 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
664 + _('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.') \
665 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
666 grid.addWidget(QLabel(_('Amount')), 4, 0)
667 grid.addWidget(self.amount_e, 4, 1, 1, 2)
668 grid.addWidget(self.amount_help, 4, 3)
670 self.fee_e = AmountEdit(self.base_unit)
671 grid.addWidget(QLabel(_('Fee')), 5, 0)
672 grid.addWidget(self.fee_e, 5, 1, 1, 2)
673 grid.addWidget(HelpButton(
674 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
675 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
676 + _('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)
678 run_hook('exchange_rate_button', grid)
680 self.send_button = EnterButton(_("Send"), self.do_send)
681 grid.addWidget(self.send_button, 6, 1)
683 b = EnterButton(_("Clear"),self.do_clear)
684 grid.addWidget(b, 6, 2)
686 self.payto_sig = QLabel('')
687 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
689 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
690 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
699 def entry_changed( is_fee ):
700 self.funds_error = False
702 if self.amount_e.is_shortcut:
703 self.amount_e.is_shortcut = False
704 sendable = self.get_sendable_balance()
705 # there is only one output because we are completely spending inputs
706 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
707 fee = self.wallet.estimated_fee(inputs, 1)
709 self.amount_e.setText( self.format_amount(amount) )
710 self.fee_e.setText( self.format_amount( fee ) )
713 amount = self.read_amount(str(self.amount_e.text()))
714 fee = self.read_amount(str(self.fee_e.text()))
716 if not is_fee: fee = None
719 # assume that there will be 2 outputs (one for change)
720 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
722 self.fee_e.setText( self.format_amount( fee ) )
725 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
729 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
730 self.funds_error = True
731 text = _( "Not enough funds" )
732 c, u = self.wallet.get_frozen_balance()
733 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
735 self.statusBar().showMessage(text)
736 self.amount_e.setPalette(palette)
737 self.fee_e.setPalette(palette)
739 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
740 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
742 run_hook('create_send_tab', grid)
746 def set_pay_from(self, l):
748 self.from_list.clear()
749 self.from_label.setHidden(len(self.pay_from) == 0)
750 self.from_list.setHidden(len(self.pay_from) == 0)
751 for addr in self.pay_from:
752 c, u = self.wallet.get_addr_balance(addr)
753 balance = self.format_amount(c + u)
754 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
757 def update_completions(self):
759 for addr,label in self.wallet.labels.items():
760 if addr in self.wallet.addressbook:
761 l.append( label + ' <' + addr + '>')
763 run_hook('update_completions', l)
764 self.completions.setStringList(l)
768 return lambda s, *args: s.do_protect(func, args)
772 label = unicode( self.message_e.text() )
774 if self.gui_object.payment_request:
775 outputs = self.gui_object.payment_request.outputs
776 amount = self.gui_object.payment_request.get_amount()
779 r = unicode( self.payto_e.text() )
782 # label or alias, with address in brackets
783 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
784 to_address = m.group(2) if m else r
785 if not is_valid(to_address):
786 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
790 amount = self.read_amount(unicode( self.amount_e.text()))
792 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
795 outputs = [(to_address, amount)]
798 fee = self.read_amount(unicode( self.fee_e.text()))
800 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
803 confirm_amount = self.config.get('confirm_amount', 100000000)
804 if amount >= confirm_amount:
805 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
808 confirm_fee = self.config.get('confirm_fee', 100000)
809 if fee >= confirm_fee:
810 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()}):
813 self.send_tx(outputs, fee, label)
818 def send_tx(self, outputs, fee, label, password):
819 self.send_button.setDisabled(True)
821 # first, create an unsigned tx
822 domain = self.get_payment_sources()
824 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
826 except Exception as e:
827 traceback.print_exc(file=sys.stdout)
828 self.show_message(str(e))
829 self.send_button.setDisabled(False)
832 # call hook to see if plugin needs gui interaction
833 run_hook('send_tx', tx)
839 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
840 self.wallet.sign_transaction(tx, keypairs, password)
841 return tx, fee, label
843 def sign_done(tx, fee, label):
845 self.show_message(tx.error)
846 self.send_button.setDisabled(False)
848 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
849 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
850 self.send_button.setDisabled(False)
853 self.wallet.set_label(tx.hash(), label)
855 if not self.gui_object.payment_request:
856 if not tx.is_complete() or self.config.get('show_before_broadcast'):
857 self.show_transaction(tx)
859 self.send_button.setDisabled(False)
862 self.broadcast_transaction(tx)
864 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
865 self.waiting_dialog.start()
869 def broadcast_transaction(self, tx):
871 def broadcast_thread():
872 if self.gui_object.payment_request:
873 refund_address = self.wallet.addresses()[0]
874 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
875 self.gui_object.payment_request = None
877 status, msg = self.wallet.sendtx(tx)
880 def broadcast_done(status, msg):
882 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
885 QMessageBox.warning(self, _('Error'), msg, _('OK'))
886 self.send_button.setDisabled(False)
888 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
889 self.waiting_dialog.start()
893 def prepare_for_payment_request(self):
894 style = "QWidget { background-color:none;border:none;}"
895 self.tabs.setCurrentIndex(1)
896 for e in [self.payto_e, self.amount_e, self.message_e]:
898 e.setStyleSheet(style)
899 for h in [self.payto_help, self.amount_help, self.message_help]:
901 self.payto_e.setText(_("please wait..."))
904 def payment_request_ok(self):
905 self.payto_e.setText(self.gui_object.payment_request.domain)
906 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
907 self.message_e.setText(self.gui_object.payment_request.memo)
909 def payment_request_error(self):
911 self.show_message(self.gui_object.payment_request.error)
914 def set_send(self, address, amount, label, message):
916 if label and self.wallet.labels.get(address) != label:
917 if self.question('Give label "%s" to address %s ?'%(label,address)):
918 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
919 self.wallet.addressbook.append(address)
920 self.wallet.set_label(address, label)
922 self.tabs.setCurrentIndex(1)
923 label = self.wallet.labels.get(address)
924 m_addr = label + ' <'+ address +'>' if label else address
925 self.payto_e.setText(m_addr)
927 self.message_e.setText(message)
929 self.amount_e.setText(amount)
933 self.payto_sig.setVisible(False)
934 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
936 self.set_frozen(e,False)
938 for h in [self.payto_help, self.amount_help, self.message_help]:
941 self.set_pay_from([])
944 def set_frozen(self,entry,frozen):
946 entry.setReadOnly(True)
947 entry.setFrame(False)
949 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
950 entry.setPalette(palette)
952 entry.setReadOnly(False)
955 palette.setColor(entry.backgroundRole(), QColor('white'))
956 entry.setPalette(palette)
959 def set_addrs_frozen(self,addrs,freeze):
961 if not addr: continue
962 if addr in self.wallet.frozen_addresses and not freeze:
963 self.wallet.unfreeze(addr)
964 elif addr not in self.wallet.frozen_addresses and freeze:
965 self.wallet.freeze(addr)
966 self.update_receive_tab()
970 def create_list_tab(self, headers):
971 "generic tab creation method"
972 l = MyTreeWidget(self)
973 l.setColumnCount( len(headers) )
974 l.setHeaderLabels( headers )
984 vbox.addWidget(buttons)
989 buttons.setLayout(hbox)
994 def create_receive_tab(self):
995 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
996 l.setContextMenuPolicy(Qt.CustomContextMenu)
997 l.customContextMenuRequested.connect(self.create_receive_menu)
998 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
999 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1000 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1001 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1002 self.receive_list = l
1003 self.receive_buttons_hbox = hbox
1010 def save_column_widths(self):
1011 self.column_widths["receive"] = []
1012 for i in range(self.receive_list.columnCount() -1):
1013 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1015 self.column_widths["history"] = []
1016 for i in range(self.history_list.columnCount() - 1):
1017 self.column_widths["history"].append(self.history_list.columnWidth(i))
1019 self.column_widths["contacts"] = []
1020 for i in range(self.contacts_list.columnCount() - 1):
1021 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1023 self.config.set_key("column_widths_2", self.column_widths, True)
1026 def create_contacts_tab(self):
1027 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1028 l.setContextMenuPolicy(Qt.CustomContextMenu)
1029 l.customContextMenuRequested.connect(self.create_contact_menu)
1030 for i,width in enumerate(self.column_widths['contacts']):
1031 l.setColumnWidth(i, width)
1033 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1034 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1035 self.contacts_list = l
1036 self.contacts_buttons_hbox = hbox
1041 def delete_imported_key(self, addr):
1042 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1043 self.wallet.delete_imported_key(addr)
1044 self.update_receive_tab()
1045 self.update_history_tab()
1047 def edit_account_label(self, k):
1048 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1050 label = unicode(text)
1051 self.wallet.set_label(k,label)
1052 self.update_receive_tab()
1054 def account_set_expanded(self, item, k, b):
1056 self.accounts_expanded[k] = b
1058 def create_account_menu(self, position, k, item):
1060 if item.isExpanded():
1061 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1063 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1064 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1065 if self.wallet.seed_version > 4:
1066 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1067 if self.wallet.account_is_pending(k):
1068 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1069 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1071 def delete_pending_account(self, k):
1072 self.wallet.delete_pending_account(k)
1073 self.update_receive_tab()
1075 def create_receive_menu(self, position):
1076 # fixme: this function apparently has a side effect.
1077 # if it is not called the menu pops up several times
1078 #self.receive_list.selectedIndexes()
1080 selected = self.receive_list.selectedItems()
1081 multi_select = len(selected) > 1
1082 addrs = [unicode(item.text(0)) for item in selected]
1083 if not multi_select:
1084 item = self.receive_list.itemAt(position)
1088 if not is_valid(addr):
1089 k = str(item.data(0,32).toString())
1091 self.create_account_menu(position, k, item)
1093 item.setExpanded(not item.isExpanded())
1097 if not multi_select:
1098 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1099 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1100 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1101 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1102 if not self.wallet.is_watching_only():
1103 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1104 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1105 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1106 if self.wallet.is_imported(addr):
1107 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1109 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1110 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1111 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1112 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1114 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1115 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1117 run_hook('receive_menu', menu, addrs)
1118 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1121 def get_sendable_balance(self):
1122 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1125 def get_payment_sources(self):
1127 return self.pay_from
1129 return self.wallet.get_account_addresses(self.current_account)
1132 def send_from_addresses(self, addrs):
1133 self.set_pay_from( addrs )
1134 self.tabs.setCurrentIndex(1)
1137 def payto(self, addr):
1139 label = self.wallet.labels.get(addr)
1140 m_addr = label + ' <' + addr + '>' if label else addr
1141 self.tabs.setCurrentIndex(1)
1142 self.payto_e.setText(m_addr)
1143 self.amount_e.setFocus()
1146 def delete_contact(self, x):
1147 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1148 self.wallet.delete_contact(x)
1149 self.wallet.set_label(x, None)
1150 self.update_history_tab()
1151 self.update_contacts_tab()
1152 self.update_completions()
1155 def create_contact_menu(self, position):
1156 item = self.contacts_list.itemAt(position)
1159 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1161 addr = unicode(item.text(0))
1162 label = unicode(item.text(1))
1163 is_editable = item.data(0,32).toBool()
1164 payto_addr = item.data(0,33).toString()
1165 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1166 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1167 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1169 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1170 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1172 run_hook('create_contact_menu', menu, item)
1173 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1176 def update_receive_item(self, item):
1177 item.setFont(0, QFont(MONOSPACE_FONT))
1178 address = str(item.data(0,0).toString())
1179 label = self.wallet.labels.get(address,'')
1180 item.setData(1,0,label)
1181 item.setData(0,32, True) # is editable
1183 run_hook('update_receive_item', address, item)
1185 if not self.wallet.is_mine(address): return
1187 c, u = self.wallet.get_addr_balance(address)
1188 balance = self.format_amount(c + u)
1189 item.setData(2,0,balance)
1191 if address in self.wallet.frozen_addresses:
1192 item.setBackgroundColor(0, QColor('lightblue'))
1195 def update_receive_tab(self):
1196 l = self.receive_list
1197 # extend the syntax for consistency
1198 l.addChild = l.addTopLevelItem
1199 l.insertChild = l.insertTopLevelItem
1202 for i,width in enumerate(self.column_widths['receive']):
1203 l.setColumnWidth(i, width)
1205 accounts = self.wallet.get_accounts()
1206 if self.current_account is None:
1207 account_items = sorted(accounts.items())
1209 account_items = [(self.current_account, accounts.get(self.current_account))]
1212 for k, account in account_items:
1214 if len(accounts) > 1:
1215 name = self.wallet.get_account_name(k)
1216 c,u = self.wallet.get_account_balance(k)
1217 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1218 l.addTopLevelItem(account_item)
1219 account_item.setExpanded(self.accounts_expanded.get(k, True))
1220 account_item.setData(0, 32, k)
1224 sequences = [0,1] if account.has_change() else [0]
1225 for is_change in sequences:
1226 if len(sequences) > 1:
1227 name = _("Receiving") if not is_change else _("Change")
1228 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1229 account_item.addChild(seq_item)
1231 seq_item.setExpanded(True)
1233 seq_item = account_item
1235 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1241 for address in account.get_addresses(is_change):
1243 num, is_used = self.wallet.is_used(address)
1246 if gap > self.wallet.gap_limit:
1251 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1252 self.update_receive_item(item)
1254 item.setBackgroundColor(1, QColor('red'))
1258 seq_item.insertChild(0,used_item)
1260 used_item.addChild(item)
1262 seq_item.addChild(item)
1264 # we use column 1 because column 0 may be hidden
1265 l.setCurrentItem(l.topLevelItem(0),1)
1268 def update_contacts_tab(self):
1269 l = self.contacts_list
1272 for address in self.wallet.addressbook:
1273 label = self.wallet.labels.get(address,'')
1274 n = self.wallet.get_num_tx(address)
1275 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1276 item.setFont(0, QFont(MONOSPACE_FONT))
1277 # 32 = label can be edited (bool)
1278 item.setData(0,32, True)
1280 item.setData(0,33, address)
1281 l.addTopLevelItem(item)
1283 run_hook('update_contacts_tab', l)
1284 l.setCurrentItem(l.topLevelItem(0))
1288 def create_console_tab(self):
1289 from console import Console
1290 self.console = console = Console()
1294 def update_console(self):
1295 console = self.console
1296 console.history = self.config.get("console-history",[])
1297 console.history_index = len(console.history)
1299 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1300 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1302 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1304 def mkfunc(f, method):
1305 return lambda *args: apply( f, (method, args, self.password_dialog ))
1307 if m[0]=='_' or m in ['network','wallet']: continue
1308 methods[m] = mkfunc(c._run, m)
1310 console.updateNamespace(methods)
1313 def change_account(self,s):
1314 if s == _("All accounts"):
1315 self.current_account = None
1317 accounts = self.wallet.get_account_names()
1318 for k, v in accounts.items():
1320 self.current_account = k
1321 self.update_history_tab()
1322 self.update_status()
1323 self.update_receive_tab()
1325 def create_status_bar(self):
1328 sb.setFixedHeight(35)
1329 qtVersion = qVersion()
1331 self.balance_label = QLabel("")
1332 sb.addWidget(self.balance_label)
1334 from version_getter import UpdateLabel
1335 self.updatelabel = UpdateLabel(self.config, sb)
1337 self.account_selector = QComboBox()
1338 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1339 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1340 sb.addPermanentWidget(self.account_selector)
1342 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1343 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1345 self.lock_icon = QIcon()
1346 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1347 sb.addPermanentWidget( self.password_button )
1349 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1350 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1351 sb.addPermanentWidget( self.seed_button )
1352 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1353 sb.addPermanentWidget( self.status_button )
1355 run_hook('create_status_bar', (sb,))
1357 self.setStatusBar(sb)
1360 def update_lock_icon(self):
1361 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1362 self.password_button.setIcon( icon )
1365 def update_buttons_on_seed(self):
1366 if self.wallet.has_seed():
1367 self.seed_button.show()
1369 self.seed_button.hide()
1371 if not self.wallet.is_watching_only():
1372 self.password_button.show()
1373 self.send_button.setText(_("Send"))
1375 self.password_button.hide()
1376 self.send_button.setText(_("Create unsigned transaction"))
1379 def change_password_dialog(self):
1380 from password_dialog import PasswordDialog
1381 d = PasswordDialog(self.wallet, self)
1383 self.update_lock_icon()
1386 def new_contact_dialog(self):
1389 d.setWindowTitle(_("New Contact"))
1390 vbox = QVBoxLayout(d)
1391 vbox.addWidget(QLabel(_('New Contact')+':'))
1393 grid = QGridLayout()
1396 grid.addWidget(QLabel(_("Address")), 1, 0)
1397 grid.addWidget(line1, 1, 1)
1398 grid.addWidget(QLabel(_("Name")), 2, 0)
1399 grid.addWidget(line2, 2, 1)
1401 vbox.addLayout(grid)
1402 vbox.addLayout(ok_cancel_buttons(d))
1407 address = str(line1.text())
1408 label = unicode(line2.text())
1410 if not is_valid(address):
1411 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1414 self.wallet.add_contact(address)
1416 self.wallet.set_label(address, label)
1418 self.update_contacts_tab()
1419 self.update_history_tab()
1420 self.update_completions()
1421 self.tabs.setCurrentIndex(3)
1425 def new_account_dialog(self, password):
1427 dialog = QDialog(self)
1429 dialog.setWindowTitle(_("New Account"))
1431 vbox = QVBoxLayout()
1432 vbox.addWidget(QLabel(_('Account name')+':'))
1435 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1436 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1441 vbox.addLayout(ok_cancel_buttons(dialog))
1442 dialog.setLayout(vbox)
1446 name = str(e.text())
1449 self.wallet.create_pending_account(name, password)
1450 self.update_receive_tab()
1451 self.tabs.setCurrentIndex(2)
1456 def show_master_public_keys(self):
1458 dialog = QDialog(self)
1460 dialog.setWindowTitle(_("Master Public Keys"))
1462 main_layout = QGridLayout()
1463 mpk_dict = self.wallet.get_master_public_keys()
1465 for key, value in mpk_dict.items():
1466 main_layout.addWidget(QLabel(key), i, 0)
1467 mpk_text = QTextEdit()
1468 mpk_text.setReadOnly(True)
1469 mpk_text.setMaximumHeight(170)
1470 mpk_text.setText(value)
1471 main_layout.addWidget(mpk_text, i + 1, 0)
1474 vbox = QVBoxLayout()
1475 vbox.addLayout(main_layout)
1476 vbox.addLayout(close_button(dialog))
1478 dialog.setLayout(vbox)
1483 def show_seed_dialog(self, password):
1484 if not self.wallet.has_seed():
1485 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1489 mnemonic = self.wallet.get_mnemonic(password)
1491 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1493 from seed_dialog import SeedDialog
1494 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1499 def show_qrcode(self, data, title = _("QR code")):
1503 d.setWindowTitle(title)
1504 d.setMinimumSize(270, 300)
1505 vbox = QVBoxLayout()
1506 qrw = QRCodeWidget(data)
1507 vbox.addWidget(qrw, 1)
1508 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1509 hbox = QHBoxLayout()
1512 filename = os.path.join(self.config.path, "qrcode.bmp")
1515 bmp.save_qrcode(qrw.qr, filename)
1516 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1518 def copy_to_clipboard():
1519 bmp.save_qrcode(qrw.qr, filename)
1520 self.app.clipboard().setImage(QImage(filename))
1521 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1523 b = QPushButton(_("Copy"))
1525 b.clicked.connect(copy_to_clipboard)
1527 b = QPushButton(_("Save"))
1529 b.clicked.connect(print_qr)
1531 b = QPushButton(_("Close"))
1533 b.clicked.connect(d.accept)
1536 vbox.addLayout(hbox)
1541 def do_protect(self, func, args):
1542 if self.wallet.use_encryption:
1543 password = self.password_dialog()
1549 if args != (False,):
1550 args = (self,) + args + (password,)
1552 args = (self,password)
1556 def show_public_keys(self, address):
1557 if not address: return
1559 pubkey_list = self.wallet.get_public_keys(address)
1560 except Exception as e:
1561 traceback.print_exc(file=sys.stdout)
1562 self.show_message(str(e))
1566 d.setMinimumSize(600, 200)
1568 vbox = QVBoxLayout()
1569 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1570 vbox.addWidget( QLabel(_("Public key") + ':'))
1572 keys.setReadOnly(True)
1573 keys.setText('\n'.join(pubkey_list))
1574 vbox.addWidget(keys)
1575 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1576 vbox.addLayout(close_button(d))
1581 def show_private_key(self, address, password):
1582 if not address: return
1584 pk_list = self.wallet.get_private_key(address, password)
1585 except Exception as e:
1586 traceback.print_exc(file=sys.stdout)
1587 self.show_message(str(e))
1591 d.setMinimumSize(600, 200)
1593 vbox = QVBoxLayout()
1594 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1595 vbox.addWidget( QLabel(_("Private key") + ':'))
1597 keys.setReadOnly(True)
1598 keys.setText('\n'.join(pk_list))
1599 vbox.addWidget(keys)
1600 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1601 vbox.addLayout(close_button(d))
1607 def do_sign(self, address, message, signature, password):
1608 message = unicode(message.toPlainText())
1609 message = message.encode('utf-8')
1611 sig = self.wallet.sign_message(str(address.text()), message, password)
1612 signature.setText(sig)
1613 except Exception as e:
1614 self.show_message(str(e))
1616 def do_verify(self, address, message, signature):
1617 message = unicode(message.toPlainText())
1618 message = message.encode('utf-8')
1619 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1620 self.show_message(_("Signature verified"))
1622 self.show_message(_("Error: wrong signature"))
1625 def sign_verify_message(self, address=''):
1628 d.setWindowTitle(_('Sign/verify Message'))
1629 d.setMinimumSize(410, 290)
1631 layout = QGridLayout(d)
1633 message_e = QTextEdit()
1634 layout.addWidget(QLabel(_('Message')), 1, 0)
1635 layout.addWidget(message_e, 1, 1)
1636 layout.setRowStretch(2,3)
1638 address_e = QLineEdit()
1639 address_e.setText(address)
1640 layout.addWidget(QLabel(_('Address')), 2, 0)
1641 layout.addWidget(address_e, 2, 1)
1643 signature_e = QTextEdit()
1644 layout.addWidget(QLabel(_('Signature')), 3, 0)
1645 layout.addWidget(signature_e, 3, 1)
1646 layout.setRowStretch(3,1)
1648 hbox = QHBoxLayout()
1650 b = QPushButton(_("Sign"))
1651 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1654 b = QPushButton(_("Verify"))
1655 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1658 b = QPushButton(_("Close"))
1659 b.clicked.connect(d.accept)
1661 layout.addLayout(hbox, 4, 1)
1666 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1668 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1669 message_e.setText(decrypted)
1670 except Exception as e:
1671 self.show_message(str(e))
1674 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1675 message = unicode(message_e.toPlainText())
1676 message = message.encode('utf-8')
1678 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1679 encrypted_e.setText(encrypted)
1680 except Exception as e:
1681 self.show_message(str(e))
1685 def encrypt_message(self, address = ''):
1688 d.setWindowTitle(_('Encrypt/decrypt Message'))
1689 d.setMinimumSize(610, 490)
1691 layout = QGridLayout(d)
1693 message_e = QTextEdit()
1694 layout.addWidget(QLabel(_('Message')), 1, 0)
1695 layout.addWidget(message_e, 1, 1)
1696 layout.setRowStretch(2,3)
1698 pubkey_e = QLineEdit()
1700 pubkey = self.wallet.getpubkeys(address)[0]
1701 pubkey_e.setText(pubkey)
1702 layout.addWidget(QLabel(_('Public key')), 2, 0)
1703 layout.addWidget(pubkey_e, 2, 1)
1705 encrypted_e = QTextEdit()
1706 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1707 layout.addWidget(encrypted_e, 3, 1)
1708 layout.setRowStretch(3,1)
1710 hbox = QHBoxLayout()
1711 b = QPushButton(_("Encrypt"))
1712 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1715 b = QPushButton(_("Decrypt"))
1716 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1719 b = QPushButton(_("Close"))
1720 b.clicked.connect(d.accept)
1723 layout.addLayout(hbox, 4, 1)
1727 def question(self, msg):
1728 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1730 def show_message(self, msg):
1731 QMessageBox.information(self, _('Message'), msg, _('OK'))
1733 def password_dialog(self ):
1736 d.setWindowTitle(_("Enter Password"))
1741 vbox = QVBoxLayout()
1742 msg = _('Please enter your password')
1743 vbox.addWidget(QLabel(msg))
1745 grid = QGridLayout()
1747 grid.addWidget(QLabel(_('Password')), 1, 0)
1748 grid.addWidget(pw, 1, 1)
1749 vbox.addLayout(grid)
1751 vbox.addLayout(ok_cancel_buttons(d))
1754 run_hook('password_dialog', pw, grid, 1)
1755 if not d.exec_(): return
1756 return unicode(pw.text())
1765 def tx_from_text(self, txt):
1766 "json or raw hexadecimal"
1769 tx = Transaction(txt)
1775 tx_dict = json.loads(str(txt))
1776 assert "hex" in tx_dict.keys()
1777 tx = Transaction(tx_dict["hex"])
1778 if tx_dict.has_key("input_info"):
1779 input_info = json.loads(tx_dict['input_info'])
1780 tx.add_input_info(input_info)
1783 traceback.print_exc(file=sys.stdout)
1786 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1790 def read_tx_from_file(self):
1791 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1795 with open(fileName, "r") as f:
1796 file_content = f.read()
1797 except (ValueError, IOError, os.error), reason:
1798 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1800 return self.tx_from_text(file_content)
1804 def sign_raw_transaction(self, tx, input_info, password):
1805 self.wallet.signrawtransaction(tx, input_info, [], password)
1807 def do_process_from_text(self):
1808 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1811 tx = self.tx_from_text(text)
1813 self.show_transaction(tx)
1815 def do_process_from_file(self):
1816 tx = self.read_tx_from_file()
1818 self.show_transaction(tx)
1820 def do_process_from_txid(self):
1821 from electrum import transaction
1822 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1824 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1826 tx = transaction.Transaction(r)
1828 self.show_transaction(tx)
1830 self.show_message("unknown transaction")
1832 def do_process_from_csvReader(self, csvReader):
1837 for position, row in enumerate(csvReader):
1839 if not is_valid(address):
1840 errors.append((position, address))
1842 amount = Decimal(row[1])
1843 amount = int(100000000*amount)
1844 outputs.append((address, amount))
1845 except (ValueError, IOError, os.error), reason:
1846 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1850 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1851 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1855 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1856 except Exception as e:
1857 self.show_message(str(e))
1860 self.show_transaction(tx)
1862 def do_process_from_csv_file(self):
1863 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1867 with open(fileName, "r") as f:
1868 csvReader = csv.reader(f)
1869 self.do_process_from_csvReader(csvReader)
1870 except (ValueError, IOError, os.error), reason:
1871 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1874 def do_process_from_csv_text(self):
1875 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1876 + _("Format: address, amount. One output per line"), _("Load CSV"))
1879 f = StringIO.StringIO(text)
1880 csvReader = csv.reader(f)
1881 self.do_process_from_csvReader(csvReader)
1886 def export_privkeys_dialog(self, password):
1887 if self.wallet.is_watching_only():
1888 self.show_message(_("This is a watching-only wallet"))
1892 d.setWindowTitle(_('Private keys'))
1893 d.setMinimumSize(850, 300)
1894 vbox = QVBoxLayout(d)
1896 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1897 _("Exposing a single private key can compromise your entire wallet!"),
1898 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1899 vbox.addWidget(QLabel(msg))
1905 defaultname = 'electrum-private-keys.csv'
1906 select_msg = _('Select file to export your private keys to')
1907 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1908 vbox.addLayout(hbox)
1910 h, b = ok_cancel_buttons2(d, _('Export'))
1915 addresses = self.wallet.addresses(True)
1917 def privkeys_thread():
1918 for addr in addresses:
1922 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1923 d.emit(SIGNAL('computing_privkeys'))
1924 d.emit(SIGNAL('show_privkeys'))
1926 def show_privkeys():
1927 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1931 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1932 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1933 threading.Thread(target=privkeys_thread).start()
1939 filename = filename_e.text()
1944 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1945 except (IOError, os.error), reason:
1946 export_error_label = _("Electrum was unable to produce a private key-export.")
1947 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1949 except Exception as e:
1950 self.show_message(str(e))
1953 self.show_message(_("Private keys exported."))
1956 def do_export_privkeys(self, fileName, pklist, is_csv):
1957 with open(fileName, "w+") as f:
1959 transaction = csv.writer(f)
1960 transaction.writerow(["address", "private_key"])
1961 for addr, pk in pklist.items():
1962 transaction.writerow(["%34s"%addr,pk])
1965 f.write(json.dumps(pklist, indent = 4))
1968 def do_import_labels(self):
1969 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1970 if not labelsFile: return
1972 f = open(labelsFile, 'r')
1975 for key, value in json.loads(data).items():
1976 self.wallet.set_label(key, value)
1977 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1978 except (IOError, os.error), reason:
1979 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1982 def do_export_labels(self):
1983 labels = self.wallet.labels
1985 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1987 with open(fileName, 'w+') as f:
1988 json.dump(labels, f)
1989 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1990 except (IOError, os.error), reason:
1991 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1994 def export_history_dialog(self):
1997 d.setWindowTitle(_('Export History'))
1998 d.setMinimumSize(400, 200)
1999 vbox = QVBoxLayout(d)
2001 defaultname = os.path.expanduser('~/electrum-history.csv')
2002 select_msg = _('Select file to export your wallet transactions to')
2004 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2005 vbox.addLayout(hbox)
2009 h, b = ok_cancel_buttons2(d, _('Export'))
2014 filename = filename_e.text()
2019 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2020 except (IOError, os.error), reason:
2021 export_error_label = _("Electrum was unable to produce a transaction export.")
2022 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2025 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2028 def do_export_history(self, wallet, fileName, is_csv):
2029 history = wallet.get_tx_history()
2031 for item in history:
2032 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2034 if timestamp is not None:
2036 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2037 except [RuntimeError, TypeError, NameError] as reason:
2038 time_string = "unknown"
2041 time_string = "unknown"
2043 time_string = "pending"
2045 if value is not None:
2046 value_string = format_satoshis(value, True)
2051 fee_string = format_satoshis(fee, True)
2056 label, is_default_label = wallet.get_label(tx_hash)
2057 label = label.encode('utf-8')
2061 balance_string = format_satoshis(balance, False)
2063 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2065 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2067 with open(fileName, "w+") as f:
2069 transaction = csv.writer(f)
2070 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2072 transaction.writerow(line)
2075 f.write(json.dumps(lines, indent = 4))
2078 def sweep_key_dialog(self):
2080 d.setWindowTitle(_('Sweep private keys'))
2081 d.setMinimumSize(600, 300)
2083 vbox = QVBoxLayout(d)
2084 vbox.addWidget(QLabel(_("Enter private keys")))
2086 keys_e = QTextEdit()
2087 keys_e.setTabChangesFocus(True)
2088 vbox.addWidget(keys_e)
2090 h, address_e = address_field(self.wallet.addresses())
2094 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2095 vbox.addLayout(hbox)
2096 button.setEnabled(False)
2099 addr = str(address_e.text())
2100 if bitcoin.is_address(addr):
2104 pk = str(keys_e.toPlainText()).strip()
2105 if Wallet.is_private_key(pk):
2108 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2109 keys_e.textChanged.connect(f)
2110 address_e.textChanged.connect(f)
2114 fee = self.wallet.fee
2115 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2116 self.show_transaction(tx)
2120 def do_import_privkey(self, password):
2121 if not self.wallet.imported_keys:
2122 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2123 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2124 + _('Are you sure you understand what you are doing?'), 3, 4)
2127 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2130 text = str(text).split()
2135 addr = self.wallet.import_key(key, password)
2136 except Exception as e:
2142 addrlist.append(addr)
2144 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2146 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2147 self.update_receive_tab()
2148 self.update_history_tab()
2151 def settings_dialog(self):
2153 d.setWindowTitle(_('Electrum Settings'))
2155 vbox = QVBoxLayout()
2156 grid = QGridLayout()
2157 grid.setColumnStretch(0,1)
2159 nz_label = QLabel(_('Display zeros') + ':')
2160 grid.addWidget(nz_label, 0, 0)
2161 nz_e = AmountEdit(None,True)
2162 nz_e.setText("%d"% self.num_zeros)
2163 grid.addWidget(nz_e, 0, 1)
2164 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2165 grid.addWidget(HelpButton(msg), 0, 2)
2166 if not self.config.is_modifiable('num_zeros'):
2167 for w in [nz_e, nz_label]: w.setEnabled(False)
2169 lang_label=QLabel(_('Language') + ':')
2170 grid.addWidget(lang_label, 1, 0)
2171 lang_combo = QComboBox()
2172 from electrum.i18n import languages
2173 lang_combo.addItems(languages.values())
2175 index = languages.keys().index(self.config.get("language",''))
2178 lang_combo.setCurrentIndex(index)
2179 grid.addWidget(lang_combo, 1, 1)
2180 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2181 if not self.config.is_modifiable('language'):
2182 for w in [lang_combo, lang_label]: w.setEnabled(False)
2185 fee_label = QLabel(_('Transaction fee') + ':')
2186 grid.addWidget(fee_label, 2, 0)
2187 fee_e = AmountEdit(self.base_unit)
2188 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2189 grid.addWidget(fee_e, 2, 1)
2190 msg = _('Fee per kilobyte of transaction.') + ' ' \
2191 + _('Recommended value') + ': ' + self.format_amount(20000)
2192 grid.addWidget(HelpButton(msg), 2, 2)
2193 if not self.config.is_modifiable('fee_per_kb'):
2194 for w in [fee_e, fee_label]: w.setEnabled(False)
2196 units = ['BTC', 'mBTC']
2197 unit_label = QLabel(_('Base unit') + ':')
2198 grid.addWidget(unit_label, 3, 0)
2199 unit_combo = QComboBox()
2200 unit_combo.addItems(units)
2201 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2202 grid.addWidget(unit_combo, 3, 1)
2203 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2204 + '\n1BTC=1000mBTC.\n' \
2205 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2207 usechange_cb = QCheckBox(_('Use change addresses'))
2208 usechange_cb.setChecked(self.wallet.use_change)
2209 grid.addWidget(usechange_cb, 4, 0)
2210 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2211 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2213 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2214 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2215 grid.addWidget(block_ex_label, 5, 0)
2216 block_ex_combo = QComboBox()
2217 block_ex_combo.addItems(block_explorers)
2218 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2219 grid.addWidget(block_ex_combo, 5, 1)
2220 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2222 show_tx = self.config.get('show_before_broadcast', False)
2223 showtx_cb = QCheckBox(_('Show before broadcast'))
2224 showtx_cb.setChecked(show_tx)
2225 grid.addWidget(showtx_cb, 6, 0)
2226 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2228 vbox.addLayout(grid)
2230 vbox.addLayout(ok_cancel_buttons(d))
2234 if not d.exec_(): return
2236 fee = unicode(fee_e.text())
2238 fee = self.read_amount(fee)
2240 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2243 self.wallet.set_fee(fee)
2245 nz = unicode(nz_e.text())
2250 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2253 if self.num_zeros != nz:
2255 self.config.set_key('num_zeros', nz, True)
2256 self.update_history_tab()
2257 self.update_receive_tab()
2259 usechange_result = usechange_cb.isChecked()
2260 if self.wallet.use_change != usechange_result:
2261 self.wallet.use_change = usechange_result
2262 self.wallet.storage.put('use_change', self.wallet.use_change)
2264 if showtx_cb.isChecked() != show_tx:
2265 self.config.set_key('show_before_broadcast', not show_tx)
2267 unit_result = units[unit_combo.currentIndex()]
2268 if self.base_unit() != unit_result:
2269 self.decimal_point = 8 if unit_result == 'BTC' else 5
2270 self.config.set_key('decimal_point', self.decimal_point, True)
2271 self.update_history_tab()
2272 self.update_status()
2274 need_restart = False
2276 lang_request = languages.keys()[lang_combo.currentIndex()]
2277 if lang_request != self.config.get('language'):
2278 self.config.set_key("language", lang_request, True)
2281 be_result = block_explorers[block_ex_combo.currentIndex()]
2282 self.config.set_key('block_explorer', be_result, True)
2284 run_hook('close_settings_dialog')
2287 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2290 def run_network_dialog(self):
2291 if not self.network:
2293 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2295 def closeEvent(self, event):
2297 self.config.set_key("is_maximized", self.isMaximized())
2298 if not self.isMaximized():
2300 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2301 self.save_column_widths()
2302 self.config.set_key("console-history", self.console.history[-50:], True)
2303 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2307 def plugins_dialog(self):
2308 from electrum.plugins import plugins
2311 d.setWindowTitle(_('Electrum Plugins'))
2314 vbox = QVBoxLayout(d)
2317 scroll = QScrollArea()
2318 scroll.setEnabled(True)
2319 scroll.setWidgetResizable(True)
2320 scroll.setMinimumSize(400,250)
2321 vbox.addWidget(scroll)
2325 w.setMinimumHeight(len(plugins)*35)
2327 grid = QGridLayout()
2328 grid.setColumnStretch(0,1)
2331 def do_toggle(cb, p, w):
2334 if w: w.setEnabled(r)
2336 def mk_toggle(cb, p, w):
2337 return lambda: do_toggle(cb,p,w)
2339 for i, p in enumerate(plugins):
2341 cb = QCheckBox(p.fullname())
2342 cb.setDisabled(not p.is_available())
2343 cb.setChecked(p.is_enabled())
2344 grid.addWidget(cb, i, 0)
2345 if p.requires_settings():
2346 w = p.settings_widget(self)
2347 w.setEnabled( p.is_enabled() )
2348 grid.addWidget(w, i, 1)
2351 cb.clicked.connect(mk_toggle(cb,p,w))
2352 grid.addWidget(HelpButton(p.description()), i, 2)
2354 print_msg(_("Error: cannot display plugin"), p)
2355 traceback.print_exc(file=sys.stdout)
2356 grid.setRowStretch(i+1,1)
2358 vbox.addLayout(close_button(d))
2363 def show_account_details(self, k):
2364 account = self.wallet.accounts[k]
2367 d.setWindowTitle(_('Account Details'))
2370 vbox = QVBoxLayout(d)
2371 name = self.wallet.get_account_name(k)
2372 label = QLabel('Name: ' + name)
2373 vbox.addWidget(label)
2375 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2377 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2379 vbox.addWidget(QLabel(_('Master Public Key:')))
2382 text.setReadOnly(True)
2383 text.setMaximumHeight(170)
2384 vbox.addWidget(text)
2386 mpk_text = '\n'.join( account.get_master_pubkeys() )
2387 text.setText(mpk_text)
2389 vbox.addLayout(close_button(d))