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):
102 def build_menu(self):
104 m.addAction(_("Show/Hide"), self.show_or_hide)
106 m.addAction(_("Exit Electrum"), self.close)
107 self.tray.setContextMenu(m)
109 def show_or_hide(self):
110 self.tray_activated(QSystemTrayIcon.DoubleClick)
112 def tray_activated(self, reason):
113 if reason == QSystemTrayIcon.DoubleClick:
114 if self.isMinimized() or self.isHidden():
119 def __init__(self, config, network):
120 QMainWindow.__init__(self)
123 self.network = network
125 self._close_electrum = False
128 if sys.platform == 'darwin':
129 self.icon = QIcon(":icons/electrum_dark_icon.png")
130 #self.icon = QIcon(":icons/lock.png")
132 self.icon = QIcon(':icons/electrum_light_icon.png')
134 self.tray = QSystemTrayIcon(self.icon, self)
135 self.tray.setToolTip('Electrum')
136 self.tray.activated.connect(self.tray_activated)
140 self.create_status_bar()
142 self.need_update = threading.Event()
144 self.decimal_point = config.get('decimal_point', 8)
145 self.num_zeros = int(config.get('num_zeros',0))
147 set_language(config.get('language'))
149 self.funds_error = False
150 self.completions = QStringListModel()
152 self.tabs = tabs = QTabWidget(self)
153 self.column_widths = self.config.get("column_widths_2", default_column_widths )
154 tabs.addTab(self.create_history_tab(), _('History') )
155 tabs.addTab(self.create_send_tab(), _('Send') )
156 tabs.addTab(self.create_receive_tab(), _('Receive') )
157 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
158 tabs.addTab(self.create_console_tab(), _('Console') )
159 tabs.setMinimumSize(600, 400)
160 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
161 self.setCentralWidget(tabs)
163 g = self.config.get("winpos-qt",[100, 100, 840, 400])
164 self.setGeometry(g[0], g[1], g[2], g[3])
166 self.setWindowIcon(QIcon(":icons/electrum.png"))
169 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
170 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
171 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
172 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
173 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
175 for i in range(tabs.count()):
176 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
178 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
179 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
180 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
182 self.history_list.setFocus(True)
186 self.network.register_callback('updated', lambda: self.need_update.set())
187 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
188 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
189 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
190 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
192 # set initial message
193 self.console.showMessage(self.network.banner)
200 self.config.set_key('lite_mode', False, True)
205 self.config.set_key('lite_mode', True, True)
212 if not self.check_qt_version():
213 if self.config.get('lite_mode') is True:
214 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
215 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
216 self.config.set_key('lite_mode', False, True)
222 actuator = lite_window.MiniActuator(self)
224 # Should probably not modify the current path but instead
225 # change the behaviour of rsrc(...)
226 old_path = QDir.currentPath()
227 actuator.load_theme()
229 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
231 driver = lite_window.MiniDriver(self, self.mini)
233 # Reset path back to original value now that loading the GUI
235 QDir.setCurrent(old_path)
237 if self.config.get('lite_mode') is True:
243 def check_qt_version(self):
244 qtVersion = qVersion()
245 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
248 def update_account_selector(self):
250 accounts = self.wallet.get_account_names()
251 self.account_selector.clear()
252 if len(accounts) > 1:
253 self.account_selector.addItems([_("All accounts")] + accounts.values())
254 self.account_selector.setCurrentIndex(0)
255 self.account_selector.show()
257 self.account_selector.hide()
260 def load_wallet(self, wallet):
263 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
264 self.current_account = self.wallet.storage.get("current_account", None)
266 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
267 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
268 self.setWindowTitle( title )
270 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
271 self.notify_transactions()
272 self.update_account_selector()
273 self.new_account.setEnabled(self.wallet.seed_version>4)
274 self.update_lock_icon()
275 self.update_buttons_on_seed()
276 self.update_console()
278 run_hook('load_wallet', wallet)
281 def open_wallet(self):
282 wallet_folder = self.wallet.storage.path
283 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
287 storage = WalletStorage({'wallet_path': filename})
288 if not storage.file_exists:
289 self.show_message("file not found "+ filename)
292 self.wallet.stop_threads()
295 wallet = Wallet(storage)
296 wallet.start_threads(self.network)
298 self.load_wallet(wallet)
302 def backup_wallet(self):
304 path = self.wallet.storage.path
305 wallet_folder = os.path.dirname(path)
306 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
310 new_path = os.path.join(wallet_folder, filename)
313 shutil.copy2(path, new_path)
314 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
315 except (IOError, os.error), reason:
316 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
319 def new_wallet(self):
322 wallet_folder = os.path.dirname(self.wallet.storage.path)
323 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
326 filename = os.path.join(wallet_folder, filename)
328 storage = WalletStorage({'wallet_path': filename})
329 if storage.file_exists:
330 QMessageBox.critical(None, "Error", _("File exists"))
333 wizard = installwizard.InstallWizard(self.config, self.network, storage)
334 wallet = wizard.run()
336 self.load_wallet(wallet)
340 def init_menubar(self):
343 file_menu = menubar.addMenu(_("&File"))
344 open_wallet_action = file_menu.addAction(_("&Open"))
345 open_wallet_action.setShortcut(QKeySequence.Open)
346 open_wallet_action.triggered.connect(self.open_wallet)
348 new_wallet_action = file_menu.addAction(_("&New/Restore"))
349 new_wallet_action.setShortcut(QKeySequence.New)
350 new_wallet_action.triggered.connect(self.new_wallet)
352 wallet_backup = file_menu.addAction(_("&Save Copy"))
353 wallet_backup.setShortcut(QKeySequence.SaveAs)
354 wallet_backup.triggered.connect(self.backup_wallet)
356 quit_item = file_menu.addAction(_("&Quit"))
357 #quit_item.setShortcut(QKeySequence.Quit)
358 quit_item.triggered.connect(self.close)
360 wallet_menu = menubar.addMenu(_("&Wallet"))
362 new_contact = wallet_menu.addAction(_("&New contact"))
363 new_contact.triggered.connect(self.new_contact_dialog)
365 self.new_account = wallet_menu.addAction(_("&New account"))
366 self.new_account.triggered.connect(self.new_account_dialog)
368 wallet_menu.addSeparator()
370 pw = wallet_menu.addAction(_("&Password"))
371 pw.triggered.connect(self.change_password_dialog)
373 show_seed = wallet_menu.addAction(_("&Seed"))
374 show_seed.triggered.connect(self.show_seed_dialog)
376 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
377 show_mpk.triggered.connect(self.show_master_public_key)
379 wallet_menu.addSeparator()
381 labels_menu = wallet_menu.addMenu(_("&Labels"))
382 import_labels = labels_menu.addAction(_("&Import"))
383 import_labels.triggered.connect(self.do_import_labels)
384 export_labels = labels_menu.addAction(_("&Export"))
385 export_labels.triggered.connect(self.do_export_labels)
387 keys_menu = wallet_menu.addMenu(_("&Private keys"))
388 import_keys = keys_menu.addAction(_("&Import"))
389 import_keys.triggered.connect(self.do_import_privkey)
390 export_keys = keys_menu.addAction(_("&Export"))
391 export_keys.triggered.connect(self.do_export_privkeys)
393 ex_history = wallet_menu.addAction(_("&Export History"))
394 ex_history.triggered.connect(self.do_export_history)
398 tools_menu = menubar.addMenu(_("&Tools"))
400 # Settings / Preferences are all reserved keywords in OSX using this as work around
401 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
402 preferences_menu = tools_menu.addAction(preferences_name)
403 #preferences_menu.setShortcut(QKeySequence.Preferences)
404 preferences_menu.triggered.connect(self.settings_dialog)
406 tools_menu.addAction(_("&Network"), self.run_network_dialog)
407 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
409 tools_menu.addSeparator()
410 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
411 tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
413 tools_menu.addSeparator()
415 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
417 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
418 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
420 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
421 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
423 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
425 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
426 raw_transaction_file.triggered.connect(self.do_process_from_file)
428 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
429 raw_transaction_text.triggered.connect(self.do_process_from_text)
431 raw_transaction_text = raw_transaction_menu.addAction(_("&From the blockchain"))
432 raw_transaction_text.triggered.connect(self.do_process_from_txid)
435 help_menu = menubar.addMenu(_("&Help"))
436 show_about = help_menu.addAction(_("&About"))
437 show_about.triggered.connect(self.show_about)
438 web_open = help_menu.addAction(_("&Official website"))
439 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
441 help_menu.addSeparator()
442 doc_open = help_menu.addAction(_("&Documentation"))
443 doc_open.setShortcut(QKeySequence.HelpContents)
444 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
445 report_bug = help_menu.addAction(_("&Report Bug"))
446 report_bug.triggered.connect(self.show_report_bug)
448 self.setMenuBar(menubar)
450 def show_about(self):
451 QMessageBox.about(self, "Electrum",
452 _("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."))
454 def show_report_bug(self):
455 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
456 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
459 def notify_transactions(self):
460 if not self.network or not self.network.is_connected():
463 print_error("Notifying GUI")
464 if len(self.network.interface.pending_transactions_for_notifications) > 0:
465 # Combine the transactions if there are more then three
466 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
469 for tx in self.network.interface.pending_transactions_for_notifications:
470 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
474 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
475 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
477 self.network.interface.pending_transactions_for_notifications = []
479 for tx in self.network.interface.pending_transactions_for_notifications:
481 self.network.interface.pending_transactions_for_notifications.remove(tx)
482 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
484 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
486 def notify(self, message):
487 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
491 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
492 def getOpenFileName(self, title, filter = ""):
493 directory = self.config.get('io_dir', os.path.expanduser('~'))
494 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
495 if fileName and directory != os.path.dirname(fileName):
496 self.config.set_key('io_dir', os.path.dirname(fileName), True)
499 def getSaveFileName(self, title, filename, filter = ""):
500 directory = self.config.get('io_dir', os.path.expanduser('~'))
501 path = os.path.join( directory, filename )
502 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
503 if fileName and directory != os.path.dirname(fileName):
504 self.config.set_key('io_dir', os.path.dirname(fileName), True)
508 QMainWindow.close(self)
509 run_hook('close_main_window')
511 def connect_slots(self, sender):
512 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
513 self.previous_payto_e=''
515 def timer_actions(self):
516 if self.need_update.is_set():
518 self.need_update.clear()
519 run_hook('timer_actions')
521 def format_amount(self, x, is_diff=False, whitespaces=False):
522 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
524 def read_amount(self, x):
525 if x in['.', '']: return None
526 p = pow(10, self.decimal_point)
527 return int( p * Decimal(x) )
530 assert self.decimal_point in [5,8]
531 return "BTC" if self.decimal_point == 8 else "mBTC"
534 def update_status(self):
535 if self.network is None or not self.network.is_running():
537 icon = QIcon(":icons/status_disconnected.png")
539 elif self.network.is_connected():
540 if not self.wallet.up_to_date:
541 text = _("Synchronizing...")
542 icon = QIcon(":icons/status_waiting.png")
543 elif self.network.server_lag > 1:
544 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
545 icon = QIcon(":icons/status_lagging.png")
547 c, u = self.wallet.get_account_balance(self.current_account)
548 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
549 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
552 run_hook('set_quote_text', c+u, r)
555 text += " (%s)"%quote
557 self.tray.setToolTip(text)
558 icon = QIcon(":icons/status_connected.png")
560 text = _("Not connected")
561 icon = QIcon(":icons/status_disconnected.png")
563 self.balance_label.setText(text)
564 self.status_button.setIcon( icon )
567 def update_wallet(self):
569 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
570 self.update_history_tab()
571 self.update_receive_tab()
572 self.update_contacts_tab()
573 self.update_completions()
576 def create_history_tab(self):
577 self.history_list = l = MyTreeWidget(self)
579 for i,width in enumerate(self.column_widths['history']):
580 l.setColumnWidth(i, width)
581 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
582 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
583 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
585 l.customContextMenuRequested.connect(self.create_history_menu)
589 def create_history_menu(self, position):
590 self.history_list.selectedIndexes()
591 item = self.history_list.currentItem()
593 tx_hash = str(item.data(0, Qt.UserRole).toString())
594 if not tx_hash: return
596 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
597 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
598 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
599 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
600 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
603 def show_transaction(self, tx):
604 import transaction_dialog
605 d = transaction_dialog.TxDialog(tx, self)
608 def tx_label_clicked(self, item, column):
609 if column==2 and item.isSelected():
611 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
612 self.history_list.editItem( item, column )
613 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
616 def tx_label_changed(self, item, column):
620 tx_hash = str(item.data(0, Qt.UserRole).toString())
621 tx = self.wallet.transactions.get(tx_hash)
622 text = unicode( item.text(2) )
623 self.wallet.set_label(tx_hash, text)
625 item.setForeground(2, QBrush(QColor('black')))
627 text = self.wallet.get_default_label(tx_hash)
628 item.setText(2, text)
629 item.setForeground(2, QBrush(QColor('gray')))
633 def edit_label(self, is_recv):
634 l = self.receive_list if is_recv else self.contacts_list
635 item = l.currentItem()
636 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
637 l.editItem( item, 1 )
638 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
642 def address_label_clicked(self, item, column, l, column_addr, column_label):
643 if column == column_label and item.isSelected():
644 is_editable = item.data(0, 32).toBool()
647 addr = unicode( item.text(column_addr) )
648 label = unicode( item.text(column_label) )
649 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
650 l.editItem( item, column )
651 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
654 def address_label_changed(self, item, column, l, column_addr, column_label):
655 if column == column_label:
656 addr = unicode( item.text(column_addr) )
657 text = unicode( item.text(column_label) )
658 is_editable = item.data(0, 32).toBool()
662 changed = self.wallet.set_label(addr, text)
664 self.update_history_tab()
665 self.update_completions()
667 self.current_item_changed(item)
669 run_hook('item_changed', item, column)
672 def current_item_changed(self, a):
673 run_hook('current_item_changed', a)
677 def update_history_tab(self):
679 self.history_list.clear()
680 for item in self.wallet.get_tx_history(self.current_account):
681 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
682 time_str = _("unknown")
685 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
687 time_str = _("error")
690 time_str = 'unverified'
691 icon = QIcon(":icons/unconfirmed.png")
694 icon = QIcon(":icons/unconfirmed.png")
696 icon = QIcon(":icons/clock%d.png"%conf)
698 icon = QIcon(":icons/confirmed.png")
700 if value is not None:
701 v_str = self.format_amount(value, True, whitespaces=True)
705 balance_str = self.format_amount(balance, whitespaces=True)
708 label, is_default_label = self.wallet.get_label(tx_hash)
710 label = _('Pruned transaction outputs')
711 is_default_label = False
713 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
714 item.setFont(2, QFont(MONOSPACE_FONT))
715 item.setFont(3, QFont(MONOSPACE_FONT))
716 item.setFont(4, QFont(MONOSPACE_FONT))
718 item.setForeground(3, QBrush(QColor("#BC1E1E")))
720 item.setData(0, Qt.UserRole, tx_hash)
721 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
723 item.setForeground(2, QBrush(QColor('grey')))
725 item.setIcon(0, icon)
726 self.history_list.insertTopLevelItem(0,item)
729 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
730 run_hook('history_tab_update')
733 def create_send_tab(self):
738 grid.setColumnMinimumWidth(3,300)
739 grid.setColumnStretch(5,1)
742 self.payto_e = QLineEdit()
743 grid.addWidget(QLabel(_('Pay to')), 1, 0)
744 grid.addWidget(self.payto_e, 1, 1, 1, 3)
746 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)
748 completer = QCompleter()
749 completer.setCaseSensitivity(False)
750 self.payto_e.setCompleter(completer)
751 completer.setModel(self.completions)
753 self.message_e = QLineEdit()
754 grid.addWidget(QLabel(_('Description')), 2, 0)
755 grid.addWidget(self.message_e, 2, 1, 1, 3)
756 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)
758 self.from_label = QLabel(_('From'))
759 grid.addWidget(self.from_label, 3, 0)
760 self.from_list = QTreeWidget(self)
761 self.from_list.setColumnCount(2)
762 self.from_list.setColumnWidth(0, 350)
763 self.from_list.setColumnWidth(1, 50)
764 self.from_list.setHeaderHidden (True)
765 self.from_list.setMaximumHeight(80)
766 grid.addWidget(self.from_list, 3, 1, 1, 3)
767 self.set_pay_from([])
769 self.amount_e = AmountEdit(self.base_unit)
770 grid.addWidget(QLabel(_('Amount')), 4, 0)
771 grid.addWidget(self.amount_e, 4, 1, 1, 2)
772 grid.addWidget(HelpButton(
773 _('Amount to be sent.') + '\n\n' \
774 + _('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.') \
775 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
777 self.fee_e = AmountEdit(self.base_unit)
778 grid.addWidget(QLabel(_('Fee')), 5, 0)
779 grid.addWidget(self.fee_e, 5, 1, 1, 2)
780 grid.addWidget(HelpButton(
781 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
782 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
783 + _('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)
786 self.send_button = EnterButton(_("Send"), self.do_send)
787 grid.addWidget(self.send_button, 6, 1)
789 b = EnterButton(_("Clear"),self.do_clear)
790 grid.addWidget(b, 6, 2)
792 self.payto_sig = QLabel('')
793 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
795 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
796 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
805 def entry_changed( is_fee ):
806 self.funds_error = False
808 if self.amount_e.is_shortcut:
809 self.amount_e.is_shortcut = False
810 sendable = self.get_sendable_balance()
811 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
812 fee = self.wallet.estimated_fee(inputs)
814 self.amount_e.setText( self.format_amount(amount) )
815 self.fee_e.setText( self.format_amount( fee ) )
818 amount = self.read_amount(str(self.amount_e.text()))
819 fee = self.read_amount(str(self.fee_e.text()))
821 if not is_fee: fee = None
824 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
826 self.fee_e.setText( self.format_amount( fee ) )
829 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
833 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
834 self.funds_error = True
835 text = _( "Not enough funds" )
836 c, u = self.wallet.get_frozen_balance()
837 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
839 self.statusBar().showMessage(text)
840 self.amount_e.setPalette(palette)
841 self.fee_e.setPalette(palette)
843 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
844 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
846 run_hook('create_send_tab', grid)
850 def set_pay_from(self, l):
852 self.from_list.clear()
853 self.from_label.setHidden(len(self.pay_from) == 0)
854 self.from_list.setHidden(len(self.pay_from) == 0)
855 for addr in self.pay_from:
856 c, u = self.wallet.get_addr_balance(addr)
857 balance = self.format_amount(c + u)
858 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
861 def update_completions(self):
863 for addr,label in self.wallet.labels.items():
864 if addr in self.wallet.addressbook:
865 l.append( label + ' <' + addr + '>')
867 run_hook('update_completions', l)
868 self.completions.setStringList(l)
872 return lambda s, *args: s.do_protect(func, args)
877 label = unicode( self.message_e.text() )
878 r = unicode( self.payto_e.text() )
881 # label or alias, with address in brackets
882 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
883 to_address = m.group(2) if m else r
885 if not is_valid(to_address):
886 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
890 amount = self.read_amount(unicode( self.amount_e.text()))
892 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
895 fee = self.read_amount(unicode( self.fee_e.text()))
897 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
900 confirm_amount = self.config.get('confirm_amount', 100000000)
901 if amount >= confirm_amount:
902 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
905 confirm_fee = self.config.get('confirm_fee', 100000)
906 if fee >= confirm_fee:
907 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()}):
910 self.send_tx(to_address, amount, fee, label)
914 def send_tx(self, to_address, amount, fee, label, password):
916 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
917 domain=self.get_payment_sources())
918 except Exception as e:
919 traceback.print_exc(file=sys.stdout)
920 self.show_message(str(e))
923 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
924 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
928 self.wallet.set_label(tx.hash(), label)
931 h = self.wallet.send_tx(tx)
932 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
933 status, msg = self.wallet.receive_tx( h, tx )
935 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
937 self.update_contacts_tab()
939 QMessageBox.warning(self, _('Error'), msg, _('OK'))
942 self.show_transaction(tx)
944 # add recipient to addressbook
945 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
946 self.wallet.addressbook.append(to_address)
951 def set_url(self, url):
952 address, amount, label, message, signature, identity, url = util.parse_url(url)
955 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
956 elif amount: amount = str(Decimal(amount))
959 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
962 self.mini.set_payment_fields(address, amount)
964 if label and self.wallet.labels.get(address) != label:
965 if self.question('Give label "%s" to address %s ?'%(label,address)):
966 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
967 self.wallet.addressbook.append(address)
968 self.wallet.set_label(address, label)
970 run_hook('set_url', url, self.show_message, self.question)
972 self.tabs.setCurrentIndex(1)
973 label = self.wallet.labels.get(address)
974 m_addr = label + ' <'+ address +'>' if label else address
975 self.payto_e.setText(m_addr)
977 self.message_e.setText(message)
979 self.amount_e.setText(amount)
982 self.set_frozen(self.payto_e,True)
983 self.set_frozen(self.amount_e,True)
984 self.set_frozen(self.message_e,True)
985 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
987 self.payto_sig.setVisible(False)
990 self.payto_sig.setVisible(False)
991 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
993 self.set_frozen(e,False)
995 self.set_pay_from([])
998 def set_frozen(self,entry,frozen):
1000 entry.setReadOnly(True)
1001 entry.setFrame(False)
1002 palette = QPalette()
1003 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1004 entry.setPalette(palette)
1006 entry.setReadOnly(False)
1007 entry.setFrame(True)
1008 palette = QPalette()
1009 palette.setColor(entry.backgroundRole(), QColor('white'))
1010 entry.setPalette(palette)
1013 def set_addrs_frozen(self,addrs,freeze):
1015 if not addr: continue
1016 if addr in self.wallet.frozen_addresses and not freeze:
1017 self.wallet.unfreeze(addr)
1018 elif addr not in self.wallet.frozen_addresses and freeze:
1019 self.wallet.freeze(addr)
1020 self.update_receive_tab()
1024 def create_list_tab(self, headers):
1025 "generic tab creation method"
1026 l = MyTreeWidget(self)
1027 l.setColumnCount( len(headers) )
1028 l.setHeaderLabels( headers )
1031 vbox = QVBoxLayout()
1038 vbox.addWidget(buttons)
1040 hbox = QHBoxLayout()
1043 buttons.setLayout(hbox)
1048 def create_receive_tab(self):
1049 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1050 l.setContextMenuPolicy(Qt.CustomContextMenu)
1051 l.customContextMenuRequested.connect(self.create_receive_menu)
1052 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1053 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1054 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1055 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1056 self.receive_list = l
1057 self.receive_buttons_hbox = hbox
1064 def save_column_widths(self):
1065 self.column_widths["receive"] = []
1066 for i in range(self.receive_list.columnCount() -1):
1067 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1069 self.column_widths["history"] = []
1070 for i in range(self.history_list.columnCount() - 1):
1071 self.column_widths["history"].append(self.history_list.columnWidth(i))
1073 self.column_widths["contacts"] = []
1074 for i in range(self.contacts_list.columnCount() - 1):
1075 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1077 self.config.set_key("column_widths_2", self.column_widths, True)
1080 def create_contacts_tab(self):
1081 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1082 l.setContextMenuPolicy(Qt.CustomContextMenu)
1083 l.customContextMenuRequested.connect(self.create_contact_menu)
1084 for i,width in enumerate(self.column_widths['contacts']):
1085 l.setColumnWidth(i, width)
1087 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1088 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1089 self.contacts_list = l
1090 self.contacts_buttons_hbox = hbox
1095 def delete_imported_key(self, addr):
1096 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1097 self.wallet.delete_imported_key(addr)
1098 self.update_receive_tab()
1099 self.update_history_tab()
1101 def edit_account_label(self, k):
1102 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1104 label = unicode(text)
1105 self.wallet.set_label(k,label)
1106 self.update_receive_tab()
1108 def account_set_expanded(self, item, k, b):
1110 self.accounts_expanded[k] = b
1112 def create_account_menu(self, position, k, item):
1114 if item.isExpanded():
1115 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1117 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1118 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1119 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1120 if self.wallet.account_is_pending(k):
1121 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1122 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1124 def delete_pending_account(self, k):
1125 self.wallet.delete_pending_account(k)
1126 self.update_receive_tab()
1128 def create_receive_menu(self, position):
1129 # fixme: this function apparently has a side effect.
1130 # if it is not called the menu pops up several times
1131 #self.receive_list.selectedIndexes()
1133 selected = self.receive_list.selectedItems()
1134 multi_select = len(selected) > 1
1135 addrs = [unicode(item.text(0)) for item in selected]
1136 if not multi_select:
1137 item = self.receive_list.itemAt(position)
1141 if not is_valid(addr):
1142 k = str(item.data(0,32).toString())
1144 self.create_account_menu(position, k, item)
1146 item.setExpanded(not item.isExpanded())
1150 if not multi_select:
1151 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1152 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1153 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1154 if self.wallet.seed:
1155 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1156 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1157 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1158 if addr in self.wallet.imported_keys:
1159 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1161 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1162 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1163 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1164 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1166 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1167 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1169 run_hook('receive_menu', menu, addrs)
1170 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1173 def get_sendable_balance(self):
1174 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1177 def get_payment_sources(self):
1179 return self.pay_from
1181 return self.wallet.get_account_addresses(self.current_account)
1184 def send_from_addresses(self, addrs):
1185 self.set_pay_from( addrs )
1186 self.tabs.setCurrentIndex(1)
1189 def payto(self, addr):
1191 label = self.wallet.labels.get(addr)
1192 m_addr = label + ' <' + addr + '>' if label else addr
1193 self.tabs.setCurrentIndex(1)
1194 self.payto_e.setText(m_addr)
1195 self.amount_e.setFocus()
1198 def delete_contact(self, x):
1199 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1200 self.wallet.delete_contact(x)
1201 self.wallet.set_label(x, None)
1202 self.update_history_tab()
1203 self.update_contacts_tab()
1204 self.update_completions()
1207 def create_contact_menu(self, position):
1208 item = self.contacts_list.itemAt(position)
1211 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1213 addr = unicode(item.text(0))
1214 label = unicode(item.text(1))
1215 is_editable = item.data(0,32).toBool()
1216 payto_addr = item.data(0,33).toString()
1217 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1218 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1219 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1221 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1222 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1224 run_hook('create_contact_menu', menu, item)
1225 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1228 def update_receive_item(self, item):
1229 item.setFont(0, QFont(MONOSPACE_FONT))
1230 address = str(item.data(0,0).toString())
1231 label = self.wallet.labels.get(address,'')
1232 item.setData(1,0,label)
1233 item.setData(0,32, True) # is editable
1235 run_hook('update_receive_item', address, item)
1237 if not self.wallet.is_mine(address): return
1239 c, u = self.wallet.get_addr_balance(address)
1240 balance = self.format_amount(c + u)
1241 item.setData(2,0,balance)
1243 if address in self.wallet.frozen_addresses:
1244 item.setBackgroundColor(0, QColor('lightblue'))
1247 def update_receive_tab(self):
1248 l = self.receive_list
1251 l.setColumnHidden(2, False)
1252 l.setColumnHidden(3, False)
1253 for i,width in enumerate(self.column_widths['receive']):
1254 l.setColumnWidth(i, width)
1256 if self.current_account is None:
1257 account_items = self.wallet.accounts.items()
1258 elif self.current_account != -1:
1259 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1263 for k, account in account_items:
1264 name = self.wallet.get_account_name(k)
1265 c,u = self.wallet.get_account_balance(k)
1266 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1267 l.addTopLevelItem(account_item)
1268 account_item.setExpanded(self.accounts_expanded.get(k, True))
1269 account_item.setData(0, 32, k)
1271 if not self.wallet.is_seeded(k):
1272 icon = QIcon(":icons/key.png")
1273 account_item.setIcon(0, icon)
1275 for is_change in ([0,1]):
1276 name = _("Receiving") if not is_change else _("Change")
1277 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1278 account_item.addChild(seq_item)
1279 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1281 if not is_change: seq_item.setExpanded(True)
1286 for address in account.get_addresses(is_change):
1287 h = self.wallet.history.get(address,[])
1291 if gap > self.wallet.gap_limit:
1296 c, u = self.wallet.get_addr_balance(address)
1297 num_tx = '*' if h == ['*'] else "%d"%len(h)
1298 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1299 self.update_receive_item(item)
1301 item.setBackgroundColor(1, QColor('red'))
1302 if len(h) > 0 and c == -u:
1304 seq_item.insertChild(0,used_item)
1306 used_item.addChild(item)
1308 seq_item.addChild(item)
1311 for k, addr in self.wallet.get_pending_accounts():
1312 name = self.wallet.labels.get(k,'')
1313 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1314 self.update_receive_item(item)
1315 l.addTopLevelItem(account_item)
1316 account_item.setExpanded(True)
1317 account_item.setData(0, 32, k)
1318 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1319 account_item.addChild(item)
1320 self.update_receive_item(item)
1323 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1324 c,u = self.wallet.get_imported_balance()
1325 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1326 l.addTopLevelItem(account_item)
1327 account_item.setExpanded(True)
1328 for address in self.wallet.imported_keys.keys():
1329 item = QTreeWidgetItem( [ address, '', '', ''] )
1330 self.update_receive_item(item)
1331 account_item.addChild(item)
1334 # we use column 1 because column 0 may be hidden
1335 l.setCurrentItem(l.topLevelItem(0),1)
1338 def update_contacts_tab(self):
1339 l = self.contacts_list
1342 for address in self.wallet.addressbook:
1343 label = self.wallet.labels.get(address,'')
1344 n = self.wallet.get_num_tx(address)
1345 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1346 item.setFont(0, QFont(MONOSPACE_FONT))
1347 # 32 = label can be edited (bool)
1348 item.setData(0,32, True)
1350 item.setData(0,33, address)
1351 l.addTopLevelItem(item)
1353 run_hook('update_contacts_tab', l)
1354 l.setCurrentItem(l.topLevelItem(0))
1358 def create_console_tab(self):
1359 from console import Console
1360 self.console = console = Console()
1364 def update_console(self):
1365 console = self.console
1366 console.history = self.config.get("console-history",[])
1367 console.history_index = len(console.history)
1369 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1370 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1372 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1374 def mkfunc(f, method):
1375 return lambda *args: apply( f, (method, args, self.password_dialog ))
1377 if m[0]=='_' or m in ['network','wallet']: continue
1378 methods[m] = mkfunc(c._run, m)
1380 console.updateNamespace(methods)
1383 def change_account(self,s):
1384 if s == _("All accounts"):
1385 self.current_account = None
1387 accounts = self.wallet.get_account_names()
1388 for k, v in accounts.items():
1390 self.current_account = k
1391 self.update_history_tab()
1392 self.update_status()
1393 self.update_receive_tab()
1395 def create_status_bar(self):
1398 sb.setFixedHeight(35)
1399 qtVersion = qVersion()
1401 self.balance_label = QLabel("")
1402 sb.addWidget(self.balance_label)
1404 from version_getter import UpdateLabel
1405 self.updatelabel = UpdateLabel(self.config, sb)
1407 self.account_selector = QComboBox()
1408 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1409 sb.addPermanentWidget(self.account_selector)
1411 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1412 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1414 self.lock_icon = QIcon()
1415 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1416 sb.addPermanentWidget( self.password_button )
1418 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1419 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1420 sb.addPermanentWidget( self.seed_button )
1421 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1422 sb.addPermanentWidget( self.status_button )
1424 run_hook('create_status_bar', (sb,))
1426 self.setStatusBar(sb)
1429 def update_lock_icon(self):
1430 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1431 self.password_button.setIcon( icon )
1434 def update_buttons_on_seed(self):
1435 if not self.wallet.is_watching_only():
1436 self.seed_button.show()
1437 self.password_button.show()
1438 self.send_button.setText(_("Send"))
1440 self.password_button.hide()
1441 self.seed_button.hide()
1442 self.send_button.setText(_("Create unsigned transaction"))
1445 def change_password_dialog(self):
1446 from password_dialog import PasswordDialog
1447 d = PasswordDialog(self.wallet, self)
1449 self.update_lock_icon()
1452 def new_contact_dialog(self):
1455 vbox = QVBoxLayout(d)
1456 vbox.addWidget(QLabel(_('New Contact')+':'))
1458 grid = QGridLayout()
1461 grid.addWidget(QLabel(_("Address")), 1, 0)
1462 grid.addWidget(line1, 1, 1)
1463 grid.addWidget(QLabel(_("Name")), 2, 0)
1464 grid.addWidget(line2, 2, 1)
1466 vbox.addLayout(grid)
1467 vbox.addLayout(ok_cancel_buttons(d))
1472 address = str(line1.text())
1473 label = unicode(line2.text())
1475 if not is_valid(address):
1476 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1479 self.wallet.add_contact(address)
1481 self.wallet.set_label(address, label)
1483 self.update_contacts_tab()
1484 self.update_history_tab()
1485 self.update_completions()
1486 self.tabs.setCurrentIndex(3)
1489 def new_account_dialog(self):
1491 dialog = QDialog(self)
1493 dialog.setWindowTitle(_("New Account"))
1495 vbox = QVBoxLayout()
1496 vbox.addWidget(QLabel(_('Account name')+':'))
1499 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1500 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1505 vbox.addLayout(ok_cancel_buttons(dialog))
1506 dialog.setLayout(vbox)
1510 name = str(e.text())
1513 self.wallet.create_pending_account('1', name)
1514 self.update_receive_tab()
1515 self.tabs.setCurrentIndex(2)
1519 def show_master_public_key_old(self):
1520 dialog = QDialog(self)
1522 dialog.setWindowTitle(_("Master Public Key"))
1524 main_text = QTextEdit()
1525 main_text.setText(self.wallet.get_master_public_key())
1526 main_text.setReadOnly(True)
1527 main_text.setMaximumHeight(170)
1528 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1530 ok_button = QPushButton(_("OK"))
1531 ok_button.setDefault(True)
1532 ok_button.clicked.connect(dialog.accept)
1534 main_layout = QGridLayout()
1535 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1537 main_layout.addWidget(main_text, 1, 0)
1538 main_layout.addWidget(qrw, 1, 1 )
1540 vbox = QVBoxLayout()
1541 vbox.addLayout(main_layout)
1542 vbox.addLayout(close_button(dialog))
1543 dialog.setLayout(vbox)
1547 def show_master_public_key(self):
1549 if self.wallet.seed_version == 4:
1550 self.show_master_public_key_old()
1553 dialog = QDialog(self)
1555 dialog.setWindowTitle(_("Master Public Keys"))
1557 chain_text = QTextEdit()
1558 chain_text.setReadOnly(True)
1559 chain_text.setMaximumHeight(170)
1560 chain_qrw = QRCodeWidget()
1562 mpk_text = QTextEdit()
1563 mpk_text.setReadOnly(True)
1564 mpk_text.setMaximumHeight(170)
1565 mpk_qrw = QRCodeWidget()
1567 main_layout = QGridLayout()
1569 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1570 main_layout.addWidget(mpk_text, 1, 1)
1571 main_layout.addWidget(mpk_qrw, 1, 2)
1573 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1574 main_layout.addWidget(chain_text, 2, 1)
1575 main_layout.addWidget(chain_qrw, 2, 2)
1578 c, K, cK = self.wallet.master_public_keys[str(key)]
1579 chain_text.setText(c)
1580 chain_qrw.set_addr(c)
1581 chain_qrw.update_qr()
1586 key_selector = QComboBox()
1587 keys = sorted(self.wallet.master_public_keys.keys())
1588 key_selector.addItems(keys)
1590 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1591 main_layout.addWidget(key_selector, 0, 1)
1592 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1596 vbox = QVBoxLayout()
1597 vbox.addLayout(main_layout)
1598 vbox.addLayout(close_button(dialog))
1600 dialog.setLayout(vbox)
1605 def show_seed_dialog(self, password):
1606 if self.wallet.is_watching_only():
1607 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1610 if self.wallet.seed:
1612 mnemonic = self.wallet.get_mnemonic(password)
1614 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1616 from seed_dialog import SeedDialog
1617 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1621 for k in self.wallet.master_private_keys.keys():
1622 pk = self.wallet.get_master_private_key(k, password)
1624 from seed_dialog import PrivateKeysDialog
1625 d = PrivateKeysDialog(self,l)
1632 def show_qrcode(self, data, title = _("QR code")):
1636 d.setWindowTitle(title)
1637 d.setMinimumSize(270, 300)
1638 vbox = QVBoxLayout()
1639 qrw = QRCodeWidget(data)
1640 vbox.addWidget(qrw, 1)
1641 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1642 hbox = QHBoxLayout()
1645 filename = os.path.join(self.config.path, "qrcode.bmp")
1648 bmp.save_qrcode(qrw.qr, filename)
1649 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1651 def copy_to_clipboard():
1652 bmp.save_qrcode(qrw.qr, filename)
1653 self.app.clipboard().setImage(QImage(filename))
1654 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1656 b = QPushButton(_("Copy"))
1658 b.clicked.connect(copy_to_clipboard)
1660 b = QPushButton(_("Save"))
1662 b.clicked.connect(print_qr)
1664 b = QPushButton(_("Close"))
1666 b.clicked.connect(d.accept)
1669 vbox.addLayout(hbox)
1674 def do_protect(self, func, args):
1675 if self.wallet.use_encryption:
1676 password = self.password_dialog()
1682 if args != (False,):
1683 args = (self,) + args + (password,)
1685 args = (self,password)
1690 def show_private_key(self, address, password):
1691 if not address: return
1693 pk_list = self.wallet.get_private_key(address, password)
1694 except Exception as e:
1695 self.show_message(str(e))
1699 d.setMinimumSize(600, 200)
1701 vbox = QVBoxLayout()
1702 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1703 vbox.addWidget( QLabel(_("Private key") + ':'))
1705 keys.setReadOnly(True)
1706 keys.setText('\n'.join(pk_list))
1707 vbox.addWidget(keys)
1708 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1709 vbox.addLayout(close_button(d))
1715 def do_sign(self, address, message, signature, password):
1716 message = unicode(message.toPlainText())
1717 message = message.encode('utf-8')
1719 sig = self.wallet.sign_message(str(address.text()), message, password)
1720 signature.setText(sig)
1721 except Exception as e:
1722 self.show_message(str(e))
1724 def do_verify(self, address, message, signature):
1725 message = unicode(message.toPlainText())
1726 message = message.encode('utf-8')
1727 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1728 self.show_message(_("Signature verified"))
1730 self.show_message(_("Error: wrong signature"))
1733 def sign_verify_message(self, address=''):
1736 d.setWindowTitle(_('Sign/verify Message'))
1737 d.setMinimumSize(410, 290)
1739 layout = QGridLayout(d)
1741 message_e = QTextEdit()
1742 layout.addWidget(QLabel(_('Message')), 1, 0)
1743 layout.addWidget(message_e, 1, 1)
1744 layout.setRowStretch(2,3)
1746 address_e = QLineEdit()
1747 address_e.setText(address)
1748 layout.addWidget(QLabel(_('Address')), 2, 0)
1749 layout.addWidget(address_e, 2, 1)
1751 signature_e = QTextEdit()
1752 layout.addWidget(QLabel(_('Signature')), 3, 0)
1753 layout.addWidget(signature_e, 3, 1)
1754 layout.setRowStretch(3,1)
1756 hbox = QHBoxLayout()
1758 b = QPushButton(_("Sign"))
1759 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1762 b = QPushButton(_("Verify"))
1763 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1766 b = QPushButton(_("Close"))
1767 b.clicked.connect(d.accept)
1769 layout.addLayout(hbox, 4, 1)
1774 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1776 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1777 message_e.setText(decrypted)
1778 except Exception as e:
1779 self.show_message(str(e))
1782 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1783 message = unicode(message_e.toPlainText())
1784 message = message.encode('utf-8')
1786 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1787 encrypted_e.setText(encrypted)
1788 except Exception as e:
1789 self.show_message(str(e))
1793 def encrypt_message(self, address = ''):
1796 d.setWindowTitle(_('Encrypt/decrypt Message'))
1797 d.setMinimumSize(610, 490)
1799 layout = QGridLayout(d)
1801 message_e = QTextEdit()
1802 layout.addWidget(QLabel(_('Message')), 1, 0)
1803 layout.addWidget(message_e, 1, 1)
1804 layout.setRowStretch(2,3)
1806 pubkey_e = QLineEdit()
1808 pubkey = self.wallet.getpubkeys(address)[0]
1809 pubkey_e.setText(pubkey)
1810 layout.addWidget(QLabel(_('Public key')), 2, 0)
1811 layout.addWidget(pubkey_e, 2, 1)
1813 encrypted_e = QTextEdit()
1814 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1815 layout.addWidget(encrypted_e, 3, 1)
1816 layout.setRowStretch(3,1)
1818 hbox = QHBoxLayout()
1819 b = QPushButton(_("Encrypt"))
1820 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1823 b = QPushButton(_("Decrypt"))
1824 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1827 b = QPushButton(_("Close"))
1828 b.clicked.connect(d.accept)
1831 layout.addLayout(hbox, 4, 1)
1835 def question(self, msg):
1836 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1838 def show_message(self, msg):
1839 QMessageBox.information(self, _('Message'), msg, _('OK'))
1841 def password_dialog(self ):
1848 vbox = QVBoxLayout()
1849 msg = _('Please enter your password')
1850 vbox.addWidget(QLabel(msg))
1852 grid = QGridLayout()
1854 grid.addWidget(QLabel(_('Password')), 1, 0)
1855 grid.addWidget(pw, 1, 1)
1856 vbox.addLayout(grid)
1858 vbox.addLayout(ok_cancel_buttons(d))
1861 run_hook('password_dialog', pw, grid, 1)
1862 if not d.exec_(): return
1863 return unicode(pw.text())
1872 def tx_from_text(self, txt):
1873 "json or raw hexadecimal"
1876 tx = Transaction(txt)
1882 tx_dict = json.loads(str(txt))
1883 assert "hex" in tx_dict.keys()
1884 assert "complete" in tx_dict.keys()
1885 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1886 if not tx_dict["complete"]:
1887 assert "input_info" in tx_dict.keys()
1888 input_info = json.loads(tx_dict['input_info'])
1889 tx.add_input_info(input_info)
1894 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1898 def read_tx_from_file(self):
1899 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1903 with open(fileName, "r") as f:
1904 file_content = f.read()
1905 except (ValueError, IOError, os.error), reason:
1906 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1908 return self.tx_from_text(file_content)
1912 def sign_raw_transaction(self, tx, input_info, password):
1913 self.wallet.signrawtransaction(tx, input_info, [], password)
1915 def do_process_from_text(self):
1916 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1919 tx = self.tx_from_text(text)
1921 self.show_transaction(tx)
1923 def do_process_from_file(self):
1924 tx = self.read_tx_from_file()
1926 self.show_transaction(tx)
1928 def do_process_from_txid(self):
1929 from electrum import transaction
1930 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1932 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1934 tx = transaction.Transaction(r)
1936 self.show_transaction(tx)
1938 self.show_message("unknown transaction")
1940 def do_process_from_csvReader(self, csvReader):
1945 for position, row in enumerate(csvReader):
1947 if not is_valid(address):
1948 errors.append((position, address))
1950 amount = Decimal(row[1])
1951 amount = int(100000000*amount)
1952 outputs.append((address, amount))
1953 except (ValueError, IOError, os.error), reason:
1954 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1958 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1959 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1963 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1964 except Exception as e:
1965 self.show_message(str(e))
1968 self.show_transaction(tx)
1970 def do_process_from_csv_file(self):
1971 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1975 with open(fileName, "r") as f:
1976 csvReader = csv.reader(f)
1977 self.do_process_from_csvReader(csvReader)
1978 except (ValueError, IOError, os.error), reason:
1979 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1982 def do_process_from_csv_text(self):
1983 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1984 + _("Format: address, amount. One output per line"), _("Load CSV"))
1987 f = StringIO.StringIO(text)
1988 csvReader = csv.reader(f)
1989 self.do_process_from_csvReader(csvReader)
1994 def do_export_privkeys(self, password):
1995 if not self.wallet.seed:
1996 self.show_message(_("This wallet has no seed"))
1999 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.")))
2002 select_export = _('Select file to export your private keys to')
2003 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
2005 with open(fileName, "w+") as csvfile:
2006 transaction = csv.writer(csvfile)
2007 transaction.writerow(["address", "private_key"])
2009 addresses = self.wallet.addresses(True)
2011 for addr in addresses:
2012 pk = "".join(self.wallet.get_private_key(addr, password))
2013 transaction.writerow(["%34s"%addr,pk])
2015 self.show_message(_("Private keys exported."))
2017 except (IOError, os.error), reason:
2018 export_error_label = _("Electrum was unable to produce a private key-export.")
2019 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2021 except Exception as e:
2022 self.show_message(str(e))
2026 def do_import_labels(self):
2027 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2028 if not labelsFile: return
2030 f = open(labelsFile, 'r')
2033 for key, value in json.loads(data).items():
2034 self.wallet.set_label(key, value)
2035 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2036 except (IOError, os.error), reason:
2037 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2040 def do_export_labels(self):
2041 labels = self.wallet.labels
2043 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2045 with open(fileName, 'w+') as f:
2046 json.dump(labels, f)
2047 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2048 except (IOError, os.error), reason:
2049 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2052 def do_export_history(self):
2053 from lite_window import csv_transaction
2054 csv_transaction(self.wallet)
2058 def do_import_privkey(self, password):
2059 if not self.wallet.imported_keys:
2060 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2061 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2062 + _('Are you sure you understand what you are doing?'), 3, 4)
2065 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2068 text = str(text).split()
2073 addr = self.wallet.import_key(key, password)
2074 except Exception as e:
2080 addrlist.append(addr)
2082 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2084 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2085 self.update_receive_tab()
2086 self.update_history_tab()
2089 def settings_dialog(self):
2091 d.setWindowTitle(_('Electrum Settings'))
2093 vbox = QVBoxLayout()
2094 grid = QGridLayout()
2095 grid.setColumnStretch(0,1)
2097 nz_label = QLabel(_('Display zeros') + ':')
2098 grid.addWidget(nz_label, 0, 0)
2099 nz_e = AmountEdit(None,True)
2100 nz_e.setText("%d"% self.num_zeros)
2101 grid.addWidget(nz_e, 0, 1)
2102 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2103 grid.addWidget(HelpButton(msg), 0, 2)
2104 if not self.config.is_modifiable('num_zeros'):
2105 for w in [nz_e, nz_label]: w.setEnabled(False)
2107 lang_label=QLabel(_('Language') + ':')
2108 grid.addWidget(lang_label, 1, 0)
2109 lang_combo = QComboBox()
2110 from electrum.i18n import languages
2111 lang_combo.addItems(languages.values())
2113 index = languages.keys().index(self.config.get("language",''))
2116 lang_combo.setCurrentIndex(index)
2117 grid.addWidget(lang_combo, 1, 1)
2118 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2119 if not self.config.is_modifiable('language'):
2120 for w in [lang_combo, lang_label]: w.setEnabled(False)
2123 fee_label = QLabel(_('Transaction fee') + ':')
2124 grid.addWidget(fee_label, 2, 0)
2125 fee_e = AmountEdit(self.base_unit)
2126 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2127 grid.addWidget(fee_e, 2, 1)
2128 msg = _('Fee per kilobyte of transaction.') + ' ' \
2129 + _('Recommended value') + ': ' + self.format_amount(20000)
2130 grid.addWidget(HelpButton(msg), 2, 2)
2131 if not self.config.is_modifiable('fee_per_kb'):
2132 for w in [fee_e, fee_label]: w.setEnabled(False)
2134 units = ['BTC', 'mBTC']
2135 unit_label = QLabel(_('Base unit') + ':')
2136 grid.addWidget(unit_label, 3, 0)
2137 unit_combo = QComboBox()
2138 unit_combo.addItems(units)
2139 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2140 grid.addWidget(unit_combo, 3, 1)
2141 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2142 + '\n1BTC=1000mBTC.\n' \
2143 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2145 usechange_cb = QCheckBox(_('Use change addresses'))
2146 usechange_cb.setChecked(self.wallet.use_change)
2147 grid.addWidget(usechange_cb, 4, 0)
2148 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2149 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2151 grid.setRowStretch(5,1)
2153 vbox.addLayout(grid)
2154 vbox.addLayout(ok_cancel_buttons(d))
2158 if not d.exec_(): return
2160 fee = unicode(fee_e.text())
2162 fee = self.read_amount(fee)
2164 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2167 self.wallet.set_fee(fee)
2169 nz = unicode(nz_e.text())
2174 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2177 if self.num_zeros != nz:
2179 self.config.set_key('num_zeros', nz, True)
2180 self.update_history_tab()
2181 self.update_receive_tab()
2183 usechange_result = usechange_cb.isChecked()
2184 if self.wallet.use_change != usechange_result:
2185 self.wallet.use_change = usechange_result
2186 self.wallet.storage.put('use_change', self.wallet.use_change)
2188 unit_result = units[unit_combo.currentIndex()]
2189 if self.base_unit() != unit_result:
2190 self.decimal_point = 8 if unit_result == 'BTC' else 5
2191 self.config.set_key('decimal_point', self.decimal_point, True)
2192 self.update_history_tab()
2193 self.update_status()
2195 need_restart = False
2197 lang_request = languages.keys()[lang_combo.currentIndex()]
2198 if lang_request != self.config.get('language'):
2199 self.config.set_key("language", lang_request, True)
2202 run_hook('close_settings_dialog')
2205 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2208 def run_network_dialog(self):
2209 if not self.network:
2211 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2213 def closeEvent(self, event):
2216 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2217 self.save_column_widths()
2218 self.config.set_key("console-history", self.console.history[-50:], True)
2219 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2223 def plugins_dialog(self):
2224 from electrum.plugins import plugins
2227 d.setWindowTitle(_('Electrum Plugins'))
2230 vbox = QVBoxLayout(d)
2233 scroll = QScrollArea()
2234 scroll.setEnabled(True)
2235 scroll.setWidgetResizable(True)
2236 scroll.setMinimumSize(400,250)
2237 vbox.addWidget(scroll)
2241 w.setMinimumHeight(len(plugins)*35)
2243 grid = QGridLayout()
2244 grid.setColumnStretch(0,1)
2247 def do_toggle(cb, p, w):
2250 if w: w.setEnabled(r)
2252 def mk_toggle(cb, p, w):
2253 return lambda: do_toggle(cb,p,w)
2255 for i, p in enumerate(plugins):
2257 cb = QCheckBox(p.fullname())
2258 cb.setDisabled(not p.is_available())
2259 cb.setChecked(p.is_enabled())
2260 grid.addWidget(cb, i, 0)
2261 if p.requires_settings():
2262 w = p.settings_widget(self)
2263 w.setEnabled( p.is_enabled() )
2264 grid.addWidget(w, i, 1)
2267 cb.clicked.connect(mk_toggle(cb,p,w))
2268 grid.addWidget(HelpButton(p.description()), i, 2)
2270 print_msg(_("Error: cannot display plugin"), p)
2271 traceback.print_exc(file=sys.stdout)
2272 grid.setRowStretch(i+1,1)
2274 vbox.addLayout(close_button(d))
2279 def show_account_details(self, k):
2281 d.setWindowTitle(_('Account Details'))
2284 vbox = QVBoxLayout(d)
2285 roots = self.wallet.get_roots(k)
2287 name = self.wallet.get_account_name(k)
2288 label = QLabel('Name: ' + name)
2289 vbox.addWidget(label)
2291 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2292 vbox.addWidget(QLabel('Type: ' + acctype))
2294 label = QLabel('Derivation: ' + k)
2295 vbox.addWidget(label)
2298 # mpk = self.wallet.master_public_keys[root]
2299 # text = QTextEdit()
2300 # text.setReadOnly(True)
2301 # text.setMaximumHeight(120)
2302 # text.setText(repr(mpk))
2303 # vbox.addWidget(text)
2305 vbox.addLayout(close_button(d))