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):
103 def build_tray_menu(self):
105 m.addAction(_("Show/Hide"), self.show_or_hide)
106 m.addAction(_("Dark/Light"), self.toggle_tray_icon)
108 m.addAction(_("Exit Electrum"), self.close)
109 self.tray.setContextMenu(m)
111 def toggle_tray_icon(self):
112 self.dark_icon = not self.dark_icon
113 self.config.set_key("dark_icon", self.dark_icon, True)
114 icon = QIcon(":icons/electrum_dark_icon.png") if self.dark_icon else QIcon(':icons/electrum_light_icon.png')
115 self.tray.setIcon(icon)
117 def show_or_hide(self):
118 self.tray_activated(QSystemTrayIcon.DoubleClick)
120 def tray_activated(self, reason):
121 if reason == QSystemTrayIcon.DoubleClick:
122 if self.isMinimized() or self.isHidden():
128 def __init__(self, config, network):
129 QMainWindow.__init__(self)
132 self.network = network
134 self._close_electrum = False
137 self.dark_icon = self.config.get("dark_icon", False)
138 icon = QIcon(":icons/electrum_dark_icon.png") if self.dark_icon else QIcon(':icons/electrum_light_icon.png')
139 self.tray = QSystemTrayIcon(icon, self)
140 self.tray.setToolTip('Electrum')
141 self.tray.activated.connect(self.tray_activated)
142 self.build_tray_menu()
145 self.create_status_bar()
146 self.need_update = threading.Event()
148 self.decimal_point = config.get('decimal_point', 5)
149 self.num_zeros = int(config.get('num_zeros',0))
151 set_language(config.get('language'))
153 self.funds_error = False
154 self.completions = QStringListModel()
156 self.tabs = tabs = QTabWidget(self)
157 self.column_widths = self.config.get("column_widths_2", default_column_widths )
158 tabs.addTab(self.create_history_tab(), _('History') )
159 tabs.addTab(self.create_send_tab(), _('Send') )
160 tabs.addTab(self.create_receive_tab(), _('Receive') )
161 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
162 tabs.addTab(self.create_console_tab(), _('Console') )
163 tabs.setMinimumSize(600, 400)
164 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
165 self.setCentralWidget(tabs)
167 g = self.config.get("winpos-qt",[100, 100, 840, 400])
168 self.setGeometry(g[0], g[1], g[2], g[3])
170 self.setWindowIcon(QIcon(":icons/electrum.png"))
173 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
174 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
175 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
176 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
177 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
179 for i in range(tabs.count()):
180 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
182 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
183 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
184 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
185 self.connect(self, QtCore.SIGNAL('send_tx2'), self.send_tx2)
186 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
188 self.history_list.setFocus(True)
192 self.network.register_callback('updated', lambda: self.need_update.set())
193 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
194 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
195 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
196 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
198 # set initial message
199 self.console.showMessage(self.network.banner)
206 self.config.set_key('lite_mode', False, True)
212 self.config.set_key('lite_mode', True, True)
220 if not self.check_qt_version():
221 if self.config.get('lite_mode') is True:
222 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
223 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
224 self.config.set_key('lite_mode', False, True)
231 actuator = lite_window.MiniActuator(self)
233 actuator.load_theme()
235 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
237 driver = lite_window.MiniDriver(self, self.mini)
239 if self.config.get('lite_mode') is True:
245 def check_qt_version(self):
246 qtVersion = qVersion()
247 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
250 def update_account_selector(self):
252 accounts = self.wallet.get_account_names()
253 self.account_selector.clear()
254 if len(accounts) > 1:
255 self.account_selector.addItems([_("All accounts")] + accounts.values())
256 self.account_selector.setCurrentIndex(0)
257 self.account_selector.show()
259 self.account_selector.hide()
262 def load_wallet(self, wallet):
265 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
266 self.current_account = self.wallet.storage.get("current_account", None)
268 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
269 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
270 self.setWindowTitle( title )
272 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
273 self.notify_transactions()
274 self.update_account_selector()
275 self.new_account.setEnabled(self.wallet.can_create_accounts())
276 self.update_lock_icon()
277 self.update_buttons_on_seed()
278 self.update_console()
280 run_hook('load_wallet', wallet)
283 def open_wallet(self):
284 wallet_folder = self.wallet.storage.path
285 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
289 storage = WalletStorage({'wallet_path': filename})
290 if not storage.file_exists:
291 self.show_message("file not found "+ filename)
294 self.wallet.stop_threads()
297 wallet = Wallet(storage)
298 wallet.start_threads(self.network)
300 self.load_wallet(wallet)
304 def backup_wallet(self):
306 path = self.wallet.storage.path
307 wallet_folder = os.path.dirname(path)
308 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
312 new_path = os.path.join(wallet_folder, filename)
315 shutil.copy2(path, new_path)
316 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
317 except (IOError, os.error), reason:
318 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
321 def new_wallet(self):
324 wallet_folder = os.path.dirname(self.wallet.storage.path)
325 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
328 filename = os.path.join(wallet_folder, filename)
330 storage = WalletStorage({'wallet_path': filename})
331 if storage.file_exists:
332 QMessageBox.critical(None, "Error", _("File exists"))
335 wizard = installwizard.InstallWizard(self.config, self.network, storage)
336 wallet = wizard.run()
338 self.load_wallet(wallet)
342 def init_menubar(self):
345 file_menu = menubar.addMenu(_("&File"))
346 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
347 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
348 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
349 file_menu.addAction(_("&Quit"), self.close)
351 wallet_menu = menubar.addMenu(_("&Wallet"))
352 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
353 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
355 wallet_menu.addSeparator()
357 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
358 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
359 wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
361 wallet_menu.addSeparator()
362 labels_menu = wallet_menu.addMenu(_("&Labels"))
363 labels_menu.addAction(_("&Import"), self.do_import_labels)
364 labels_menu.addAction(_("&Export"), self.do_export_labels)
366 keys_menu = wallet_menu.addMenu(_("&Private keys"))
367 keys_menu.addAction(_("&Import"), self.do_import_privkey)
368 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
370 wallet_menu.addAction(_("&Export History"), self.do_export_history)
372 tools_menu = menubar.addMenu(_("&Tools"))
374 # Settings / Preferences are all reserved keywords in OSX using this as work around
375 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
376 tools_menu.addAction(_("&Network"), self.run_network_dialog)
377 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
378 tools_menu.addSeparator()
379 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
380 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
381 tools_menu.addSeparator()
383 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
384 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
385 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
387 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
388 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
389 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
390 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
392 help_menu = menubar.addMenu(_("&Help"))
393 help_menu.addAction(_("&About"), self.show_about)
394 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
395 help_menu.addSeparator()
396 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
397 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
399 self.setMenuBar(menubar)
401 def show_about(self):
402 QMessageBox.about(self, "Electrum",
403 _("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."))
405 def show_report_bug(self):
406 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
407 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
410 def notify_transactions(self):
411 if not self.network or not self.network.is_connected():
414 print_error("Notifying GUI")
415 if len(self.network.pending_transactions_for_notifications) > 0:
416 # Combine the transactions if there are more then three
417 tx_amount = len(self.network.pending_transactions_for_notifications)
420 for tx in self.network.pending_transactions_for_notifications:
421 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
425 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
426 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
428 self.network.pending_transactions_for_notifications = []
430 for tx in self.network.pending_transactions_for_notifications:
432 self.network.pending_transactions_for_notifications.remove(tx)
433 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
435 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
437 def notify(self, message):
438 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
442 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
443 def getOpenFileName(self, title, filter = ""):
444 directory = self.config.get('io_dir', os.path.expanduser('~'))
445 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
446 if fileName and directory != os.path.dirname(fileName):
447 self.config.set_key('io_dir', os.path.dirname(fileName), True)
450 def getSaveFileName(self, title, filename, filter = ""):
451 directory = self.config.get('io_dir', os.path.expanduser('~'))
452 path = os.path.join( directory, filename )
453 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
454 if fileName and directory != os.path.dirname(fileName):
455 self.config.set_key('io_dir', os.path.dirname(fileName), True)
459 QMainWindow.close(self)
460 run_hook('close_main_window')
462 def connect_slots(self, sender):
463 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
464 self.previous_payto_e=''
466 def timer_actions(self):
467 if self.need_update.is_set():
469 self.need_update.clear()
470 run_hook('timer_actions')
472 def format_amount(self, x, is_diff=False, whitespaces=False):
473 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
475 def read_amount(self, x):
476 if x in['.', '']: return None
477 p = pow(10, self.decimal_point)
478 return int( p * Decimal(x) )
481 assert self.decimal_point in [5,8]
482 return "BTC" if self.decimal_point == 8 else "mBTC"
485 def update_status(self):
486 if self.network is None or not self.network.is_running():
488 icon = QIcon(":icons/status_disconnected.png")
490 elif self.network.is_connected():
491 if not self.wallet.up_to_date:
492 text = _("Synchronizing...")
493 icon = QIcon(":icons/status_waiting.png")
494 elif self.network.server_lag > 1:
495 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
496 icon = QIcon(":icons/status_lagging.png")
498 c, u = self.wallet.get_account_balance(self.current_account)
499 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
500 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
502 # append fiat balance and price from exchange rate plugin
504 run_hook('get_fiat_status_text', c+u, r)
509 self.tray.setToolTip(text)
510 icon = QIcon(":icons/status_connected.png")
512 text = _("Not connected")
513 icon = QIcon(":icons/status_disconnected.png")
515 self.balance_label.setText(text)
516 self.status_button.setIcon( icon )
519 def update_wallet(self):
521 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
522 self.update_history_tab()
523 self.update_receive_tab()
524 self.update_contacts_tab()
525 self.update_completions()
528 def create_history_tab(self):
529 self.history_list = l = MyTreeWidget(self)
531 for i,width in enumerate(self.column_widths['history']):
532 l.setColumnWidth(i, width)
533 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
534 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
535 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
537 l.customContextMenuRequested.connect(self.create_history_menu)
541 def create_history_menu(self, position):
542 self.history_list.selectedIndexes()
543 item = self.history_list.currentItem()
544 be = self.config.get('block_explorer', 'Blockchain.info')
545 if be == 'Blockchain.info':
546 block_explorer = 'https://blockchain.info/tx/'
547 elif be == 'Blockr.io':
548 block_explorer = 'https://blockr.io/tx/info/'
549 elif be == 'Insight.is':
550 block_explorer = 'http://live.insight.is/tx/'
552 tx_hash = str(item.data(0, Qt.UserRole).toString())
553 if not tx_hash: return
555 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
556 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
557 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
558 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
559 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
562 def show_transaction(self, tx):
563 import transaction_dialog
564 d = transaction_dialog.TxDialog(tx, self)
567 def tx_label_clicked(self, item, column):
568 if column==2 and item.isSelected():
570 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
571 self.history_list.editItem( item, column )
572 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
575 def tx_label_changed(self, item, column):
579 tx_hash = str(item.data(0, Qt.UserRole).toString())
580 tx = self.wallet.transactions.get(tx_hash)
581 text = unicode( item.text(2) )
582 self.wallet.set_label(tx_hash, text)
584 item.setForeground(2, QBrush(QColor('black')))
586 text = self.wallet.get_default_label(tx_hash)
587 item.setText(2, text)
588 item.setForeground(2, QBrush(QColor('gray')))
592 def edit_label(self, is_recv):
593 l = self.receive_list if is_recv else self.contacts_list
594 item = l.currentItem()
595 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
596 l.editItem( item, 1 )
597 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
601 def address_label_clicked(self, item, column, l, column_addr, column_label):
602 if column == column_label and item.isSelected():
603 is_editable = item.data(0, 32).toBool()
606 addr = unicode( item.text(column_addr) )
607 label = unicode( item.text(column_label) )
608 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
609 l.editItem( item, column )
610 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
613 def address_label_changed(self, item, column, l, column_addr, column_label):
614 if column == column_label:
615 addr = unicode( item.text(column_addr) )
616 text = unicode( item.text(column_label) )
617 is_editable = item.data(0, 32).toBool()
621 changed = self.wallet.set_label(addr, text)
623 self.update_history_tab()
624 self.update_completions()
626 self.current_item_changed(item)
628 run_hook('item_changed', item, column)
631 def current_item_changed(self, a):
632 run_hook('current_item_changed', a)
636 def update_history_tab(self):
638 self.history_list.clear()
639 for item in self.wallet.get_tx_history(self.current_account):
640 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
641 time_str = _("unknown")
644 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
646 time_str = _("error")
649 time_str = 'unverified'
650 icon = QIcon(":icons/unconfirmed.png")
653 icon = QIcon(":icons/unconfirmed.png")
655 icon = QIcon(":icons/clock%d.png"%conf)
657 icon = QIcon(":icons/confirmed.png")
659 if value is not None:
660 v_str = self.format_amount(value, True, whitespaces=True)
664 balance_str = self.format_amount(balance, whitespaces=True)
667 label, is_default_label = self.wallet.get_label(tx_hash)
669 label = _('Pruned transaction outputs')
670 is_default_label = False
672 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
673 item.setFont(2, QFont(MONOSPACE_FONT))
674 item.setFont(3, QFont(MONOSPACE_FONT))
675 item.setFont(4, QFont(MONOSPACE_FONT))
677 item.setForeground(3, QBrush(QColor("#BC1E1E")))
679 item.setData(0, Qt.UserRole, tx_hash)
680 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
682 item.setForeground(2, QBrush(QColor('grey')))
684 item.setIcon(0, icon)
685 self.history_list.insertTopLevelItem(0,item)
688 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
689 run_hook('history_tab_update')
692 def create_send_tab(self):
697 grid.setColumnMinimumWidth(3,300)
698 grid.setColumnStretch(5,1)
701 self.payto_e = QLineEdit()
702 grid.addWidget(QLabel(_('Pay to')), 1, 0)
703 grid.addWidget(self.payto_e, 1, 1, 1, 3)
705 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
707 completer = QCompleter()
708 completer.setCaseSensitivity(False)
709 self.payto_e.setCompleter(completer)
710 completer.setModel(self.completions)
712 self.message_e = QLineEdit()
713 grid.addWidget(QLabel(_('Description')), 2, 0)
714 grid.addWidget(self.message_e, 2, 1, 1, 3)
715 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
717 self.from_label = QLabel(_('From'))
718 grid.addWidget(self.from_label, 3, 0)
719 self.from_list = QTreeWidget(self)
720 self.from_list.setColumnCount(2)
721 self.from_list.setColumnWidth(0, 350)
722 self.from_list.setColumnWidth(1, 50)
723 self.from_list.setHeaderHidden (True)
724 self.from_list.setMaximumHeight(80)
725 grid.addWidget(self.from_list, 3, 1, 1, 3)
726 self.set_pay_from([])
728 self.amount_e = AmountEdit(self.base_unit)
729 grid.addWidget(QLabel(_('Amount')), 4, 0)
730 grid.addWidget(self.amount_e, 4, 1, 1, 2)
731 grid.addWidget(HelpButton(
732 _('Amount to be sent.') + '\n\n' \
733 + _('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.') \
734 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
736 self.fee_e = AmountEdit(self.base_unit)
737 grid.addWidget(QLabel(_('Fee')), 5, 0)
738 grid.addWidget(self.fee_e, 5, 1, 1, 2)
739 grid.addWidget(HelpButton(
740 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
741 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
742 + _('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)
744 run_hook('exchange_rate_button', grid)
746 self.send_button = EnterButton(_("Send"), self.do_send)
747 grid.addWidget(self.send_button, 6, 1)
749 b = EnterButton(_("Clear"),self.do_clear)
750 grid.addWidget(b, 6, 2)
752 self.payto_sig = QLabel('')
753 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
755 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
756 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
765 def entry_changed( is_fee ):
766 self.funds_error = False
768 if self.amount_e.is_shortcut:
769 self.amount_e.is_shortcut = False
770 sendable = self.get_sendable_balance()
771 # there is only one output because we are completely spending inputs
772 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
773 fee = self.wallet.estimated_fee(inputs, 1)
775 self.amount_e.setText( self.format_amount(amount) )
776 self.fee_e.setText( self.format_amount( fee ) )
779 amount = self.read_amount(str(self.amount_e.text()))
780 fee = self.read_amount(str(self.fee_e.text()))
782 if not is_fee: fee = None
785 # assume that there will be 2 outputs (one for change)
786 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
788 self.fee_e.setText( self.format_amount( fee ) )
791 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
795 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
796 self.funds_error = True
797 text = _( "Not enough funds" )
798 c, u = self.wallet.get_frozen_balance()
799 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
801 self.statusBar().showMessage(text)
802 self.amount_e.setPalette(palette)
803 self.fee_e.setPalette(palette)
805 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
806 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
808 run_hook('create_send_tab', grid)
812 def set_pay_from(self, l):
814 self.from_list.clear()
815 self.from_label.setHidden(len(self.pay_from) == 0)
816 self.from_list.setHidden(len(self.pay_from) == 0)
817 for addr in self.pay_from:
818 c, u = self.wallet.get_addr_balance(addr)
819 balance = self.format_amount(c + u)
820 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
823 def update_completions(self):
825 for addr,label in self.wallet.labels.items():
826 if addr in self.wallet.addressbook:
827 l.append( label + ' <' + addr + '>')
829 run_hook('update_completions', l)
830 self.completions.setStringList(l)
834 return lambda s, *args: s.do_protect(func, args)
839 label = unicode( self.message_e.text() )
840 r = unicode( self.payto_e.text() )
843 # label or alias, with address in brackets
844 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
845 to_address = m.group(2) if m else r
847 if not is_valid(to_address):
848 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
852 amount = self.read_amount(unicode( self.amount_e.text()))
854 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
857 fee = self.read_amount(unicode( self.fee_e.text()))
859 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
862 confirm_amount = self.config.get('confirm_amount', 100000000)
863 if amount >= confirm_amount:
864 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
867 confirm_fee = self.config.get('confirm_fee', 100000)
868 if fee >= confirm_fee:
869 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()}):
872 self.send_tx(to_address, amount, fee, label)
875 def waiting_dialog(self, message):
877 d.setWindowTitle('Please wait')
879 vbox = QVBoxLayout(d)
886 def send_tx(self, to_address, amount, fee, label, password):
888 # first, create an unsigned tx
889 domain = self.get_payment_sources()
890 outputs = [(to_address, amount)]
892 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
894 except Exception as e:
895 traceback.print_exc(file=sys.stdout)
896 self.show_message(str(e))
899 # call hook to see if plugin needs gui interaction
900 run_hook('send_tx', tx)
906 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
907 self.wallet.sign_transaction(tx, keypairs, password)
908 self.signed_tx_data = (tx, fee, label)
909 self.emit(SIGNAL('send_tx2'))
910 self.tx_wait_dialog = self.waiting_dialog('Signing..')
911 threading.Thread(target=sign_thread).start()
913 # add recipient to addressbook
914 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
915 self.wallet.addressbook.append(to_address)
919 tx, fee, label = self.signed_tx_data
920 self.tx_wait_dialog.accept()
923 self.show_message(tx.error)
926 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
927 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
931 self.wallet.set_label(tx.hash(), label)
933 if not tx.is_complete():
934 self.show_transaction(tx)
938 def broadcast_thread():
939 self.tx_broadcast_result = self.wallet.sendtx(tx)
940 self.emit(SIGNAL('send_tx3'))
941 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
942 threading.Thread(target=broadcast_thread).start()
946 self.tx_broadcast_dialog.accept()
947 status, msg = self.tx_broadcast_result
949 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
951 self.update_contacts_tab()
953 QMessageBox.warning(self, _('Error'), msg, _('OK'))
960 def set_url(self, url):
962 address, amount, label, message, signature, identity, url = util.parse_url(url)
964 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URL'), _('OK'))
968 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
969 elif amount: amount = str(Decimal(amount))
972 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
975 self.mini.set_payment_fields(address, amount)
977 if label and self.wallet.labels.get(address) != label:
978 if self.question('Give label "%s" to address %s ?'%(label,address)):
979 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
980 self.wallet.addressbook.append(address)
981 self.wallet.set_label(address, label)
983 run_hook('set_url', url, self.show_message, self.question)
985 self.tabs.setCurrentIndex(1)
986 label = self.wallet.labels.get(address)
987 m_addr = label + ' <'+ address +'>' if label else address
988 self.payto_e.setText(m_addr)
990 self.message_e.setText(message)
992 self.amount_e.setText(amount)
995 self.set_frozen(self.payto_e,True)
996 self.set_frozen(self.amount_e,True)
997 self.set_frozen(self.message_e,True)
998 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
1000 self.payto_sig.setVisible(False)
1003 self.payto_sig.setVisible(False)
1004 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1006 self.set_frozen(e,False)
1008 self.set_pay_from([])
1009 self.update_status()
1011 def set_frozen(self,entry,frozen):
1013 entry.setReadOnly(True)
1014 entry.setFrame(False)
1015 palette = QPalette()
1016 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1017 entry.setPalette(palette)
1019 entry.setReadOnly(False)
1020 entry.setFrame(True)
1021 palette = QPalette()
1022 palette.setColor(entry.backgroundRole(), QColor('white'))
1023 entry.setPalette(palette)
1026 def set_addrs_frozen(self,addrs,freeze):
1028 if not addr: continue
1029 if addr in self.wallet.frozen_addresses and not freeze:
1030 self.wallet.unfreeze(addr)
1031 elif addr not in self.wallet.frozen_addresses and freeze:
1032 self.wallet.freeze(addr)
1033 self.update_receive_tab()
1037 def create_list_tab(self, headers):
1038 "generic tab creation method"
1039 l = MyTreeWidget(self)
1040 l.setColumnCount( len(headers) )
1041 l.setHeaderLabels( headers )
1044 vbox = QVBoxLayout()
1051 vbox.addWidget(buttons)
1053 hbox = QHBoxLayout()
1056 buttons.setLayout(hbox)
1061 def create_receive_tab(self):
1062 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1063 l.setContextMenuPolicy(Qt.CustomContextMenu)
1064 l.customContextMenuRequested.connect(self.create_receive_menu)
1065 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1066 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1067 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1068 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1069 self.receive_list = l
1070 self.receive_buttons_hbox = hbox
1077 def save_column_widths(self):
1078 self.column_widths["receive"] = []
1079 for i in range(self.receive_list.columnCount() -1):
1080 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1082 self.column_widths["history"] = []
1083 for i in range(self.history_list.columnCount() - 1):
1084 self.column_widths["history"].append(self.history_list.columnWidth(i))
1086 self.column_widths["contacts"] = []
1087 for i in range(self.contacts_list.columnCount() - 1):
1088 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1090 self.config.set_key("column_widths_2", self.column_widths, True)
1093 def create_contacts_tab(self):
1094 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1095 l.setContextMenuPolicy(Qt.CustomContextMenu)
1096 l.customContextMenuRequested.connect(self.create_contact_menu)
1097 for i,width in enumerate(self.column_widths['contacts']):
1098 l.setColumnWidth(i, width)
1100 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1101 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1102 self.contacts_list = l
1103 self.contacts_buttons_hbox = hbox
1108 def delete_imported_key(self, addr):
1109 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1110 self.wallet.delete_imported_key(addr)
1111 self.update_receive_tab()
1112 self.update_history_tab()
1114 def edit_account_label(self, k):
1115 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1117 label = unicode(text)
1118 self.wallet.set_label(k,label)
1119 self.update_receive_tab()
1121 def account_set_expanded(self, item, k, b):
1123 self.accounts_expanded[k] = b
1125 def create_account_menu(self, position, k, item):
1127 if item.isExpanded():
1128 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1130 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1131 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1132 if self.wallet.seed_version > 4:
1133 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1134 if self.wallet.account_is_pending(k):
1135 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1136 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1138 def delete_pending_account(self, k):
1139 self.wallet.delete_pending_account(k)
1140 self.update_receive_tab()
1142 def create_receive_menu(self, position):
1143 # fixme: this function apparently has a side effect.
1144 # if it is not called the menu pops up several times
1145 #self.receive_list.selectedIndexes()
1147 selected = self.receive_list.selectedItems()
1148 multi_select = len(selected) > 1
1149 addrs = [unicode(item.text(0)) for item in selected]
1150 if not multi_select:
1151 item = self.receive_list.itemAt(position)
1155 if not is_valid(addr):
1156 k = str(item.data(0,32).toString())
1158 self.create_account_menu(position, k, item)
1160 item.setExpanded(not item.isExpanded())
1164 if not multi_select:
1165 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1166 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1167 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1168 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1169 if self.wallet.seed:
1170 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1171 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1172 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1173 if addr in self.wallet.imported_keys:
1174 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1176 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1177 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1178 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1179 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1181 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1182 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1184 run_hook('receive_menu', menu, addrs)
1185 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1188 def get_sendable_balance(self):
1189 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1192 def get_payment_sources(self):
1194 return self.pay_from
1196 return self.wallet.get_account_addresses(self.current_account)
1199 def send_from_addresses(self, addrs):
1200 self.set_pay_from( addrs )
1201 self.tabs.setCurrentIndex(1)
1204 def payto(self, addr):
1206 label = self.wallet.labels.get(addr)
1207 m_addr = label + ' <' + addr + '>' if label else addr
1208 self.tabs.setCurrentIndex(1)
1209 self.payto_e.setText(m_addr)
1210 self.amount_e.setFocus()
1213 def delete_contact(self, x):
1214 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1215 self.wallet.delete_contact(x)
1216 self.wallet.set_label(x, None)
1217 self.update_history_tab()
1218 self.update_contacts_tab()
1219 self.update_completions()
1222 def create_contact_menu(self, position):
1223 item = self.contacts_list.itemAt(position)
1226 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1228 addr = unicode(item.text(0))
1229 label = unicode(item.text(1))
1230 is_editable = item.data(0,32).toBool()
1231 payto_addr = item.data(0,33).toString()
1232 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1233 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1234 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1236 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1237 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1239 run_hook('create_contact_menu', menu, item)
1240 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1243 def update_receive_item(self, item):
1244 item.setFont(0, QFont(MONOSPACE_FONT))
1245 address = str(item.data(0,0).toString())
1246 label = self.wallet.labels.get(address,'')
1247 item.setData(1,0,label)
1248 item.setData(0,32, True) # is editable
1250 run_hook('update_receive_item', address, item)
1252 if not self.wallet.is_mine(address): return
1254 c, u = self.wallet.get_addr_balance(address)
1255 balance = self.format_amount(c + u)
1256 item.setData(2,0,balance)
1258 if address in self.wallet.frozen_addresses:
1259 item.setBackgroundColor(0, QColor('lightblue'))
1262 def update_receive_tab(self):
1263 l = self.receive_list
1266 l.setColumnHidden(2, False)
1267 l.setColumnHidden(3, False)
1268 for i,width in enumerate(self.column_widths['receive']):
1269 l.setColumnWidth(i, width)
1271 if self.current_account is None:
1272 account_items = self.wallet.accounts.items()
1273 elif self.current_account != -1:
1274 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1278 for k, account in account_items:
1279 name = self.wallet.get_account_name(k)
1280 c,u = self.wallet.get_account_balance(k)
1281 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1282 l.addTopLevelItem(account_item)
1283 account_item.setExpanded(self.accounts_expanded.get(k, True))
1284 account_item.setData(0, 32, k)
1286 if not self.wallet.is_seeded(k):
1287 icon = QIcon(":icons/key.png")
1288 account_item.setIcon(0, icon)
1290 for is_change in ([0,1]):
1291 name = _("Receiving") if not is_change else _("Change")
1292 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1293 account_item.addChild(seq_item)
1294 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1296 if not is_change: seq_item.setExpanded(True)
1301 for address in account.get_addresses(is_change):
1302 h = self.wallet.history.get(address,[])
1306 if gap > self.wallet.gap_limit:
1311 c, u = self.wallet.get_addr_balance(address)
1312 num_tx = '*' if h == ['*'] else "%d"%len(h)
1313 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1314 self.update_receive_item(item)
1316 item.setBackgroundColor(1, QColor('red'))
1317 if len(h) > 0 and c == -u:
1319 seq_item.insertChild(0,used_item)
1321 used_item.addChild(item)
1323 seq_item.addChild(item)
1326 for k, addr in self.wallet.get_pending_accounts():
1327 name = self.wallet.labels.get(k,'')
1328 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1329 self.update_receive_item(item)
1330 l.addTopLevelItem(account_item)
1331 account_item.setExpanded(True)
1332 account_item.setData(0, 32, k)
1333 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1334 account_item.addChild(item)
1335 self.update_receive_item(item)
1338 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1339 c,u = self.wallet.get_imported_balance()
1340 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1341 l.addTopLevelItem(account_item)
1342 account_item.setExpanded(True)
1343 for address in self.wallet.imported_keys.keys():
1344 item = QTreeWidgetItem( [ address, '', '', ''] )
1345 self.update_receive_item(item)
1346 account_item.addChild(item)
1349 # we use column 1 because column 0 may be hidden
1350 l.setCurrentItem(l.topLevelItem(0),1)
1353 def update_contacts_tab(self):
1354 l = self.contacts_list
1357 for address in self.wallet.addressbook:
1358 label = self.wallet.labels.get(address,'')
1359 n = self.wallet.get_num_tx(address)
1360 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1361 item.setFont(0, QFont(MONOSPACE_FONT))
1362 # 32 = label can be edited (bool)
1363 item.setData(0,32, True)
1365 item.setData(0,33, address)
1366 l.addTopLevelItem(item)
1368 run_hook('update_contacts_tab', l)
1369 l.setCurrentItem(l.topLevelItem(0))
1373 def create_console_tab(self):
1374 from console import Console
1375 self.console = console = Console()
1379 def update_console(self):
1380 console = self.console
1381 console.history = self.config.get("console-history",[])
1382 console.history_index = len(console.history)
1384 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1385 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1387 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1389 def mkfunc(f, method):
1390 return lambda *args: apply( f, (method, args, self.password_dialog ))
1392 if m[0]=='_' or m in ['network','wallet']: continue
1393 methods[m] = mkfunc(c._run, m)
1395 console.updateNamespace(methods)
1398 def change_account(self,s):
1399 if s == _("All accounts"):
1400 self.current_account = None
1402 accounts = self.wallet.get_account_names()
1403 for k, v in accounts.items():
1405 self.current_account = k
1406 self.update_history_tab()
1407 self.update_status()
1408 self.update_receive_tab()
1410 def create_status_bar(self):
1413 sb.setFixedHeight(35)
1414 qtVersion = qVersion()
1416 self.balance_label = QLabel("")
1417 sb.addWidget(self.balance_label)
1419 from version_getter import UpdateLabel
1420 self.updatelabel = UpdateLabel(self.config, sb)
1422 self.account_selector = QComboBox()
1423 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1424 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1425 sb.addPermanentWidget(self.account_selector)
1427 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1428 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1430 self.lock_icon = QIcon()
1431 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1432 sb.addPermanentWidget( self.password_button )
1434 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1435 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1436 sb.addPermanentWidget( self.seed_button )
1437 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1438 sb.addPermanentWidget( self.status_button )
1440 run_hook('create_status_bar', (sb,))
1442 self.setStatusBar(sb)
1445 def update_lock_icon(self):
1446 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1447 self.password_button.setIcon( icon )
1450 def update_buttons_on_seed(self):
1451 if not self.wallet.is_watching_only():
1452 self.seed_button.show()
1453 self.password_button.show()
1454 self.send_button.setText(_("Send"))
1456 self.password_button.hide()
1457 self.seed_button.hide()
1458 self.send_button.setText(_("Create unsigned transaction"))
1461 def change_password_dialog(self):
1462 from password_dialog import PasswordDialog
1463 d = PasswordDialog(self.wallet, self)
1465 self.update_lock_icon()
1468 def new_contact_dialog(self):
1471 d.setWindowTitle(_("New Contact"))
1472 vbox = QVBoxLayout(d)
1473 vbox.addWidget(QLabel(_('New Contact')+':'))
1475 grid = QGridLayout()
1478 grid.addWidget(QLabel(_("Address")), 1, 0)
1479 grid.addWidget(line1, 1, 1)
1480 grid.addWidget(QLabel(_("Name")), 2, 0)
1481 grid.addWidget(line2, 2, 1)
1483 vbox.addLayout(grid)
1484 vbox.addLayout(ok_cancel_buttons(d))
1489 address = str(line1.text())
1490 label = unicode(line2.text())
1492 if not is_valid(address):
1493 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1496 self.wallet.add_contact(address)
1498 self.wallet.set_label(address, label)
1500 self.update_contacts_tab()
1501 self.update_history_tab()
1502 self.update_completions()
1503 self.tabs.setCurrentIndex(3)
1507 def new_account_dialog(self, password):
1509 dialog = QDialog(self)
1511 dialog.setWindowTitle(_("New Account"))
1513 vbox = QVBoxLayout()
1514 vbox.addWidget(QLabel(_('Account name')+':'))
1517 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1518 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1523 vbox.addLayout(ok_cancel_buttons(dialog))
1524 dialog.setLayout(vbox)
1528 name = str(e.text())
1531 self.wallet.create_pending_account('1of1', name, password)
1532 self.update_receive_tab()
1533 self.tabs.setCurrentIndex(2)
1538 def show_master_public_keys(self):
1540 dialog = QDialog(self)
1542 dialog.setWindowTitle(_("Master Public Keys"))
1544 main_layout = QGridLayout()
1545 mpk_dict = self.wallet.get_master_public_keys()
1547 for key, value in mpk_dict.items():
1548 main_layout.addWidget(QLabel(key), i, 0)
1549 mpk_text = QTextEdit()
1550 mpk_text.setReadOnly(True)
1551 mpk_text.setMaximumHeight(170)
1552 mpk_text.setText(value)
1553 main_layout.addWidget(mpk_text, i + 1, 0)
1556 vbox = QVBoxLayout()
1557 vbox.addLayout(main_layout)
1558 vbox.addLayout(close_button(dialog))
1560 dialog.setLayout(vbox)
1565 def show_seed_dialog(self, password):
1566 if self.wallet.is_watching_only():
1567 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1570 if self.wallet.seed:
1572 mnemonic = self.wallet.get_mnemonic(password)
1574 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1576 from seed_dialog import SeedDialog
1577 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1581 for k in self.wallet.master_private_keys.keys():
1582 pk = self.wallet.get_master_private_key(k, password)
1584 from seed_dialog import PrivateKeysDialog
1585 d = PrivateKeysDialog(self,l)
1592 def show_qrcode(self, data, title = _("QR code")):
1596 d.setWindowTitle(title)
1597 d.setMinimumSize(270, 300)
1598 vbox = QVBoxLayout()
1599 qrw = QRCodeWidget(data)
1600 vbox.addWidget(qrw, 1)
1601 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1602 hbox = QHBoxLayout()
1605 filename = os.path.join(self.config.path, "qrcode.bmp")
1608 bmp.save_qrcode(qrw.qr, filename)
1609 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1611 def copy_to_clipboard():
1612 bmp.save_qrcode(qrw.qr, filename)
1613 self.app.clipboard().setImage(QImage(filename))
1614 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1616 b = QPushButton(_("Copy"))
1618 b.clicked.connect(copy_to_clipboard)
1620 b = QPushButton(_("Save"))
1622 b.clicked.connect(print_qr)
1624 b = QPushButton(_("Close"))
1626 b.clicked.connect(d.accept)
1629 vbox.addLayout(hbox)
1634 def do_protect(self, func, args):
1635 if self.wallet.use_encryption:
1636 password = self.password_dialog()
1642 if args != (False,):
1643 args = (self,) + args + (password,)
1645 args = (self,password)
1649 def show_public_keys(self, address):
1650 if not address: return
1652 pubkey_list = self.wallet.get_public_keys(address)
1653 except Exception as e:
1654 traceback.print_exc(file=sys.stdout)
1655 self.show_message(str(e))
1659 d.setMinimumSize(600, 200)
1661 vbox = QVBoxLayout()
1662 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1663 vbox.addWidget( QLabel(_("Public key") + ':'))
1665 keys.setReadOnly(True)
1666 keys.setText('\n'.join(pubkey_list))
1667 vbox.addWidget(keys)
1668 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1669 vbox.addLayout(close_button(d))
1674 def show_private_key(self, address, password):
1675 if not address: return
1677 pk_list = self.wallet.get_private_key(address, password)
1678 except Exception as e:
1679 traceback.print_exc(file=sys.stdout)
1680 self.show_message(str(e))
1684 d.setMinimumSize(600, 200)
1686 vbox = QVBoxLayout()
1687 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1688 vbox.addWidget( QLabel(_("Private key") + ':'))
1690 keys.setReadOnly(True)
1691 keys.setText('\n'.join(pk_list))
1692 vbox.addWidget(keys)
1693 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1694 vbox.addLayout(close_button(d))
1700 def do_sign(self, address, message, signature, password):
1701 message = unicode(message.toPlainText())
1702 message = message.encode('utf-8')
1704 sig = self.wallet.sign_message(str(address.text()), message, password)
1705 signature.setText(sig)
1706 except Exception as e:
1707 self.show_message(str(e))
1709 def do_verify(self, address, message, signature):
1710 message = unicode(message.toPlainText())
1711 message = message.encode('utf-8')
1712 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1713 self.show_message(_("Signature verified"))
1715 self.show_message(_("Error: wrong signature"))
1718 def sign_verify_message(self, address=''):
1721 d.setWindowTitle(_('Sign/verify Message'))
1722 d.setMinimumSize(410, 290)
1724 layout = QGridLayout(d)
1726 message_e = QTextEdit()
1727 layout.addWidget(QLabel(_('Message')), 1, 0)
1728 layout.addWidget(message_e, 1, 1)
1729 layout.setRowStretch(2,3)
1731 address_e = QLineEdit()
1732 address_e.setText(address)
1733 layout.addWidget(QLabel(_('Address')), 2, 0)
1734 layout.addWidget(address_e, 2, 1)
1736 signature_e = QTextEdit()
1737 layout.addWidget(QLabel(_('Signature')), 3, 0)
1738 layout.addWidget(signature_e, 3, 1)
1739 layout.setRowStretch(3,1)
1741 hbox = QHBoxLayout()
1743 b = QPushButton(_("Sign"))
1744 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1747 b = QPushButton(_("Verify"))
1748 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1751 b = QPushButton(_("Close"))
1752 b.clicked.connect(d.accept)
1754 layout.addLayout(hbox, 4, 1)
1759 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1761 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1762 message_e.setText(decrypted)
1763 except Exception as e:
1764 self.show_message(str(e))
1767 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1768 message = unicode(message_e.toPlainText())
1769 message = message.encode('utf-8')
1771 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1772 encrypted_e.setText(encrypted)
1773 except Exception as e:
1774 self.show_message(str(e))
1778 def encrypt_message(self, address = ''):
1781 d.setWindowTitle(_('Encrypt/decrypt Message'))
1782 d.setMinimumSize(610, 490)
1784 layout = QGridLayout(d)
1786 message_e = QTextEdit()
1787 layout.addWidget(QLabel(_('Message')), 1, 0)
1788 layout.addWidget(message_e, 1, 1)
1789 layout.setRowStretch(2,3)
1791 pubkey_e = QLineEdit()
1793 pubkey = self.wallet.getpubkeys(address)[0]
1794 pubkey_e.setText(pubkey)
1795 layout.addWidget(QLabel(_('Public key')), 2, 0)
1796 layout.addWidget(pubkey_e, 2, 1)
1798 encrypted_e = QTextEdit()
1799 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1800 layout.addWidget(encrypted_e, 3, 1)
1801 layout.setRowStretch(3,1)
1803 hbox = QHBoxLayout()
1804 b = QPushButton(_("Encrypt"))
1805 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1808 b = QPushButton(_("Decrypt"))
1809 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1812 b = QPushButton(_("Close"))
1813 b.clicked.connect(d.accept)
1816 layout.addLayout(hbox, 4, 1)
1820 def question(self, msg):
1821 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1823 def show_message(self, msg):
1824 QMessageBox.information(self, _('Message'), msg, _('OK'))
1826 def password_dialog(self ):
1829 d.setWindowTitle(_("Enter Password"))
1834 vbox = QVBoxLayout()
1835 msg = _('Please enter your password')
1836 vbox.addWidget(QLabel(msg))
1838 grid = QGridLayout()
1840 grid.addWidget(QLabel(_('Password')), 1, 0)
1841 grid.addWidget(pw, 1, 1)
1842 vbox.addLayout(grid)
1844 vbox.addLayout(ok_cancel_buttons(d))
1847 run_hook('password_dialog', pw, grid, 1)
1848 if not d.exec_(): return
1849 return unicode(pw.text())
1858 def tx_from_text(self, txt):
1859 "json or raw hexadecimal"
1862 tx = Transaction(txt)
1868 tx_dict = json.loads(str(txt))
1869 assert "hex" in tx_dict.keys()
1870 assert "complete" in tx_dict.keys()
1871 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1872 if not tx_dict["complete"]:
1873 assert "input_info" in tx_dict.keys()
1874 input_info = json.loads(tx_dict['input_info'])
1875 tx.add_input_info(input_info)
1880 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1884 def read_tx_from_file(self):
1885 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1889 with open(fileName, "r") as f:
1890 file_content = f.read()
1891 except (ValueError, IOError, os.error), reason:
1892 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1894 return self.tx_from_text(file_content)
1898 def sign_raw_transaction(self, tx, input_info, password):
1899 self.wallet.signrawtransaction(tx, input_info, [], password)
1901 def do_process_from_text(self):
1902 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1905 tx = self.tx_from_text(text)
1907 self.show_transaction(tx)
1909 def do_process_from_file(self):
1910 tx = self.read_tx_from_file()
1912 self.show_transaction(tx)
1914 def do_process_from_txid(self):
1915 from electrum import transaction
1916 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1918 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1920 tx = transaction.Transaction(r)
1922 self.show_transaction(tx)
1924 self.show_message("unknown transaction")
1926 def do_process_from_csvReader(self, csvReader):
1931 for position, row in enumerate(csvReader):
1933 if not is_valid(address):
1934 errors.append((position, address))
1936 amount = Decimal(row[1])
1937 amount = int(100000000*amount)
1938 outputs.append((address, amount))
1939 except (ValueError, IOError, os.error), reason:
1940 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1944 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1945 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1949 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1950 except Exception as e:
1951 self.show_message(str(e))
1954 self.show_transaction(tx)
1956 def do_process_from_csv_file(self):
1957 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1961 with open(fileName, "r") as f:
1962 csvReader = csv.reader(f)
1963 self.do_process_from_csvReader(csvReader)
1964 except (ValueError, IOError, os.error), reason:
1965 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1968 def do_process_from_csv_text(self):
1969 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1970 + _("Format: address, amount. One output per line"), _("Load CSV"))
1973 f = StringIO.StringIO(text)
1974 csvReader = csv.reader(f)
1975 self.do_process_from_csvReader(csvReader)
1980 def do_export_privkeys(self, password):
1981 if not self.wallet.seed:
1982 self.show_message(_("This wallet has no seed"))
1985 self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
1988 select_export = _('Select file to export your private keys to')
1989 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1991 with open(fileName, "w+") as csvfile:
1992 transaction = csv.writer(csvfile)
1993 transaction.writerow(["address", "private_key"])
1995 addresses = self.wallet.addresses(True)
1997 for addr in addresses:
1998 pk = "".join(self.wallet.get_private_key(addr, password))
1999 transaction.writerow(["%34s"%addr,pk])
2001 self.show_message(_("Private keys exported."))
2003 except (IOError, os.error), reason:
2004 export_error_label = _("Electrum was unable to produce a private key-export.")
2005 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2007 except Exception as e:
2008 self.show_message(str(e))
2012 def do_import_labels(self):
2013 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2014 if not labelsFile: return
2016 f = open(labelsFile, 'r')
2019 for key, value in json.loads(data).items():
2020 self.wallet.set_label(key, value)
2021 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2022 except (IOError, os.error), reason:
2023 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2026 def do_export_labels(self):
2027 labels = self.wallet.labels
2029 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2031 with open(fileName, 'w+') as f:
2032 json.dump(labels, f)
2033 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2034 except (IOError, os.error), reason:
2035 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2038 def do_export_history(self):
2039 wallet = self.wallet
2040 select_export = _('Select file to export your wallet transactions to')
2041 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
2046 with open(fileName, "w+") as csvfile:
2047 transaction = csv.writer(csvfile)
2048 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2049 for item in wallet.get_tx_history():
2050 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2052 if timestamp is not None:
2054 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2055 except [RuntimeError, TypeError, NameError] as reason:
2056 time_string = "unknown"
2059 time_string = "unknown"
2061 time_string = "pending"
2063 if value is not None:
2064 value_string = format_satoshis(value, True)
2069 fee_string = format_satoshis(fee, True)
2074 label, is_default_label = wallet.get_label(tx_hash)
2075 label = label.encode('utf-8')
2079 balance_string = format_satoshis(balance, False)
2080 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2081 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
2083 except (IOError, os.error), reason:
2084 export_error_label = _("Electrum was unable to produce a transaction export.")
2085 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
2090 def do_import_privkey(self, password):
2091 if not self.wallet.imported_keys:
2092 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2093 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2094 + _('Are you sure you understand what you are doing?'), 3, 4)
2097 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2100 text = str(text).split()
2105 addr = self.wallet.import_key(key, password)
2106 except Exception as e:
2112 addrlist.append(addr)
2114 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2116 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2117 self.update_receive_tab()
2118 self.update_history_tab()
2121 def settings_dialog(self):
2123 d.setWindowTitle(_('Electrum Settings'))
2125 vbox = QVBoxLayout()
2126 grid = QGridLayout()
2127 grid.setColumnStretch(0,1)
2129 nz_label = QLabel(_('Display zeros') + ':')
2130 grid.addWidget(nz_label, 0, 0)
2131 nz_e = AmountEdit(None,True)
2132 nz_e.setText("%d"% self.num_zeros)
2133 grid.addWidget(nz_e, 0, 1)
2134 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2135 grid.addWidget(HelpButton(msg), 0, 2)
2136 if not self.config.is_modifiable('num_zeros'):
2137 for w in [nz_e, nz_label]: w.setEnabled(False)
2139 lang_label=QLabel(_('Language') + ':')
2140 grid.addWidget(lang_label, 1, 0)
2141 lang_combo = QComboBox()
2142 from electrum.i18n import languages
2143 lang_combo.addItems(languages.values())
2145 index = languages.keys().index(self.config.get("language",''))
2148 lang_combo.setCurrentIndex(index)
2149 grid.addWidget(lang_combo, 1, 1)
2150 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2151 if not self.config.is_modifiable('language'):
2152 for w in [lang_combo, lang_label]: w.setEnabled(False)
2155 fee_label = QLabel(_('Transaction fee') + ':')
2156 grid.addWidget(fee_label, 2, 0)
2157 fee_e = AmountEdit(self.base_unit)
2158 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2159 grid.addWidget(fee_e, 2, 1)
2160 msg = _('Fee per kilobyte of transaction.') + ' ' \
2161 + _('Recommended value') + ': ' + self.format_amount(20000)
2162 grid.addWidget(HelpButton(msg), 2, 2)
2163 if not self.config.is_modifiable('fee_per_kb'):
2164 for w in [fee_e, fee_label]: w.setEnabled(False)
2166 units = ['BTC', 'mBTC']
2167 unit_label = QLabel(_('Base unit') + ':')
2168 grid.addWidget(unit_label, 3, 0)
2169 unit_combo = QComboBox()
2170 unit_combo.addItems(units)
2171 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2172 grid.addWidget(unit_combo, 3, 1)
2173 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2174 + '\n1BTC=1000mBTC.\n' \
2175 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2177 usechange_cb = QCheckBox(_('Use change addresses'))
2178 usechange_cb.setChecked(self.wallet.use_change)
2179 grid.addWidget(usechange_cb, 4, 0)
2180 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2181 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2183 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2184 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2185 grid.addWidget(block_ex_label, 5, 0)
2186 block_ex_combo = QComboBox()
2187 block_ex_combo.addItems(block_explorers)
2188 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2189 grid.addWidget(block_ex_combo, 5, 1)
2190 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2192 grid.setRowStretch(6,1)
2194 vbox.addLayout(grid)
2195 vbox.addLayout(ok_cancel_buttons(d))
2199 if not d.exec_(): return
2201 fee = unicode(fee_e.text())
2203 fee = self.read_amount(fee)
2205 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2208 self.wallet.set_fee(fee)
2210 nz = unicode(nz_e.text())
2215 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2218 if self.num_zeros != nz:
2220 self.config.set_key('num_zeros', nz, True)
2221 self.update_history_tab()
2222 self.update_receive_tab()
2224 usechange_result = usechange_cb.isChecked()
2225 if self.wallet.use_change != usechange_result:
2226 self.wallet.use_change = usechange_result
2227 self.wallet.storage.put('use_change', self.wallet.use_change)
2229 unit_result = units[unit_combo.currentIndex()]
2230 if self.base_unit() != unit_result:
2231 self.decimal_point = 8 if unit_result == 'BTC' else 5
2232 self.config.set_key('decimal_point', self.decimal_point, True)
2233 self.update_history_tab()
2234 self.update_status()
2236 need_restart = False
2238 lang_request = languages.keys()[lang_combo.currentIndex()]
2239 if lang_request != self.config.get('language'):
2240 self.config.set_key("language", lang_request, True)
2243 be_result = block_explorers[block_ex_combo.currentIndex()]
2244 self.config.set_key('block_explorer', be_result, True)
2246 run_hook('close_settings_dialog')
2249 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2252 def run_network_dialog(self):
2253 if not self.network:
2255 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2257 def closeEvent(self, event):
2260 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2261 self.save_column_widths()
2262 self.config.set_key("console-history", self.console.history[-50:], True)
2263 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2267 def plugins_dialog(self):
2268 from electrum.plugins import plugins
2271 d.setWindowTitle(_('Electrum Plugins'))
2274 vbox = QVBoxLayout(d)
2277 scroll = QScrollArea()
2278 scroll.setEnabled(True)
2279 scroll.setWidgetResizable(True)
2280 scroll.setMinimumSize(400,250)
2281 vbox.addWidget(scroll)
2285 w.setMinimumHeight(len(plugins)*35)
2287 grid = QGridLayout()
2288 grid.setColumnStretch(0,1)
2291 def do_toggle(cb, p, w):
2294 if w: w.setEnabled(r)
2296 def mk_toggle(cb, p, w):
2297 return lambda: do_toggle(cb,p,w)
2299 for i, p in enumerate(plugins):
2301 cb = QCheckBox(p.fullname())
2302 cb.setDisabled(not p.is_available())
2303 cb.setChecked(p.is_enabled())
2304 grid.addWidget(cb, i, 0)
2305 if p.requires_settings():
2306 w = p.settings_widget(self)
2307 w.setEnabled( p.is_enabled() )
2308 grid.addWidget(w, i, 1)
2311 cb.clicked.connect(mk_toggle(cb,p,w))
2312 grid.addWidget(HelpButton(p.description()), i, 2)
2314 print_msg(_("Error: cannot display plugin"), p)
2315 traceback.print_exc(file=sys.stdout)
2316 grid.setRowStretch(i+1,1)
2318 vbox.addLayout(close_button(d))
2323 def show_account_details(self, k):
2324 account = self.wallet.accounts[k]
2327 d.setWindowTitle(_('Account Details'))
2330 vbox = QVBoxLayout(d)
2331 name = self.wallet.get_account_name(k)
2332 label = QLabel('Name: ' + name)
2333 vbox.addWidget(label)
2335 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2337 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2339 vbox.addWidget(QLabel(_('Master Public Key:')))
2342 text.setReadOnly(True)
2343 text.setMaximumHeight(170)
2344 vbox.addWidget(text)
2346 mpk_text = '\n'.join( account.get_master_pubkeys() )
2347 text.setText(mpk_text)
2349 vbox.addLayout(close_button(d))