3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.gui_object = gui_object
111 self.tray = gui_object.tray
112 self.go_lite = gui_object.go_lite
115 self.create_status_bar()
116 self.need_update = threading.Event()
118 self.decimal_point = config.get('decimal_point', 5)
119 self.num_zeros = int(config.get('num_zeros',0))
121 set_language(config.get('language'))
123 self.funds_error = False
124 self.completions = QStringListModel()
126 self.tabs = tabs = QTabWidget(self)
127 self.column_widths = self.config.get("column_widths_2", default_column_widths )
128 tabs.addTab(self.create_history_tab(), _('History') )
129 tabs.addTab(self.create_send_tab(), _('Send') )
130 tabs.addTab(self.create_receive_tab(), _('Receive') )
131 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
132 tabs.addTab(self.create_console_tab(), _('Console') )
133 tabs.setMinimumSize(600, 400)
134 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
135 self.setCentralWidget(tabs)
137 g = self.config.get("winpos-qt",[100, 100, 840, 400])
138 self.setGeometry(g[0], g[1], g[2], g[3])
139 if self.config.get("is_maximized"):
142 self.setWindowIcon(QIcon(":icons/electrum.png"))
145 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
146 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
147 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
148 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
149 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
151 for i in range(tabs.count()):
152 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
154 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
155 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
156 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
157 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
158 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
160 self.history_list.setFocus(True)
164 self.network.register_callback('updated', lambda: self.need_update.set())
165 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
166 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
167 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
168 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
170 # set initial message
171 self.console.showMessage(self.network.banner)
176 def update_account_selector(self):
178 accounts = self.wallet.get_account_names()
179 self.account_selector.clear()
180 if len(accounts) > 1:
181 self.account_selector.addItems([_("All accounts")] + accounts.values())
182 self.account_selector.setCurrentIndex(0)
183 self.account_selector.show()
185 self.account_selector.hide()
188 def load_wallet(self, wallet):
192 self.update_wallet_format()
194 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
195 self.current_account = self.wallet.storage.get("current_account", None)
196 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
197 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
198 self.setWindowTitle( title )
200 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
201 self.notify_transactions()
202 self.update_account_selector()
204 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
205 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
206 self.password_menu.setEnabled(not self.wallet.is_watching_only())
207 self.seed_menu.setEnabled(self.wallet.has_seed())
208 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
209 self.import_menu.setEnabled(self.wallet.can_import())
211 self.update_lock_icon()
212 self.update_buttons_on_seed()
213 self.update_console()
215 run_hook('load_wallet', wallet)
218 def update_wallet_format(self):
219 # convert old-format imported keys
220 if self.wallet.imported_keys:
221 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
223 self.wallet.convert_imported_keys(password)
225 self.show_message("error")
228 def open_wallet(self):
229 wallet_folder = self.wallet.storage.path
230 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
234 storage = WalletStorage({'wallet_path': filename})
235 if not storage.file_exists:
236 self.show_message("file not found "+ filename)
239 self.wallet.stop_threads()
242 wallet = Wallet(storage)
243 wallet.start_threads(self.network)
245 self.load_wallet(wallet)
249 def backup_wallet(self):
251 path = self.wallet.storage.path
252 wallet_folder = os.path.dirname(path)
253 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
257 new_path = os.path.join(wallet_folder, filename)
260 shutil.copy2(path, new_path)
261 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
262 except (IOError, os.error), reason:
263 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
266 def new_wallet(self):
269 wallet_folder = os.path.dirname(self.wallet.storage.path)
270 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
273 filename = os.path.join(wallet_folder, filename)
275 storage = WalletStorage({'wallet_path': filename})
276 if storage.file_exists:
277 QMessageBox.critical(None, "Error", _("File exists"))
280 wizard = installwizard.InstallWizard(self.config, self.network, storage)
281 wallet = wizard.run('new')
283 self.load_wallet(wallet)
287 def init_menubar(self):
290 file_menu = menubar.addMenu(_("&File"))
291 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
292 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
293 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
294 file_menu.addAction(_("&Quit"), self.close)
296 wallet_menu = menubar.addMenu(_("&Wallet"))
297 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
298 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
300 wallet_menu.addSeparator()
302 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
303 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
304 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
306 wallet_menu.addSeparator()
307 labels_menu = wallet_menu.addMenu(_("&Labels"))
308 labels_menu.addAction(_("&Import"), self.do_import_labels)
309 labels_menu.addAction(_("&Export"), self.do_export_labels)
311 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
312 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
313 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
314 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
315 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
317 tools_menu = menubar.addMenu(_("&Tools"))
319 # Settings / Preferences are all reserved keywords in OSX using this as work around
320 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
321 tools_menu.addAction(_("&Network"), self.run_network_dialog)
322 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
323 tools_menu.addSeparator()
324 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
325 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
326 tools_menu.addSeparator()
328 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
329 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
330 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
332 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
333 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
334 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
335 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
337 help_menu = menubar.addMenu(_("&Help"))
338 help_menu.addAction(_("&About"), self.show_about)
339 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
340 help_menu.addSeparator()
341 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
342 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
344 self.setMenuBar(menubar)
346 def show_about(self):
347 QMessageBox.about(self, "Electrum",
348 _("Version")+" %s" % (self.wallet.electrum_version) + "\n\n" + _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
350 def show_report_bug(self):
351 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
352 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
355 def notify_transactions(self):
356 if not self.network or not self.network.is_connected():
359 print_error("Notifying GUI")
360 if len(self.network.pending_transactions_for_notifications) > 0:
361 # Combine the transactions if there are more then three
362 tx_amount = len(self.network.pending_transactions_for_notifications)
365 for tx in self.network.pending_transactions_for_notifications:
366 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
370 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
371 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
373 self.network.pending_transactions_for_notifications = []
375 for tx in self.network.pending_transactions_for_notifications:
377 self.network.pending_transactions_for_notifications.remove(tx)
378 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
380 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
382 def notify(self, message):
383 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
387 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
388 def getOpenFileName(self, title, filter = ""):
389 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
390 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
391 if fileName and directory != os.path.dirname(fileName):
392 self.config.set_key('io_dir', os.path.dirname(fileName), True)
395 def getSaveFileName(self, title, filename, filter = ""):
396 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
397 path = os.path.join( directory, filename )
398 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
399 if fileName and directory != os.path.dirname(fileName):
400 self.config.set_key('io_dir', os.path.dirname(fileName), True)
404 QMainWindow.close(self)
405 run_hook('close_main_window')
407 def connect_slots(self, sender):
408 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
409 self.previous_payto_e=''
411 def timer_actions(self):
412 if self.need_update.is_set():
414 self.need_update.clear()
415 run_hook('timer_actions')
417 def format_amount(self, x, is_diff=False, whitespaces=False):
418 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
421 def get_decimal_point(self):
422 return self.decimal_point
426 assert self.decimal_point in [5,8]
427 return "BTC" if self.decimal_point == 8 else "mBTC"
430 def update_status(self):
431 if self.network is None or not self.network.is_running():
433 icon = QIcon(":icons/status_disconnected.png")
435 elif self.network.is_connected():
436 if not self.wallet.up_to_date:
437 text = _("Synchronizing...")
438 icon = QIcon(":icons/status_waiting.png")
439 elif self.network.server_lag > 1:
440 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
441 icon = QIcon(":icons/status_lagging.png")
443 c, u = self.wallet.get_account_balance(self.current_account)
444 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
445 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
447 # append fiat balance and price from exchange rate plugin
449 run_hook('get_fiat_status_text', c+u, r)
454 self.tray.setToolTip(text)
455 icon = QIcon(":icons/status_connected.png")
457 text = _("Not connected")
458 icon = QIcon(":icons/status_disconnected.png")
460 self.balance_label.setText(text)
461 self.status_button.setIcon( icon )
464 def update_wallet(self):
466 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
467 self.update_history_tab()
468 self.update_receive_tab()
469 self.update_contacts_tab()
470 self.update_completions()
473 def create_history_tab(self):
474 self.history_list = l = MyTreeWidget(self)
476 for i,width in enumerate(self.column_widths['history']):
477 l.setColumnWidth(i, width)
478 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
479 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
480 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
482 l.customContextMenuRequested.connect(self.create_history_menu)
486 def create_history_menu(self, position):
487 self.history_list.selectedIndexes()
488 item = self.history_list.currentItem()
489 be = self.config.get('block_explorer', 'Blockchain.info')
490 if be == 'Blockchain.info':
491 block_explorer = 'https://blockchain.info/tx/'
492 elif be == 'Blockr.io':
493 block_explorer = 'https://blockr.io/tx/info/'
494 elif be == 'Insight.is':
495 block_explorer = 'http://live.insight.is/tx/'
497 tx_hash = str(item.data(0, Qt.UserRole).toString())
498 if not tx_hash: return
500 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
501 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
502 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
503 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
504 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
507 def show_transaction(self, tx):
508 import transaction_dialog
509 d = transaction_dialog.TxDialog(tx, self)
512 def tx_label_clicked(self, item, column):
513 if column==2 and item.isSelected():
515 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
516 self.history_list.editItem( item, column )
517 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
520 def tx_label_changed(self, item, column):
524 tx_hash = str(item.data(0, Qt.UserRole).toString())
525 tx = self.wallet.transactions.get(tx_hash)
526 text = unicode( item.text(2) )
527 self.wallet.set_label(tx_hash, text)
529 item.setForeground(2, QBrush(QColor('black')))
531 text = self.wallet.get_default_label(tx_hash)
532 item.setText(2, text)
533 item.setForeground(2, QBrush(QColor('gray')))
537 def edit_label(self, is_recv):
538 l = self.receive_list if is_recv else self.contacts_list
539 item = l.currentItem()
540 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
541 l.editItem( item, 1 )
542 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
546 def address_label_clicked(self, item, column, l, column_addr, column_label):
547 if column == column_label and item.isSelected():
548 is_editable = item.data(0, 32).toBool()
551 addr = unicode( item.text(column_addr) )
552 label = unicode( item.text(column_label) )
553 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
554 l.editItem( item, column )
555 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 def address_label_changed(self, item, column, l, column_addr, column_label):
559 if column == column_label:
560 addr = unicode( item.text(column_addr) )
561 text = unicode( item.text(column_label) )
562 is_editable = item.data(0, 32).toBool()
566 changed = self.wallet.set_label(addr, text)
568 self.update_history_tab()
569 self.update_completions()
571 self.current_item_changed(item)
573 run_hook('item_changed', item, column)
576 def current_item_changed(self, a):
577 run_hook('current_item_changed', a)
581 def update_history_tab(self):
583 self.history_list.clear()
584 for item in self.wallet.get_tx_history(self.current_account):
585 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
586 time_str = _("unknown")
589 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
591 time_str = _("error")
594 time_str = 'unverified'
595 icon = QIcon(":icons/unconfirmed.png")
598 icon = QIcon(":icons/unconfirmed.png")
600 icon = QIcon(":icons/clock%d.png"%conf)
602 icon = QIcon(":icons/confirmed.png")
604 if value is not None:
605 v_str = self.format_amount(value, True, whitespaces=True)
609 balance_str = self.format_amount(balance, whitespaces=True)
612 label, is_default_label = self.wallet.get_label(tx_hash)
614 label = _('Pruned transaction outputs')
615 is_default_label = False
617 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
618 item.setFont(2, QFont(MONOSPACE_FONT))
619 item.setFont(3, QFont(MONOSPACE_FONT))
620 item.setFont(4, QFont(MONOSPACE_FONT))
622 item.setForeground(3, QBrush(QColor("#BC1E1E")))
624 item.setData(0, Qt.UserRole, tx_hash)
625 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
627 item.setForeground(2, QBrush(QColor('grey')))
629 item.setIcon(0, icon)
630 self.history_list.insertTopLevelItem(0,item)
633 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
634 run_hook('history_tab_update')
637 def create_send_tab(self):
640 grid = QGridLayout(w)
642 grid.setColumnMinimumWidth(3,300)
643 grid.setColumnStretch(5,1)
644 grid.setRowStretch(8, 1)
646 self.payto_e = QLineEdit()
647 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)'))
648 grid.addWidget(QLabel(_('Pay to')), 1, 0)
649 grid.addWidget(self.payto_e, 1, 1, 1, 3)
650 grid.addWidget(self.payto_help, 1, 4)
652 completer = QCompleter()
653 completer.setCaseSensitivity(False)
654 self.payto_e.setCompleter(completer)
655 completer.setModel(self.completions)
657 self.message_e = QLineEdit()
658 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.'))
659 grid.addWidget(QLabel(_('Description')), 2, 0)
660 grid.addWidget(self.message_e, 2, 1, 1, 3)
661 grid.addWidget(self.message_help, 2, 4)
663 self.from_label = QLabel(_('From'))
664 grid.addWidget(self.from_label, 3, 0)
665 self.from_list = QTreeWidget(self)
666 self.from_list.setColumnCount(2)
667 self.from_list.setColumnWidth(0, 350)
668 self.from_list.setColumnWidth(1, 50)
669 self.from_list.setHeaderHidden (True)
670 self.from_list.setMaximumHeight(80)
671 grid.addWidget(self.from_list, 3, 1, 1, 3)
672 self.set_pay_from([])
674 self.amount_e = AmountEdit(self.get_decimal_point)
675 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
676 + _('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.') \
677 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
678 grid.addWidget(QLabel(_('Amount')), 4, 0)
679 grid.addWidget(self.amount_e, 4, 1, 1, 2)
680 grid.addWidget(self.amount_help, 4, 3)
682 self.fee_e = AmountEdit(self.get_decimal_point)
683 grid.addWidget(QLabel(_('Fee')), 5, 0)
684 grid.addWidget(self.fee_e, 5, 1, 1, 2)
685 grid.addWidget(HelpButton(
686 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
687 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
688 + _('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)
690 run_hook('exchange_rate_button', grid)
692 self.send_button = EnterButton(_("Send"), self.do_send)
693 grid.addWidget(self.send_button, 6, 1)
695 b = EnterButton(_("Clear"), self.do_clear)
696 grid.addWidget(b, 6, 2)
698 self.payto_sig = QLabel('')
699 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
701 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
702 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
705 def entry_changed( is_fee ):
706 self.funds_error = False
708 if self.amount_e.is_shortcut:
709 self.amount_e.is_shortcut = False
710 sendable = self.get_sendable_balance()
711 # there is only one output because we are completely spending inputs
712 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
713 fee = self.wallet.estimated_fee(inputs, 1)
715 self.amount_e.setText( self.format_amount(amount) )
716 self.fee_e.setText( self.format_amount( fee ) )
719 amount = self.amount_e.get_amount()
720 fee = self.fee_e.get_amount()
722 if not is_fee: fee = None
725 # assume that there will be 2 outputs (one for change)
726 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
728 self.fee_e.setText( self.format_amount( fee ) )
731 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
735 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
736 self.funds_error = True
737 text = _( "Not enough funds" )
738 c, u = self.wallet.get_frozen_balance()
739 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
741 self.statusBar().showMessage(text)
742 self.amount_e.setPalette(palette)
743 self.fee_e.setPalette(palette)
745 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
746 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
748 run_hook('create_send_tab', grid)
752 def set_pay_from(self, l):
754 self.from_list.clear()
755 self.from_label.setHidden(len(self.pay_from) == 0)
756 self.from_list.setHidden(len(self.pay_from) == 0)
757 for addr in self.pay_from:
758 c, u = self.wallet.get_addr_balance(addr)
759 balance = self.format_amount(c + u)
760 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
763 def update_completions(self):
765 for addr,label in self.wallet.labels.items():
766 if addr in self.wallet.addressbook:
767 l.append( label + ' <' + addr + '>')
769 run_hook('update_completions', l)
770 self.completions.setStringList(l)
774 return lambda s, *args: s.do_protect(func, args)
778 label = unicode( self.message_e.text() )
780 if self.gui_object.payment_request:
781 outputs = self.gui_object.payment_request.outputs
782 amount = self.gui_object.payment_request.get_amount()
785 r = unicode( self.payto_e.text() )
788 # label or alias, with address in brackets
789 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
790 to_address = m.group(2) if m else r
791 if not is_valid(to_address):
792 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
796 amount = self.amount_e.get_amount()
798 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
801 outputs = [(to_address, amount)]
804 fee = self.fee_e.get_amount()
806 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
809 confirm_amount = self.config.get('confirm_amount', 100000000)
810 if amount >= confirm_amount:
811 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
814 confirm_fee = self.config.get('confirm_fee', 100000)
815 if fee >= confirm_fee:
816 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()}):
819 self.send_tx(outputs, fee, label)
824 def send_tx(self, outputs, fee, label, password):
825 self.send_button.setDisabled(True)
827 # first, create an unsigned tx
828 domain = self.get_payment_sources()
830 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
832 except Exception as e:
833 traceback.print_exc(file=sys.stdout)
834 self.show_message(str(e))
835 self.send_button.setDisabled(False)
838 # call hook to see if plugin needs gui interaction
839 run_hook('send_tx', tx)
845 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
846 self.wallet.sign_transaction(tx, keypairs, password)
847 return tx, fee, label
849 def sign_done(tx, fee, label):
851 self.show_message(tx.error)
852 self.send_button.setDisabled(False)
854 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
855 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
856 self.send_button.setDisabled(False)
859 self.wallet.set_label(tx.hash(), label)
861 if not self.gui_object.payment_request:
862 if not tx.is_complete() or self.config.get('show_before_broadcast'):
863 self.show_transaction(tx)
865 self.send_button.setDisabled(False)
868 self.broadcast_transaction(tx)
870 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
871 self.waiting_dialog.start()
875 def broadcast_transaction(self, tx):
877 def broadcast_thread():
878 if self.gui_object.payment_request:
879 refund_address = self.wallet.addresses()[0]
880 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
881 self.gui_object.payment_request = None
883 status, msg = self.wallet.sendtx(tx)
886 def broadcast_done(status, msg):
888 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
891 QMessageBox.warning(self, _('Error'), msg, _('OK'))
892 self.send_button.setDisabled(False)
894 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
895 self.waiting_dialog.start()
899 def prepare_for_payment_request(self):
900 style = "QWidget { background-color:none;border:none;}"
901 self.tabs.setCurrentIndex(1)
902 for e in [self.payto_e, self.amount_e, self.message_e]:
904 e.setStyleSheet(style)
905 for h in [self.payto_help, self.amount_help, self.message_help]:
907 self.payto_e.setText(_("please wait..."))
910 def payment_request_ok(self):
911 self.payto_e.setText(self.gui_object.payment_request.domain)
912 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
913 self.message_e.setText(self.gui_object.payment_request.memo)
915 def payment_request_error(self):
917 self.show_message(self.gui_object.payment_request.error)
920 def set_send(self, address, amount, label, message):
922 if label and self.wallet.labels.get(address) != label:
923 if self.question('Give label "%s" to address %s ?'%(label,address)):
924 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
925 self.wallet.addressbook.append(address)
926 self.wallet.set_label(address, label)
928 self.tabs.setCurrentIndex(1)
929 label = self.wallet.labels.get(address)
930 m_addr = label + ' <'+ address +'>' if label else address
931 self.payto_e.setText(m_addr)
933 self.message_e.setText(message)
935 self.amount_e.setText(amount)
939 self.payto_sig.setVisible(False)
940 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
942 self.set_frozen(e,False)
944 for h in [self.payto_help, self.amount_help, self.message_help]:
947 self.set_pay_from([])
950 def set_frozen(self,entry,frozen):
952 entry.setReadOnly(True)
953 entry.setFrame(False)
955 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
956 entry.setPalette(palette)
958 entry.setReadOnly(False)
961 palette.setColor(entry.backgroundRole(), QColor('white'))
962 entry.setPalette(palette)
965 def set_addrs_frozen(self,addrs,freeze):
967 if not addr: continue
968 if addr in self.wallet.frozen_addresses and not freeze:
969 self.wallet.unfreeze(addr)
970 elif addr not in self.wallet.frozen_addresses and freeze:
971 self.wallet.freeze(addr)
972 self.update_receive_tab()
976 def create_list_tab(self, headers):
977 "generic tab creation method"
978 l = MyTreeWidget(self)
979 l.setColumnCount( len(headers) )
980 l.setHeaderLabels( headers )
990 vbox.addWidget(buttons)
995 buttons.setLayout(hbox)
1000 def create_receive_tab(self):
1001 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1002 l.setContextMenuPolicy(Qt.CustomContextMenu)
1003 l.customContextMenuRequested.connect(self.create_receive_menu)
1004 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1005 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1006 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1007 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1008 self.receive_list = l
1009 self.receive_buttons_hbox = hbox
1016 def save_column_widths(self):
1017 self.column_widths["receive"] = []
1018 for i in range(self.receive_list.columnCount() -1):
1019 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1021 self.column_widths["history"] = []
1022 for i in range(self.history_list.columnCount() - 1):
1023 self.column_widths["history"].append(self.history_list.columnWidth(i))
1025 self.column_widths["contacts"] = []
1026 for i in range(self.contacts_list.columnCount() - 1):
1027 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1029 self.config.set_key("column_widths_2", self.column_widths, True)
1032 def create_contacts_tab(self):
1033 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1034 l.setContextMenuPolicy(Qt.CustomContextMenu)
1035 l.customContextMenuRequested.connect(self.create_contact_menu)
1036 for i,width in enumerate(self.column_widths['contacts']):
1037 l.setColumnWidth(i, width)
1039 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1040 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1041 self.contacts_list = l
1042 self.contacts_buttons_hbox = hbox
1047 def delete_imported_key(self, addr):
1048 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1049 self.wallet.delete_imported_key(addr)
1050 self.update_receive_tab()
1051 self.update_history_tab()
1053 def edit_account_label(self, k):
1054 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1056 label = unicode(text)
1057 self.wallet.set_label(k,label)
1058 self.update_receive_tab()
1060 def account_set_expanded(self, item, k, b):
1062 self.accounts_expanded[k] = b
1064 def create_account_menu(self, position, k, item):
1066 if item.isExpanded():
1067 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1069 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1070 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1071 if self.wallet.seed_version > 4:
1072 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1073 if self.wallet.account_is_pending(k):
1074 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1075 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1077 def delete_pending_account(self, k):
1078 self.wallet.delete_pending_account(k)
1079 self.update_receive_tab()
1081 def create_receive_menu(self, position):
1082 # fixme: this function apparently has a side effect.
1083 # if it is not called the menu pops up several times
1084 #self.receive_list.selectedIndexes()
1086 selected = self.receive_list.selectedItems()
1087 multi_select = len(selected) > 1
1088 addrs = [unicode(item.text(0)) for item in selected]
1089 if not multi_select:
1090 item = self.receive_list.itemAt(position)
1094 if not is_valid(addr):
1095 k = str(item.data(0,32).toString())
1097 self.create_account_menu(position, k, item)
1099 item.setExpanded(not item.isExpanded())
1103 if not multi_select:
1104 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1105 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1106 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1107 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1108 if not self.wallet.is_watching_only():
1109 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1110 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1111 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1112 if self.wallet.is_imported(addr):
1113 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1115 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1116 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1117 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1118 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1120 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1121 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1123 run_hook('receive_menu', menu, addrs)
1124 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1127 def get_sendable_balance(self):
1128 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1131 def get_payment_sources(self):
1133 return self.pay_from
1135 return self.wallet.get_account_addresses(self.current_account)
1138 def send_from_addresses(self, addrs):
1139 self.set_pay_from( addrs )
1140 self.tabs.setCurrentIndex(1)
1143 def payto(self, addr):
1145 label = self.wallet.labels.get(addr)
1146 m_addr = label + ' <' + addr + '>' if label else addr
1147 self.tabs.setCurrentIndex(1)
1148 self.payto_e.setText(m_addr)
1149 self.amount_e.setFocus()
1152 def delete_contact(self, x):
1153 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1154 self.wallet.delete_contact(x)
1155 self.wallet.set_label(x, None)
1156 self.update_history_tab()
1157 self.update_contacts_tab()
1158 self.update_completions()
1161 def create_contact_menu(self, position):
1162 item = self.contacts_list.itemAt(position)
1165 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1167 addr = unicode(item.text(0))
1168 label = unicode(item.text(1))
1169 is_editable = item.data(0,32).toBool()
1170 payto_addr = item.data(0,33).toString()
1171 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1172 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1173 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1175 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1176 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1178 run_hook('create_contact_menu', menu, item)
1179 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1182 def update_receive_item(self, item):
1183 item.setFont(0, QFont(MONOSPACE_FONT))
1184 address = str(item.data(0,0).toString())
1185 label = self.wallet.labels.get(address,'')
1186 item.setData(1,0,label)
1187 item.setData(0,32, True) # is editable
1189 run_hook('update_receive_item', address, item)
1191 if not self.wallet.is_mine(address): return
1193 c, u = self.wallet.get_addr_balance(address)
1194 balance = self.format_amount(c + u)
1195 item.setData(2,0,balance)
1197 if address in self.wallet.frozen_addresses:
1198 item.setBackgroundColor(0, QColor('lightblue'))
1201 def update_receive_tab(self):
1202 l = self.receive_list
1203 # extend the syntax for consistency
1204 l.addChild = l.addTopLevelItem
1205 l.insertChild = l.insertTopLevelItem
1208 for i,width in enumerate(self.column_widths['receive']):
1209 l.setColumnWidth(i, width)
1211 accounts = self.wallet.get_accounts()
1212 if self.current_account is None:
1213 account_items = sorted(accounts.items())
1215 account_items = [(self.current_account, accounts.get(self.current_account))]
1218 for k, account in account_items:
1220 if len(accounts) > 1:
1221 name = self.wallet.get_account_name(k)
1222 c,u = self.wallet.get_account_balance(k)
1223 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1224 l.addTopLevelItem(account_item)
1225 account_item.setExpanded(self.accounts_expanded.get(k, True))
1226 account_item.setData(0, 32, k)
1230 sequences = [0,1] if account.has_change() else [0]
1231 for is_change in sequences:
1232 if len(sequences) > 1:
1233 name = _("Receiving") if not is_change else _("Change")
1234 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1235 account_item.addChild(seq_item)
1237 seq_item.setExpanded(True)
1239 seq_item = account_item
1241 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1247 for address in account.get_addresses(is_change):
1249 num, is_used = self.wallet.is_used(address)
1252 if gap > self.wallet.gap_limit:
1257 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1258 self.update_receive_item(item)
1260 item.setBackgroundColor(1, QColor('red'))
1264 seq_item.insertChild(0,used_item)
1266 used_item.addChild(item)
1268 seq_item.addChild(item)
1270 # we use column 1 because column 0 may be hidden
1271 l.setCurrentItem(l.topLevelItem(0),1)
1274 def update_contacts_tab(self):
1275 l = self.contacts_list
1278 for address in self.wallet.addressbook:
1279 label = self.wallet.labels.get(address,'')
1280 n = self.wallet.get_num_tx(address)
1281 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1282 item.setFont(0, QFont(MONOSPACE_FONT))
1283 # 32 = label can be edited (bool)
1284 item.setData(0,32, True)
1286 item.setData(0,33, address)
1287 l.addTopLevelItem(item)
1289 run_hook('update_contacts_tab', l)
1290 l.setCurrentItem(l.topLevelItem(0))
1294 def create_console_tab(self):
1295 from console import Console
1296 self.console = console = Console()
1300 def update_console(self):
1301 console = self.console
1302 console.history = self.config.get("console-history",[])
1303 console.history_index = len(console.history)
1305 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1306 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1308 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1310 def mkfunc(f, method):
1311 return lambda *args: apply( f, (method, args, self.password_dialog ))
1313 if m[0]=='_' or m in ['network','wallet']: continue
1314 methods[m] = mkfunc(c._run, m)
1316 console.updateNamespace(methods)
1319 def change_account(self,s):
1320 if s == _("All accounts"):
1321 self.current_account = None
1323 accounts = self.wallet.get_account_names()
1324 for k, v in accounts.items():
1326 self.current_account = k
1327 self.update_history_tab()
1328 self.update_status()
1329 self.update_receive_tab()
1331 def create_status_bar(self):
1334 sb.setFixedHeight(35)
1335 qtVersion = qVersion()
1337 self.balance_label = QLabel("")
1338 sb.addWidget(self.balance_label)
1340 from version_getter import UpdateLabel
1341 self.updatelabel = UpdateLabel(self.config, sb)
1343 self.account_selector = QComboBox()
1344 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1345 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1346 sb.addPermanentWidget(self.account_selector)
1348 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1349 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1351 self.lock_icon = QIcon()
1352 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1353 sb.addPermanentWidget( self.password_button )
1355 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1356 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1357 sb.addPermanentWidget( self.seed_button )
1358 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1359 sb.addPermanentWidget( self.status_button )
1361 run_hook('create_status_bar', (sb,))
1363 self.setStatusBar(sb)
1366 def update_lock_icon(self):
1367 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1368 self.password_button.setIcon( icon )
1371 def update_buttons_on_seed(self):
1372 if self.wallet.has_seed():
1373 self.seed_button.show()
1375 self.seed_button.hide()
1377 if not self.wallet.is_watching_only():
1378 self.password_button.show()
1379 self.send_button.setText(_("Send"))
1381 self.password_button.hide()
1382 self.send_button.setText(_("Create unsigned transaction"))
1385 def change_password_dialog(self):
1386 from password_dialog import PasswordDialog
1387 d = PasswordDialog(self.wallet, self)
1389 self.update_lock_icon()
1392 def new_contact_dialog(self):
1395 d.setWindowTitle(_("New Contact"))
1396 vbox = QVBoxLayout(d)
1397 vbox.addWidget(QLabel(_('New Contact')+':'))
1399 grid = QGridLayout()
1402 grid.addWidget(QLabel(_("Address")), 1, 0)
1403 grid.addWidget(line1, 1, 1)
1404 grid.addWidget(QLabel(_("Name")), 2, 0)
1405 grid.addWidget(line2, 2, 1)
1407 vbox.addLayout(grid)
1408 vbox.addLayout(ok_cancel_buttons(d))
1413 address = str(line1.text())
1414 label = unicode(line2.text())
1416 if not is_valid(address):
1417 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1420 self.wallet.add_contact(address)
1422 self.wallet.set_label(address, label)
1424 self.update_contacts_tab()
1425 self.update_history_tab()
1426 self.update_completions()
1427 self.tabs.setCurrentIndex(3)
1431 def new_account_dialog(self, password):
1433 dialog = QDialog(self)
1435 dialog.setWindowTitle(_("New Account"))
1437 vbox = QVBoxLayout()
1438 vbox.addWidget(QLabel(_('Account name')+':'))
1441 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1442 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1447 vbox.addLayout(ok_cancel_buttons(dialog))
1448 dialog.setLayout(vbox)
1452 name = str(e.text())
1455 self.wallet.create_pending_account(name, password)
1456 self.update_receive_tab()
1457 self.tabs.setCurrentIndex(2)
1462 def show_master_public_keys(self):
1464 dialog = QDialog(self)
1466 dialog.setWindowTitle(_("Master Public Keys"))
1468 main_layout = QGridLayout()
1469 mpk_dict = self.wallet.get_master_public_keys()
1471 for key, value in mpk_dict.items():
1472 main_layout.addWidget(QLabel(key), i, 0)
1473 mpk_text = QTextEdit()
1474 mpk_text.setReadOnly(True)
1475 mpk_text.setMaximumHeight(170)
1476 mpk_text.setText(value)
1477 main_layout.addWidget(mpk_text, i + 1, 0)
1480 vbox = QVBoxLayout()
1481 vbox.addLayout(main_layout)
1482 vbox.addLayout(close_button(dialog))
1484 dialog.setLayout(vbox)
1489 def show_seed_dialog(self, password):
1490 if not self.wallet.has_seed():
1491 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1495 mnemonic = self.wallet.get_mnemonic(password)
1497 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1499 from seed_dialog import SeedDialog
1500 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1505 def show_qrcode(self, data, title = _("QR code")):
1509 d.setWindowTitle(title)
1510 d.setMinimumSize(270, 300)
1511 vbox = QVBoxLayout()
1512 qrw = QRCodeWidget(data)
1513 vbox.addWidget(qrw, 1)
1514 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1515 hbox = QHBoxLayout()
1518 filename = os.path.join(self.config.path, "qrcode.bmp")
1521 bmp.save_qrcode(qrw.qr, filename)
1522 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1524 def copy_to_clipboard():
1525 bmp.save_qrcode(qrw.qr, filename)
1526 self.app.clipboard().setImage(QImage(filename))
1527 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1529 b = QPushButton(_("Copy"))
1531 b.clicked.connect(copy_to_clipboard)
1533 b = QPushButton(_("Save"))
1535 b.clicked.connect(print_qr)
1537 b = QPushButton(_("Close"))
1539 b.clicked.connect(d.accept)
1542 vbox.addLayout(hbox)
1547 def do_protect(self, func, args):
1548 if self.wallet.use_encryption:
1549 password = self.password_dialog()
1555 if args != (False,):
1556 args = (self,) + args + (password,)
1558 args = (self,password)
1562 def show_public_keys(self, address):
1563 if not address: return
1565 pubkey_list = self.wallet.get_public_keys(address)
1566 except Exception as e:
1567 traceback.print_exc(file=sys.stdout)
1568 self.show_message(str(e))
1572 d.setMinimumSize(600, 200)
1574 vbox = QVBoxLayout()
1575 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1576 vbox.addWidget( QLabel(_("Public key") + ':'))
1578 keys.setReadOnly(True)
1579 keys.setText('\n'.join(pubkey_list))
1580 vbox.addWidget(keys)
1581 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1582 vbox.addLayout(close_button(d))
1587 def show_private_key(self, address, password):
1588 if not address: return
1590 pk_list = self.wallet.get_private_key(address, password)
1591 except Exception as e:
1592 traceback.print_exc(file=sys.stdout)
1593 self.show_message(str(e))
1597 d.setMinimumSize(600, 200)
1599 vbox = QVBoxLayout()
1600 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1601 vbox.addWidget( QLabel(_("Private key") + ':'))
1603 keys.setReadOnly(True)
1604 keys.setText('\n'.join(pk_list))
1605 vbox.addWidget(keys)
1606 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1607 vbox.addLayout(close_button(d))
1613 def do_sign(self, address, message, signature, password):
1614 message = unicode(message.toPlainText())
1615 message = message.encode('utf-8')
1617 sig = self.wallet.sign_message(str(address.text()), message, password)
1618 signature.setText(sig)
1619 except Exception as e:
1620 self.show_message(str(e))
1622 def do_verify(self, address, message, signature):
1623 message = unicode(message.toPlainText())
1624 message = message.encode('utf-8')
1625 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1626 self.show_message(_("Signature verified"))
1628 self.show_message(_("Error: wrong signature"))
1631 def sign_verify_message(self, address=''):
1634 d.setWindowTitle(_('Sign/verify Message'))
1635 d.setMinimumSize(410, 290)
1637 layout = QGridLayout(d)
1639 message_e = QTextEdit()
1640 layout.addWidget(QLabel(_('Message')), 1, 0)
1641 layout.addWidget(message_e, 1, 1)
1642 layout.setRowStretch(2,3)
1644 address_e = QLineEdit()
1645 address_e.setText(address)
1646 layout.addWidget(QLabel(_('Address')), 2, 0)
1647 layout.addWidget(address_e, 2, 1)
1649 signature_e = QTextEdit()
1650 layout.addWidget(QLabel(_('Signature')), 3, 0)
1651 layout.addWidget(signature_e, 3, 1)
1652 layout.setRowStretch(3,1)
1654 hbox = QHBoxLayout()
1656 b = QPushButton(_("Sign"))
1657 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1660 b = QPushButton(_("Verify"))
1661 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1664 b = QPushButton(_("Close"))
1665 b.clicked.connect(d.accept)
1667 layout.addLayout(hbox, 4, 1)
1672 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1674 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1675 message_e.setText(decrypted)
1676 except Exception as e:
1677 self.show_message(str(e))
1680 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1681 message = unicode(message_e.toPlainText())
1682 message = message.encode('utf-8')
1684 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1685 encrypted_e.setText(encrypted)
1686 except Exception as e:
1687 self.show_message(str(e))
1691 def encrypt_message(self, address = ''):
1694 d.setWindowTitle(_('Encrypt/decrypt Message'))
1695 d.setMinimumSize(610, 490)
1697 layout = QGridLayout(d)
1699 message_e = QTextEdit()
1700 layout.addWidget(QLabel(_('Message')), 1, 0)
1701 layout.addWidget(message_e, 1, 1)
1702 layout.setRowStretch(2,3)
1704 pubkey_e = QLineEdit()
1706 pubkey = self.wallet.getpubkeys(address)[0]
1707 pubkey_e.setText(pubkey)
1708 layout.addWidget(QLabel(_('Public key')), 2, 0)
1709 layout.addWidget(pubkey_e, 2, 1)
1711 encrypted_e = QTextEdit()
1712 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1713 layout.addWidget(encrypted_e, 3, 1)
1714 layout.setRowStretch(3,1)
1716 hbox = QHBoxLayout()
1717 b = QPushButton(_("Encrypt"))
1718 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1721 b = QPushButton(_("Decrypt"))
1722 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1725 b = QPushButton(_("Close"))
1726 b.clicked.connect(d.accept)
1729 layout.addLayout(hbox, 4, 1)
1733 def question(self, msg):
1734 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1736 def show_message(self, msg):
1737 QMessageBox.information(self, _('Message'), msg, _('OK'))
1739 def password_dialog(self, msg=None):
1742 d.setWindowTitle(_("Enter Password"))
1747 vbox = QVBoxLayout()
1749 msg = _('Please enter your password')
1750 vbox.addWidget(QLabel(msg))
1752 grid = QGridLayout()
1754 grid.addWidget(QLabel(_('Password')), 1, 0)
1755 grid.addWidget(pw, 1, 1)
1756 vbox.addLayout(grid)
1758 vbox.addLayout(ok_cancel_buttons(d))
1761 run_hook('password_dialog', pw, grid, 1)
1762 if not d.exec_(): return
1763 return unicode(pw.text())
1772 def tx_from_text(self, txt):
1773 "json or raw hexadecimal"
1776 tx = Transaction(txt)
1782 tx_dict = json.loads(str(txt))
1783 assert "hex" in tx_dict.keys()
1784 tx = Transaction(tx_dict["hex"])
1785 if tx_dict.has_key("input_info"):
1786 input_info = json.loads(tx_dict['input_info'])
1787 tx.add_input_info(input_info)
1790 traceback.print_exc(file=sys.stdout)
1793 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1797 def read_tx_from_file(self):
1798 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1802 with open(fileName, "r") as f:
1803 file_content = f.read()
1804 except (ValueError, IOError, os.error), reason:
1805 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1807 return self.tx_from_text(file_content)
1811 def sign_raw_transaction(self, tx, input_info, password):
1812 self.wallet.signrawtransaction(tx, input_info, [], password)
1814 def do_process_from_text(self):
1815 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1818 tx = self.tx_from_text(text)
1820 self.show_transaction(tx)
1822 def do_process_from_file(self):
1823 tx = self.read_tx_from_file()
1825 self.show_transaction(tx)
1827 def do_process_from_txid(self):
1828 from electrum import transaction
1829 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1831 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1833 tx = transaction.Transaction(r)
1835 self.show_transaction(tx)
1837 self.show_message("unknown transaction")
1839 def do_process_from_csvReader(self, csvReader):
1844 for position, row in enumerate(csvReader):
1846 if not is_valid(address):
1847 errors.append((position, address))
1849 amount = Decimal(row[1])
1850 amount = int(100000000*amount)
1851 outputs.append((address, amount))
1852 except (ValueError, IOError, os.error), reason:
1853 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1857 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1858 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1862 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1863 except Exception as e:
1864 self.show_message(str(e))
1867 self.show_transaction(tx)
1869 def do_process_from_csv_file(self):
1870 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1874 with open(fileName, "r") as f:
1875 csvReader = csv.reader(f)
1876 self.do_process_from_csvReader(csvReader)
1877 except (ValueError, IOError, os.error), reason:
1878 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1881 def do_process_from_csv_text(self):
1882 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1883 + _("Format: address, amount. One output per line"), _("Load CSV"))
1886 f = StringIO.StringIO(text)
1887 csvReader = csv.reader(f)
1888 self.do_process_from_csvReader(csvReader)
1893 def export_privkeys_dialog(self, password):
1894 if self.wallet.is_watching_only():
1895 self.show_message(_("This is a watching-only wallet"))
1899 d.setWindowTitle(_('Private keys'))
1900 d.setMinimumSize(850, 300)
1901 vbox = QVBoxLayout(d)
1903 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1904 _("Exposing a single private key can compromise your entire wallet!"),
1905 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1906 vbox.addWidget(QLabel(msg))
1912 defaultname = 'electrum-private-keys.csv'
1913 select_msg = _('Select file to export your private keys to')
1914 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1915 vbox.addLayout(hbox)
1917 h, b = ok_cancel_buttons2(d, _('Export'))
1922 addresses = self.wallet.addresses(True)
1924 def privkeys_thread():
1925 for addr in addresses:
1929 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1930 d.emit(SIGNAL('computing_privkeys'))
1931 d.emit(SIGNAL('show_privkeys'))
1933 def show_privkeys():
1934 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1938 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1939 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1940 threading.Thread(target=privkeys_thread).start()
1946 filename = filename_e.text()
1951 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1952 except (IOError, os.error), reason:
1953 export_error_label = _("Electrum was unable to produce a private key-export.")
1954 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1956 except Exception as e:
1957 self.show_message(str(e))
1960 self.show_message(_("Private keys exported."))
1963 def do_export_privkeys(self, fileName, pklist, is_csv):
1964 with open(fileName, "w+") as f:
1966 transaction = csv.writer(f)
1967 transaction.writerow(["address", "private_key"])
1968 for addr, pk in pklist.items():
1969 transaction.writerow(["%34s"%addr,pk])
1972 f.write(json.dumps(pklist, indent = 4))
1975 def do_import_labels(self):
1976 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1977 if not labelsFile: return
1979 f = open(labelsFile, 'r')
1982 for key, value in json.loads(data).items():
1983 self.wallet.set_label(key, value)
1984 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1985 except (IOError, os.error), reason:
1986 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1989 def do_export_labels(self):
1990 labels = self.wallet.labels
1992 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1994 with open(fileName, 'w+') as f:
1995 json.dump(labels, f)
1996 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1997 except (IOError, os.error), reason:
1998 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2001 def export_history_dialog(self):
2004 d.setWindowTitle(_('Export History'))
2005 d.setMinimumSize(400, 200)
2006 vbox = QVBoxLayout(d)
2008 defaultname = os.path.expanduser('~/electrum-history.csv')
2009 select_msg = _('Select file to export your wallet transactions to')
2011 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2012 vbox.addLayout(hbox)
2016 h, b = ok_cancel_buttons2(d, _('Export'))
2021 filename = filename_e.text()
2026 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2027 except (IOError, os.error), reason:
2028 export_error_label = _("Electrum was unable to produce a transaction export.")
2029 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2032 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2035 def do_export_history(self, wallet, fileName, is_csv):
2036 history = wallet.get_tx_history()
2038 for item in history:
2039 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2041 if timestamp is not None:
2043 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2044 except [RuntimeError, TypeError, NameError] as reason:
2045 time_string = "unknown"
2048 time_string = "unknown"
2050 time_string = "pending"
2052 if value is not None:
2053 value_string = format_satoshis(value, True)
2058 fee_string = format_satoshis(fee, True)
2063 label, is_default_label = wallet.get_label(tx_hash)
2064 label = label.encode('utf-8')
2068 balance_string = format_satoshis(balance, False)
2070 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2072 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2074 with open(fileName, "w+") as f:
2076 transaction = csv.writer(f)
2077 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2079 transaction.writerow(line)
2082 f.write(json.dumps(lines, indent = 4))
2085 def sweep_key_dialog(self):
2087 d.setWindowTitle(_('Sweep private keys'))
2088 d.setMinimumSize(600, 300)
2090 vbox = QVBoxLayout(d)
2091 vbox.addWidget(QLabel(_("Enter private keys")))
2093 keys_e = QTextEdit()
2094 keys_e.setTabChangesFocus(True)
2095 vbox.addWidget(keys_e)
2097 h, address_e = address_field(self.wallet.addresses())
2101 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2102 vbox.addLayout(hbox)
2103 button.setEnabled(False)
2106 addr = str(address_e.text())
2107 if bitcoin.is_address(addr):
2111 pk = str(keys_e.toPlainText()).strip()
2112 if Wallet.is_private_key(pk):
2115 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2116 keys_e.textChanged.connect(f)
2117 address_e.textChanged.connect(f)
2121 fee = self.wallet.fee
2122 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2123 self.show_transaction(tx)
2127 def do_import_privkey(self, password):
2128 if not self.wallet.has_imported_keys():
2129 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2130 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2131 + _('Are you sure you understand what you are doing?'), 3, 4)
2134 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2137 text = str(text).split()
2142 addr = self.wallet.import_key(key, password)
2143 except Exception as e:
2149 addrlist.append(addr)
2151 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2153 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2154 self.update_receive_tab()
2155 self.update_history_tab()
2158 def settings_dialog(self):
2160 d.setWindowTitle(_('Electrum Settings'))
2162 vbox = QVBoxLayout()
2163 grid = QGridLayout()
2164 grid.setColumnStretch(0,1)
2166 nz_label = QLabel(_('Display zeros') + ':')
2167 grid.addWidget(nz_label, 0, 0)
2168 nz_e = AmountEdit(None,True)
2169 nz_e.setText("%d"% self.num_zeros)
2170 grid.addWidget(nz_e, 0, 1)
2171 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2172 grid.addWidget(HelpButton(msg), 0, 2)
2173 if not self.config.is_modifiable('num_zeros'):
2174 for w in [nz_e, nz_label]: w.setEnabled(False)
2176 lang_label=QLabel(_('Language') + ':')
2177 grid.addWidget(lang_label, 1, 0)
2178 lang_combo = QComboBox()
2179 from electrum.i18n import languages
2180 lang_combo.addItems(languages.values())
2182 index = languages.keys().index(self.config.get("language",''))
2185 lang_combo.setCurrentIndex(index)
2186 grid.addWidget(lang_combo, 1, 1)
2187 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2188 if not self.config.is_modifiable('language'):
2189 for w in [lang_combo, lang_label]: w.setEnabled(False)
2192 fee_label = QLabel(_('Transaction fee') + ':')
2193 grid.addWidget(fee_label, 2, 0)
2194 fee_e = AmountEdit(self.get_decimal_point)
2195 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2196 grid.addWidget(fee_e, 2, 1)
2197 msg = _('Fee per kilobyte of transaction.') + ' ' \
2198 + _('Recommended value') + ': ' + self.format_amount(20000)
2199 grid.addWidget(HelpButton(msg), 2, 2)
2200 if not self.config.is_modifiable('fee_per_kb'):
2201 for w in [fee_e, fee_label]: w.setEnabled(False)
2203 units = ['BTC', 'mBTC']
2204 unit_label = QLabel(_('Base unit') + ':')
2205 grid.addWidget(unit_label, 3, 0)
2206 unit_combo = QComboBox()
2207 unit_combo.addItems(units)
2208 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2209 grid.addWidget(unit_combo, 3, 1)
2210 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2211 + '\n1BTC=1000mBTC.\n' \
2212 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2214 usechange_cb = QCheckBox(_('Use change addresses'))
2215 usechange_cb.setChecked(self.wallet.use_change)
2216 grid.addWidget(usechange_cb, 4, 0)
2217 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2218 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2220 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2221 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2222 grid.addWidget(block_ex_label, 5, 0)
2223 block_ex_combo = QComboBox()
2224 block_ex_combo.addItems(block_explorers)
2225 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2226 grid.addWidget(block_ex_combo, 5, 1)
2227 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2229 show_tx = self.config.get('show_before_broadcast', False)
2230 showtx_cb = QCheckBox(_('Show before broadcast'))
2231 showtx_cb.setChecked(show_tx)
2232 grid.addWidget(showtx_cb, 6, 0)
2233 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2235 vbox.addLayout(grid)
2237 vbox.addLayout(ok_cancel_buttons(d))
2241 if not d.exec_(): return
2244 fee = self.fee_e.get_amount()
2246 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2249 self.wallet.set_fee(fee)
2251 nz = unicode(nz_e.text())
2256 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2259 if self.num_zeros != nz:
2261 self.config.set_key('num_zeros', nz, True)
2262 self.update_history_tab()
2263 self.update_receive_tab()
2265 usechange_result = usechange_cb.isChecked()
2266 if self.wallet.use_change != usechange_result:
2267 self.wallet.use_change = usechange_result
2268 self.wallet.storage.put('use_change', self.wallet.use_change)
2270 if showtx_cb.isChecked() != show_tx:
2271 self.config.set_key('show_before_broadcast', not show_tx)
2273 unit_result = units[unit_combo.currentIndex()]
2274 if self.base_unit() != unit_result:
2275 self.decimal_point = 8 if unit_result == 'BTC' else 5
2276 self.config.set_key('decimal_point', self.decimal_point, True)
2277 self.update_history_tab()
2278 self.update_status()
2280 need_restart = False
2282 lang_request = languages.keys()[lang_combo.currentIndex()]
2283 if lang_request != self.config.get('language'):
2284 self.config.set_key("language", lang_request, True)
2287 be_result = block_explorers[block_ex_combo.currentIndex()]
2288 self.config.set_key('block_explorer', be_result, True)
2290 run_hook('close_settings_dialog')
2293 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2296 def run_network_dialog(self):
2297 if not self.network:
2299 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2301 def closeEvent(self, event):
2303 self.config.set_key("is_maximized", self.isMaximized())
2304 if not self.isMaximized():
2306 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2307 self.save_column_widths()
2308 self.config.set_key("console-history", self.console.history[-50:], True)
2309 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2313 def plugins_dialog(self):
2314 from electrum.plugins import plugins
2317 d.setWindowTitle(_('Electrum Plugins'))
2320 vbox = QVBoxLayout(d)
2323 scroll = QScrollArea()
2324 scroll.setEnabled(True)
2325 scroll.setWidgetResizable(True)
2326 scroll.setMinimumSize(400,250)
2327 vbox.addWidget(scroll)
2331 w.setMinimumHeight(len(plugins)*35)
2333 grid = QGridLayout()
2334 grid.setColumnStretch(0,1)
2337 def do_toggle(cb, p, w):
2340 if w: w.setEnabled(r)
2342 def mk_toggle(cb, p, w):
2343 return lambda: do_toggle(cb,p,w)
2345 for i, p in enumerate(plugins):
2347 cb = QCheckBox(p.fullname())
2348 cb.setDisabled(not p.is_available())
2349 cb.setChecked(p.is_enabled())
2350 grid.addWidget(cb, i, 0)
2351 if p.requires_settings():
2352 w = p.settings_widget(self)
2353 w.setEnabled( p.is_enabled() )
2354 grid.addWidget(w, i, 1)
2357 cb.clicked.connect(mk_toggle(cb,p,w))
2358 grid.addWidget(HelpButton(p.description()), i, 2)
2360 print_msg(_("Error: cannot display plugin"), p)
2361 traceback.print_exc(file=sys.stdout)
2362 grid.setRowStretch(i+1,1)
2364 vbox.addLayout(close_button(d))
2369 def show_account_details(self, k):
2370 account = self.wallet.accounts[k]
2373 d.setWindowTitle(_('Account Details'))
2376 vbox = QVBoxLayout(d)
2377 name = self.wallet.get_account_name(k)
2378 label = QLabel('Name: ' + name)
2379 vbox.addWidget(label)
2381 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2383 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2385 vbox.addWidget(QLabel(_('Master Public Key:')))
2388 text.setReadOnly(True)
2389 text.setMaximumHeight(170)
2390 vbox.addWidget(text)
2392 mpk_text = '\n'.join( account.get_master_pubkeys() )
2393 text.setText(mpk_text)
2395 vbox.addLayout(close_button(d))