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():
120 def __init__(self, config, network):
121 QMainWindow.__init__(self)
124 self.network = network
126 self._close_electrum = False
129 if sys.platform == 'darwin':
130 self.icon = QIcon(":icons/electrum_dark_icon.png")
131 #self.icon = QIcon(":icons/lock.png")
133 self.icon = QIcon(':icons/electrum_light_icon.png')
135 self.tray = QSystemTrayIcon(self.icon, self)
136 self.tray.setToolTip('Electrum')
137 self.tray.activated.connect(self.tray_activated)
141 self.create_status_bar()
143 self.need_update = threading.Event()
145 self.decimal_point = config.get('decimal_point', 8)
146 self.num_zeros = int(config.get('num_zeros',0))
148 set_language(config.get('language'))
150 self.funds_error = False
151 self.completions = QStringListModel()
153 self.tabs = tabs = QTabWidget(self)
154 self.column_widths = self.config.get("column_widths_2", default_column_widths )
155 tabs.addTab(self.create_history_tab(), _('History') )
156 tabs.addTab(self.create_send_tab(), _('Send') )
157 tabs.addTab(self.create_receive_tab(), _('Receive') )
158 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
159 tabs.addTab(self.create_console_tab(), _('Console') )
160 tabs.setMinimumSize(600, 400)
161 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
162 self.setCentralWidget(tabs)
164 g = self.config.get("winpos-qt",[100, 100, 840, 400])
165 self.setGeometry(g[0], g[1], g[2], g[3])
167 self.setWindowIcon(QIcon(":icons/electrum.png"))
170 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
171 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
172 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
173 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
174 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
176 for i in range(tabs.count()):
177 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
179 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
180 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
181 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
182 self.connect(self, QtCore.SIGNAL('send_tx2'), self.send_tx2)
183 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
185 self.history_list.setFocus(True)
189 self.network.register_callback('updated', lambda: self.need_update.set())
190 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
191 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
192 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
193 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
195 # set initial message
196 self.console.showMessage(self.network.banner)
203 self.config.set_key('lite_mode', False, True)
209 self.config.set_key('lite_mode', True, True)
217 if not self.check_qt_version():
218 if self.config.get('lite_mode') is True:
219 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
220 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
221 self.config.set_key('lite_mode', False, True)
228 actuator = lite_window.MiniActuator(self)
230 actuator.load_theme()
232 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
234 driver = lite_window.MiniDriver(self, self.mini)
236 if self.config.get('lite_mode') is True:
242 def check_qt_version(self):
243 qtVersion = qVersion()
244 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
247 def update_account_selector(self):
249 accounts = self.wallet.get_account_names()
250 self.account_selector.clear()
251 if len(accounts) > 1:
252 self.account_selector.addItems([_("All accounts")] + accounts.values())
253 self.account_selector.setCurrentIndex(0)
254 self.account_selector.show()
256 self.account_selector.hide()
259 def load_wallet(self, wallet):
262 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
263 self.current_account = self.wallet.storage.get("current_account", None)
265 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
266 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
267 self.setWindowTitle( title )
269 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
270 self.notify_transactions()
271 self.update_account_selector()
272 self.new_account.setEnabled(self.wallet.seed_version>4)
273 self.update_lock_icon()
274 self.update_buttons_on_seed()
275 self.update_console()
277 run_hook('load_wallet', wallet)
280 def open_wallet(self):
281 wallet_folder = self.wallet.storage.path
282 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
286 storage = WalletStorage({'wallet_path': filename})
287 if not storage.file_exists:
288 self.show_message("file not found "+ filename)
291 self.wallet.stop_threads()
294 wallet = Wallet(storage)
295 wallet.start_threads(self.network)
297 self.load_wallet(wallet)
301 def backup_wallet(self):
303 path = self.wallet.storage.path
304 wallet_folder = os.path.dirname(path)
305 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
309 new_path = os.path.join(wallet_folder, filename)
312 shutil.copy2(path, new_path)
313 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
314 except (IOError, os.error), reason:
315 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
318 def new_wallet(self):
321 wallet_folder = os.path.dirname(self.wallet.storage.path)
322 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
325 filename = os.path.join(wallet_folder, filename)
327 storage = WalletStorage({'wallet_path': filename})
328 if storage.file_exists:
329 QMessageBox.critical(None, "Error", _("File exists"))
332 wizard = installwizard.InstallWizard(self.config, self.network, storage)
333 wallet = wizard.run()
335 self.load_wallet(wallet)
339 def init_menubar(self):
342 file_menu = menubar.addMenu(_("&File"))
343 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
344 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
345 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
346 file_menu.addAction(_("&Quit"), self.close)
348 wallet_menu = menubar.addMenu(_("&Wallet"))
349 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
350 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
352 wallet_menu.addSeparator()
354 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
355 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
356 wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
358 wallet_menu.addSeparator()
359 labels_menu = wallet_menu.addMenu(_("&Labels"))
360 labels_menu.addAction(_("&Import"), self.do_import_labels)
361 labels_menu.addAction(_("&Export"), self.do_export_labels)
363 keys_menu = wallet_menu.addMenu(_("&Private keys"))
364 keys_menu.addAction(_("&Import"), self.do_import_privkey)
365 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
367 wallet_menu.addAction(_("&Export History"), self.do_export_history)
369 tools_menu = menubar.addMenu(_("&Tools"))
371 # Settings / Preferences are all reserved keywords in OSX using this as work around
372 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
373 tools_menu.addAction(_("&Network"), self.run_network_dialog)
374 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
375 tools_menu.addSeparator()
376 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
377 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
378 tools_menu.addSeparator()
380 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
381 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
382 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
384 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
385 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
386 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
387 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
389 help_menu = menubar.addMenu(_("&Help"))
390 help_menu.addAction(_("&About"), self.show_about)
391 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
392 help_menu.addSeparator()
393 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
394 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
396 self.setMenuBar(menubar)
398 def show_about(self):
399 QMessageBox.about(self, "Electrum",
400 _("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."))
402 def show_report_bug(self):
403 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
404 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
407 def notify_transactions(self):
408 if not self.network or not self.network.is_connected():
411 print_error("Notifying GUI")
412 if len(self.network.interface.pending_transactions_for_notifications) > 0:
413 # Combine the transactions if there are more then three
414 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
417 for tx in self.network.interface.pending_transactions_for_notifications:
418 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
422 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
423 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
425 self.network.interface.pending_transactions_for_notifications = []
427 for tx in self.network.interface.pending_transactions_for_notifications:
429 self.network.interface.pending_transactions_for_notifications.remove(tx)
430 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
432 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
434 def notify(self, message):
435 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
439 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
440 def getOpenFileName(self, title, filter = ""):
441 directory = self.config.get('io_dir', os.path.expanduser('~'))
442 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
443 if fileName and directory != os.path.dirname(fileName):
444 self.config.set_key('io_dir', os.path.dirname(fileName), True)
447 def getSaveFileName(self, title, filename, filter = ""):
448 directory = self.config.get('io_dir', os.path.expanduser('~'))
449 path = os.path.join( directory, filename )
450 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
451 if fileName and directory != os.path.dirname(fileName):
452 self.config.set_key('io_dir', os.path.dirname(fileName), True)
456 QMainWindow.close(self)
457 run_hook('close_main_window')
459 def connect_slots(self, sender):
460 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
461 self.previous_payto_e=''
463 def timer_actions(self):
464 if self.need_update.is_set():
466 self.need_update.clear()
467 run_hook('timer_actions')
469 def format_amount(self, x, is_diff=False, whitespaces=False):
470 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
472 def read_amount(self, x):
473 if x in['.', '']: return None
474 p = pow(10, self.decimal_point)
475 return int( p * Decimal(x) )
478 assert self.decimal_point in [5,8]
479 return "BTC" if self.decimal_point == 8 else "mBTC"
482 def update_status(self):
483 if self.network is None or not self.network.is_running():
485 icon = QIcon(":icons/status_disconnected.png")
487 elif self.network.is_connected():
488 if not self.wallet.up_to_date:
489 text = _("Synchronizing...")
490 icon = QIcon(":icons/status_waiting.png")
491 elif self.network.server_lag > 1:
492 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
493 icon = QIcon(":icons/status_lagging.png")
495 c, u = self.wallet.get_account_balance(self.current_account)
496 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
497 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
499 # append fiat balance and price from exchange rate plugin
501 run_hook('get_fiat_status_text', c+u, r)
506 self.tray.setToolTip(text)
507 icon = QIcon(":icons/status_connected.png")
509 text = _("Not connected")
510 icon = QIcon(":icons/status_disconnected.png")
512 self.balance_label.setText(text)
513 self.status_button.setIcon( icon )
516 def update_wallet(self):
518 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
519 self.update_history_tab()
520 self.update_receive_tab()
521 self.update_contacts_tab()
522 self.update_completions()
525 def create_history_tab(self):
526 self.history_list = l = MyTreeWidget(self)
528 for i,width in enumerate(self.column_widths['history']):
529 l.setColumnWidth(i, width)
530 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
531 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
532 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
534 l.customContextMenuRequested.connect(self.create_history_menu)
538 def create_history_menu(self, position):
539 self.history_list.selectedIndexes()
540 item = self.history_list.currentItem()
542 tx_hash = str(item.data(0, Qt.UserRole).toString())
543 if not tx_hash: return
545 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
546 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
547 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
548 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
549 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
552 def show_transaction(self, tx):
553 import transaction_dialog
554 d = transaction_dialog.TxDialog(tx, self)
557 def tx_label_clicked(self, item, column):
558 if column==2 and item.isSelected():
560 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
561 self.history_list.editItem( item, column )
562 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
565 def tx_label_changed(self, item, column):
569 tx_hash = str(item.data(0, Qt.UserRole).toString())
570 tx = self.wallet.transactions.get(tx_hash)
571 text = unicode( item.text(2) )
572 self.wallet.set_label(tx_hash, text)
574 item.setForeground(2, QBrush(QColor('black')))
576 text = self.wallet.get_default_label(tx_hash)
577 item.setText(2, text)
578 item.setForeground(2, QBrush(QColor('gray')))
582 def edit_label(self, is_recv):
583 l = self.receive_list if is_recv else self.contacts_list
584 item = l.currentItem()
585 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
586 l.editItem( item, 1 )
587 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
591 def address_label_clicked(self, item, column, l, column_addr, column_label):
592 if column == column_label and item.isSelected():
593 is_editable = item.data(0, 32).toBool()
596 addr = unicode( item.text(column_addr) )
597 label = unicode( item.text(column_label) )
598 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
599 l.editItem( item, column )
600 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
603 def address_label_changed(self, item, column, l, column_addr, column_label):
604 if column == column_label:
605 addr = unicode( item.text(column_addr) )
606 text = unicode( item.text(column_label) )
607 is_editable = item.data(0, 32).toBool()
611 changed = self.wallet.set_label(addr, text)
613 self.update_history_tab()
614 self.update_completions()
616 self.current_item_changed(item)
618 run_hook('item_changed', item, column)
621 def current_item_changed(self, a):
622 run_hook('current_item_changed', a)
626 def update_history_tab(self):
628 self.history_list.clear()
629 for item in self.wallet.get_tx_history(self.current_account):
630 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
631 time_str = _("unknown")
634 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
636 time_str = _("error")
639 time_str = 'unverified'
640 icon = QIcon(":icons/unconfirmed.png")
643 icon = QIcon(":icons/unconfirmed.png")
645 icon = QIcon(":icons/clock%d.png"%conf)
647 icon = QIcon(":icons/confirmed.png")
649 if value is not None:
650 v_str = self.format_amount(value, True, whitespaces=True)
654 balance_str = self.format_amount(balance, whitespaces=True)
657 label, is_default_label = self.wallet.get_label(tx_hash)
659 label = _('Pruned transaction outputs')
660 is_default_label = False
662 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
663 item.setFont(2, QFont(MONOSPACE_FONT))
664 item.setFont(3, QFont(MONOSPACE_FONT))
665 item.setFont(4, QFont(MONOSPACE_FONT))
667 item.setForeground(3, QBrush(QColor("#BC1E1E")))
669 item.setData(0, Qt.UserRole, tx_hash)
670 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
672 item.setForeground(2, QBrush(QColor('grey')))
674 item.setIcon(0, icon)
675 self.history_list.insertTopLevelItem(0,item)
678 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
679 run_hook('history_tab_update')
682 def create_send_tab(self):
687 grid.setColumnMinimumWidth(3,300)
688 grid.setColumnStretch(5,1)
691 self.payto_e = QLineEdit()
692 grid.addWidget(QLabel(_('Pay to')), 1, 0)
693 grid.addWidget(self.payto_e, 1, 1, 1, 3)
695 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)
697 completer = QCompleter()
698 completer.setCaseSensitivity(False)
699 self.payto_e.setCompleter(completer)
700 completer.setModel(self.completions)
702 self.message_e = QLineEdit()
703 grid.addWidget(QLabel(_('Description')), 2, 0)
704 grid.addWidget(self.message_e, 2, 1, 1, 3)
705 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)
707 self.from_label = QLabel(_('From'))
708 grid.addWidget(self.from_label, 3, 0)
709 self.from_list = QTreeWidget(self)
710 self.from_list.setColumnCount(2)
711 self.from_list.setColumnWidth(0, 350)
712 self.from_list.setColumnWidth(1, 50)
713 self.from_list.setHeaderHidden (True)
714 self.from_list.setMaximumHeight(80)
715 grid.addWidget(self.from_list, 3, 1, 1, 3)
716 self.set_pay_from([])
718 self.amount_e = AmountEdit(self.base_unit)
719 grid.addWidget(QLabel(_('Amount')), 4, 0)
720 grid.addWidget(self.amount_e, 4, 1, 1, 2)
721 grid.addWidget(HelpButton(
722 _('Amount to be sent.') + '\n\n' \
723 + _('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.') \
724 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
726 self.fee_e = AmountEdit(self.base_unit)
727 grid.addWidget(QLabel(_('Fee')), 5, 0)
728 grid.addWidget(self.fee_e, 5, 1, 1, 2)
729 grid.addWidget(HelpButton(
730 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
731 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
732 + _('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)
734 run_hook('exchange_rate_button', grid)
736 self.send_button = EnterButton(_("Send"), self.do_send)
737 grid.addWidget(self.send_button, 6, 1)
739 b = EnterButton(_("Clear"),self.do_clear)
740 grid.addWidget(b, 6, 2)
742 self.payto_sig = QLabel('')
743 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
745 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
746 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
755 def entry_changed( is_fee ):
756 self.funds_error = False
758 if self.amount_e.is_shortcut:
759 self.amount_e.is_shortcut = False
760 sendable = self.get_sendable_balance()
761 # there is only one output because we are completely spending inputs
762 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
763 fee = self.wallet.estimated_fee(inputs, 1)
765 self.amount_e.setText( self.format_amount(amount) )
766 self.fee_e.setText( self.format_amount( fee ) )
769 amount = self.read_amount(str(self.amount_e.text()))
770 fee = self.read_amount(str(self.fee_e.text()))
772 if not is_fee: fee = None
775 # assume that there will be 2 outputs (one for change)
776 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
778 self.fee_e.setText( self.format_amount( fee ) )
781 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
785 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
786 self.funds_error = True
787 text = _( "Not enough funds" )
788 c, u = self.wallet.get_frozen_balance()
789 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
791 self.statusBar().showMessage(text)
792 self.amount_e.setPalette(palette)
793 self.fee_e.setPalette(palette)
795 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
796 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
798 run_hook('create_send_tab', grid)
802 def set_pay_from(self, l):
804 self.from_list.clear()
805 self.from_label.setHidden(len(self.pay_from) == 0)
806 self.from_list.setHidden(len(self.pay_from) == 0)
807 for addr in self.pay_from:
808 c, u = self.wallet.get_addr_balance(addr)
809 balance = self.format_amount(c + u)
810 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
813 def update_completions(self):
815 for addr,label in self.wallet.labels.items():
816 if addr in self.wallet.addressbook:
817 l.append( label + ' <' + addr + '>')
819 run_hook('update_completions', l)
820 self.completions.setStringList(l)
824 return lambda s, *args: s.do_protect(func, args)
829 label = unicode( self.message_e.text() )
830 r = unicode( self.payto_e.text() )
833 # label or alias, with address in brackets
834 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
835 to_address = m.group(2) if m else r
837 if not is_valid(to_address):
838 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
842 amount = self.read_amount(unicode( self.amount_e.text()))
844 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
847 fee = self.read_amount(unicode( self.fee_e.text()))
849 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
852 confirm_amount = self.config.get('confirm_amount', 100000000)
853 if amount >= confirm_amount:
854 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
857 confirm_fee = self.config.get('confirm_fee', 100000)
858 if fee >= confirm_fee:
859 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()}):
862 self.send_tx(to_address, amount, fee, label)
865 def waiting_dialog(self, message):
867 d.setWindowTitle('Please wait')
869 vbox = QVBoxLayout(d)
876 def send_tx(self, to_address, amount, fee, label, password):
878 # first, create an unsigned tx
879 domain = self.get_payment_sources()
880 outputs = [(to_address, amount)]
882 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
884 except Exception as e:
885 traceback.print_exc(file=sys.stdout)
886 self.show_message(str(e))
889 # call hook to see if plugin needs gui interaction
890 run_hook('send_tx', tx)
896 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
897 self.wallet.sign_transaction(tx, keypairs, password)
898 self.signed_tx_data = (tx, fee, label)
899 self.emit(SIGNAL('send_tx2'))
900 self.tx_wait_dialog = self.waiting_dialog('Signing..')
901 threading.Thread(target=sign_thread).start()
903 # add recipient to addressbook
904 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
905 self.wallet.addressbook.append(to_address)
909 tx, fee, label = self.signed_tx_data
910 self.tx_wait_dialog.accept()
913 self.show_message(tx.error)
916 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
917 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
921 self.wallet.set_label(tx.hash(), label)
923 if not tx.is_complete():
924 self.show_transaction(tx)
928 def broadcast_thread():
929 self.tx_broadcast_result = self.wallet.sendtx(tx)
930 self.emit(SIGNAL('send_tx3'))
931 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
932 threading.Thread(target=broadcast_thread).start()
936 self.tx_broadcast_dialog.accept()
937 status, msg = self.tx_broadcast_result
939 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
941 self.update_contacts_tab()
943 QMessageBox.warning(self, _('Error'), msg, _('OK'))
950 def set_url(self, url):
952 address, amount, label, message, signature, identity, url = util.parse_url(url)
954 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URL'), _('OK'))
958 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
959 elif amount: amount = str(Decimal(amount))
962 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
965 self.mini.set_payment_fields(address, amount)
967 if label and self.wallet.labels.get(address) != label:
968 if self.question('Give label "%s" to address %s ?'%(label,address)):
969 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
970 self.wallet.addressbook.append(address)
971 self.wallet.set_label(address, label)
973 run_hook('set_url', url, self.show_message, self.question)
975 self.tabs.setCurrentIndex(1)
976 label = self.wallet.labels.get(address)
977 m_addr = label + ' <'+ address +'>' if label else address
978 self.payto_e.setText(m_addr)
980 self.message_e.setText(message)
982 self.amount_e.setText(amount)
985 self.set_frozen(self.payto_e,True)
986 self.set_frozen(self.amount_e,True)
987 self.set_frozen(self.message_e,True)
988 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
990 self.payto_sig.setVisible(False)
993 self.payto_sig.setVisible(False)
994 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
996 self.set_frozen(e,False)
998 self.set_pay_from([])
1001 def set_frozen(self,entry,frozen):
1003 entry.setReadOnly(True)
1004 entry.setFrame(False)
1005 palette = QPalette()
1006 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1007 entry.setPalette(palette)
1009 entry.setReadOnly(False)
1010 entry.setFrame(True)
1011 palette = QPalette()
1012 palette.setColor(entry.backgroundRole(), QColor('white'))
1013 entry.setPalette(palette)
1016 def set_addrs_frozen(self,addrs,freeze):
1018 if not addr: continue
1019 if addr in self.wallet.frozen_addresses and not freeze:
1020 self.wallet.unfreeze(addr)
1021 elif addr not in self.wallet.frozen_addresses and freeze:
1022 self.wallet.freeze(addr)
1023 self.update_receive_tab()
1027 def create_list_tab(self, headers):
1028 "generic tab creation method"
1029 l = MyTreeWidget(self)
1030 l.setColumnCount( len(headers) )
1031 l.setHeaderLabels( headers )
1034 vbox = QVBoxLayout()
1041 vbox.addWidget(buttons)
1043 hbox = QHBoxLayout()
1046 buttons.setLayout(hbox)
1051 def create_receive_tab(self):
1052 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1053 l.setContextMenuPolicy(Qt.CustomContextMenu)
1054 l.customContextMenuRequested.connect(self.create_receive_menu)
1055 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1056 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1057 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1058 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1059 self.receive_list = l
1060 self.receive_buttons_hbox = hbox
1067 def save_column_widths(self):
1068 self.column_widths["receive"] = []
1069 for i in range(self.receive_list.columnCount() -1):
1070 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1072 self.column_widths["history"] = []
1073 for i in range(self.history_list.columnCount() - 1):
1074 self.column_widths["history"].append(self.history_list.columnWidth(i))
1076 self.column_widths["contacts"] = []
1077 for i in range(self.contacts_list.columnCount() - 1):
1078 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1080 self.config.set_key("column_widths_2", self.column_widths, True)
1083 def create_contacts_tab(self):
1084 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1085 l.setContextMenuPolicy(Qt.CustomContextMenu)
1086 l.customContextMenuRequested.connect(self.create_contact_menu)
1087 for i,width in enumerate(self.column_widths['contacts']):
1088 l.setColumnWidth(i, width)
1090 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1091 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1092 self.contacts_list = l
1093 self.contacts_buttons_hbox = hbox
1098 def delete_imported_key(self, addr):
1099 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1100 self.wallet.delete_imported_key(addr)
1101 self.update_receive_tab()
1102 self.update_history_tab()
1104 def edit_account_label(self, k):
1105 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1107 label = unicode(text)
1108 self.wallet.set_label(k,label)
1109 self.update_receive_tab()
1111 def account_set_expanded(self, item, k, b):
1113 self.accounts_expanded[k] = b
1115 def create_account_menu(self, position, k, item):
1117 if item.isExpanded():
1118 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1120 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1121 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1122 if self.wallet.seed_version > 4:
1123 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1124 if self.wallet.account_is_pending(k):
1125 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1126 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1128 def delete_pending_account(self, k):
1129 self.wallet.delete_pending_account(k)
1130 self.update_receive_tab()
1132 def create_receive_menu(self, position):
1133 # fixme: this function apparently has a side effect.
1134 # if it is not called the menu pops up several times
1135 #self.receive_list.selectedIndexes()
1137 selected = self.receive_list.selectedItems()
1138 multi_select = len(selected) > 1
1139 addrs = [unicode(item.text(0)) for item in selected]
1140 if not multi_select:
1141 item = self.receive_list.itemAt(position)
1145 if not is_valid(addr):
1146 k = str(item.data(0,32).toString())
1148 self.create_account_menu(position, k, item)
1150 item.setExpanded(not item.isExpanded())
1154 if not multi_select:
1155 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1156 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1157 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1158 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1159 if self.wallet.seed:
1160 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1161 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1162 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1163 if addr in self.wallet.imported_keys:
1164 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1166 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1167 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1168 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1169 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1171 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1172 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1174 run_hook('receive_menu', menu, addrs)
1175 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1178 def get_sendable_balance(self):
1179 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1182 def get_payment_sources(self):
1184 return self.pay_from
1186 return self.wallet.get_account_addresses(self.current_account)
1189 def send_from_addresses(self, addrs):
1190 self.set_pay_from( addrs )
1191 self.tabs.setCurrentIndex(1)
1194 def payto(self, addr):
1196 label = self.wallet.labels.get(addr)
1197 m_addr = label + ' <' + addr + '>' if label else addr
1198 self.tabs.setCurrentIndex(1)
1199 self.payto_e.setText(m_addr)
1200 self.amount_e.setFocus()
1203 def delete_contact(self, x):
1204 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1205 self.wallet.delete_contact(x)
1206 self.wallet.set_label(x, None)
1207 self.update_history_tab()
1208 self.update_contacts_tab()
1209 self.update_completions()
1212 def create_contact_menu(self, position):
1213 item = self.contacts_list.itemAt(position)
1216 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1218 addr = unicode(item.text(0))
1219 label = unicode(item.text(1))
1220 is_editable = item.data(0,32).toBool()
1221 payto_addr = item.data(0,33).toString()
1222 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1223 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1224 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1226 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1227 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1229 run_hook('create_contact_menu', menu, item)
1230 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1233 def update_receive_item(self, item):
1234 item.setFont(0, QFont(MONOSPACE_FONT))
1235 address = str(item.data(0,0).toString())
1236 label = self.wallet.labels.get(address,'')
1237 item.setData(1,0,label)
1238 item.setData(0,32, True) # is editable
1240 run_hook('update_receive_item', address, item)
1242 if not self.wallet.is_mine(address): return
1244 c, u = self.wallet.get_addr_balance(address)
1245 balance = self.format_amount(c + u)
1246 item.setData(2,0,balance)
1248 if address in self.wallet.frozen_addresses:
1249 item.setBackgroundColor(0, QColor('lightblue'))
1252 def update_receive_tab(self):
1253 l = self.receive_list
1256 l.setColumnHidden(2, False)
1257 l.setColumnHidden(3, False)
1258 for i,width in enumerate(self.column_widths['receive']):
1259 l.setColumnWidth(i, width)
1261 if self.current_account is None:
1262 account_items = self.wallet.accounts.items()
1263 elif self.current_account != -1:
1264 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1268 for k, account in account_items:
1269 name = self.wallet.get_account_name(k)
1270 c,u = self.wallet.get_account_balance(k)
1271 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1272 l.addTopLevelItem(account_item)
1273 account_item.setExpanded(self.accounts_expanded.get(k, True))
1274 account_item.setData(0, 32, k)
1276 if not self.wallet.is_seeded(k):
1277 icon = QIcon(":icons/key.png")
1278 account_item.setIcon(0, icon)
1280 for is_change in ([0,1]):
1281 name = _("Receiving") if not is_change else _("Change")
1282 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1283 account_item.addChild(seq_item)
1284 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1286 if not is_change: seq_item.setExpanded(True)
1291 for address in account.get_addresses(is_change):
1292 h = self.wallet.history.get(address,[])
1296 if gap > self.wallet.gap_limit:
1301 c, u = self.wallet.get_addr_balance(address)
1302 num_tx = '*' if h == ['*'] else "%d"%len(h)
1303 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1304 self.update_receive_item(item)
1306 item.setBackgroundColor(1, QColor('red'))
1307 if len(h) > 0 and c == -u:
1309 seq_item.insertChild(0,used_item)
1311 used_item.addChild(item)
1313 seq_item.addChild(item)
1316 for k, addr in self.wallet.get_pending_accounts():
1317 name = self.wallet.labels.get(k,'')
1318 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1319 self.update_receive_item(item)
1320 l.addTopLevelItem(account_item)
1321 account_item.setExpanded(True)
1322 account_item.setData(0, 32, k)
1323 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1324 account_item.addChild(item)
1325 self.update_receive_item(item)
1328 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1329 c,u = self.wallet.get_imported_balance()
1330 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1331 l.addTopLevelItem(account_item)
1332 account_item.setExpanded(True)
1333 for address in self.wallet.imported_keys.keys():
1334 item = QTreeWidgetItem( [ address, '', '', ''] )
1335 self.update_receive_item(item)
1336 account_item.addChild(item)
1339 # we use column 1 because column 0 may be hidden
1340 l.setCurrentItem(l.topLevelItem(0),1)
1343 def update_contacts_tab(self):
1344 l = self.contacts_list
1347 for address in self.wallet.addressbook:
1348 label = self.wallet.labels.get(address,'')
1349 n = self.wallet.get_num_tx(address)
1350 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1351 item.setFont(0, QFont(MONOSPACE_FONT))
1352 # 32 = label can be edited (bool)
1353 item.setData(0,32, True)
1355 item.setData(0,33, address)
1356 l.addTopLevelItem(item)
1358 run_hook('update_contacts_tab', l)
1359 l.setCurrentItem(l.topLevelItem(0))
1363 def create_console_tab(self):
1364 from console import Console
1365 self.console = console = Console()
1369 def update_console(self):
1370 console = self.console
1371 console.history = self.config.get("console-history",[])
1372 console.history_index = len(console.history)
1374 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1375 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1377 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1379 def mkfunc(f, method):
1380 return lambda *args: apply( f, (method, args, self.password_dialog ))
1382 if m[0]=='_' or m in ['network','wallet']: continue
1383 methods[m] = mkfunc(c._run, m)
1385 console.updateNamespace(methods)
1388 def change_account(self,s):
1389 if s == _("All accounts"):
1390 self.current_account = None
1392 accounts = self.wallet.get_account_names()
1393 for k, v in accounts.items():
1395 self.current_account = k
1396 self.update_history_tab()
1397 self.update_status()
1398 self.update_receive_tab()
1400 def create_status_bar(self):
1403 sb.setFixedHeight(35)
1404 qtVersion = qVersion()
1406 self.balance_label = QLabel("")
1407 sb.addWidget(self.balance_label)
1409 from version_getter import UpdateLabel
1410 self.updatelabel = UpdateLabel(self.config, sb)
1412 self.account_selector = QComboBox()
1413 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1414 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1415 sb.addPermanentWidget(self.account_selector)
1417 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1418 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1420 self.lock_icon = QIcon()
1421 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1422 sb.addPermanentWidget( self.password_button )
1424 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1425 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1426 sb.addPermanentWidget( self.seed_button )
1427 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1428 sb.addPermanentWidget( self.status_button )
1430 run_hook('create_status_bar', (sb,))
1432 self.setStatusBar(sb)
1435 def update_lock_icon(self):
1436 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1437 self.password_button.setIcon( icon )
1440 def update_buttons_on_seed(self):
1441 if not self.wallet.is_watching_only():
1442 self.seed_button.show()
1443 self.password_button.show()
1444 self.send_button.setText(_("Send"))
1446 self.password_button.hide()
1447 self.seed_button.hide()
1448 self.send_button.setText(_("Create unsigned transaction"))
1451 def change_password_dialog(self):
1452 from password_dialog import PasswordDialog
1453 d = PasswordDialog(self.wallet, self)
1455 self.update_lock_icon()
1458 def new_contact_dialog(self):
1461 d.setWindowTitle(_("New Contact"))
1462 vbox = QVBoxLayout(d)
1463 vbox.addWidget(QLabel(_('New Contact')+':'))
1465 grid = QGridLayout()
1468 grid.addWidget(QLabel(_("Address")), 1, 0)
1469 grid.addWidget(line1, 1, 1)
1470 grid.addWidget(QLabel(_("Name")), 2, 0)
1471 grid.addWidget(line2, 2, 1)
1473 vbox.addLayout(grid)
1474 vbox.addLayout(ok_cancel_buttons(d))
1479 address = str(line1.text())
1480 label = unicode(line2.text())
1482 if not is_valid(address):
1483 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1486 self.wallet.add_contact(address)
1488 self.wallet.set_label(address, label)
1490 self.update_contacts_tab()
1491 self.update_history_tab()
1492 self.update_completions()
1493 self.tabs.setCurrentIndex(3)
1497 def new_account_dialog(self, password):
1499 dialog = QDialog(self)
1501 dialog.setWindowTitle(_("New Account"))
1503 vbox = QVBoxLayout()
1504 vbox.addWidget(QLabel(_('Account name')+':'))
1507 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1508 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1513 vbox.addLayout(ok_cancel_buttons(dialog))
1514 dialog.setLayout(vbox)
1518 name = str(e.text())
1521 self.wallet.create_pending_account('1of1', name, password)
1522 self.update_receive_tab()
1523 self.tabs.setCurrentIndex(2)
1527 def show_master_public_key_old(self):
1528 dialog = QDialog(self)
1530 dialog.setWindowTitle(_("Master Public Key"))
1532 main_text = QTextEdit()
1533 main_text.setText(self.wallet.get_master_public_key())
1534 main_text.setReadOnly(True)
1535 main_text.setMaximumHeight(170)
1536 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1538 ok_button = QPushButton(_("OK"))
1539 ok_button.setDefault(True)
1540 ok_button.clicked.connect(dialog.accept)
1542 main_layout = QGridLayout()
1543 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1545 main_layout.addWidget(main_text, 1, 0)
1546 main_layout.addWidget(qrw, 1, 1 )
1548 vbox = QVBoxLayout()
1549 vbox.addLayout(main_layout)
1550 vbox.addLayout(close_button(dialog))
1551 dialog.setLayout(vbox)
1555 def show_master_public_key(self):
1557 if self.wallet.seed_version == 4:
1558 self.show_master_public_key_old()
1561 dialog = QDialog(self)
1563 dialog.setWindowTitle(_("Master Public Keys"))
1565 mpk_text = QTextEdit()
1566 mpk_text.setReadOnly(True)
1567 mpk_text.setMaximumHeight(170)
1568 mpk_qrw = QRCodeWidget()
1570 main_layout = QGridLayout()
1572 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1573 main_layout.addWidget(mpk_text, 1, 1)
1574 main_layout.addWidget(mpk_qrw, 1, 2)
1577 xpub = self.wallet.master_public_keys[str(key)]
1578 mpk_text.setText(xpub)
1579 mpk_qrw.set_addr(xpub)
1582 key_selector = QComboBox()
1583 keys = sorted(self.wallet.master_public_keys.keys())
1584 key_selector.addItems(keys)
1586 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1587 main_layout.addWidget(key_selector, 0, 1)
1588 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1592 vbox = QVBoxLayout()
1593 vbox.addLayout(main_layout)
1594 vbox.addLayout(close_button(dialog))
1596 dialog.setLayout(vbox)
1601 def show_seed_dialog(self, password):
1602 if self.wallet.is_watching_only():
1603 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1606 if self.wallet.seed:
1608 mnemonic = self.wallet.get_mnemonic(password)
1610 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1612 from seed_dialog import SeedDialog
1613 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1617 for k in self.wallet.master_private_keys.keys():
1618 pk = self.wallet.get_master_private_key(k, password)
1620 from seed_dialog import PrivateKeysDialog
1621 d = PrivateKeysDialog(self,l)
1628 def show_qrcode(self, data, title = _("QR code")):
1632 d.setWindowTitle(title)
1633 d.setMinimumSize(270, 300)
1634 vbox = QVBoxLayout()
1635 qrw = QRCodeWidget(data)
1636 vbox.addWidget(qrw, 1)
1637 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1638 hbox = QHBoxLayout()
1641 filename = os.path.join(self.config.path, "qrcode.bmp")
1644 bmp.save_qrcode(qrw.qr, filename)
1645 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1647 def copy_to_clipboard():
1648 bmp.save_qrcode(qrw.qr, filename)
1649 self.app.clipboard().setImage(QImage(filename))
1650 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1652 b = QPushButton(_("Copy"))
1654 b.clicked.connect(copy_to_clipboard)
1656 b = QPushButton(_("Save"))
1658 b.clicked.connect(print_qr)
1660 b = QPushButton(_("Close"))
1662 b.clicked.connect(d.accept)
1665 vbox.addLayout(hbox)
1670 def do_protect(self, func, args):
1671 if self.wallet.use_encryption:
1672 password = self.password_dialog()
1678 if args != (False,):
1679 args = (self,) + args + (password,)
1681 args = (self,password)
1685 def show_public_keys(self, address):
1686 if not address: return
1688 pubkey_list = self.wallet.get_public_keys(address)
1689 except Exception as e:
1690 traceback.print_exc(file=sys.stdout)
1691 self.show_message(str(e))
1695 d.setMinimumSize(600, 200)
1697 vbox = QVBoxLayout()
1698 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1699 vbox.addWidget( QLabel(_("Public key") + ':'))
1701 keys.setReadOnly(True)
1702 keys.setText('\n'.join(pubkey_list))
1703 vbox.addWidget(keys)
1704 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1705 vbox.addLayout(close_button(d))
1710 def show_private_key(self, address, password):
1711 if not address: return
1713 pk_list = self.wallet.get_private_key(address, password)
1714 except Exception as e:
1715 traceback.print_exc(file=sys.stdout)
1716 self.show_message(str(e))
1720 d.setMinimumSize(600, 200)
1722 vbox = QVBoxLayout()
1723 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1724 vbox.addWidget( QLabel(_("Private key") + ':'))
1726 keys.setReadOnly(True)
1727 keys.setText('\n'.join(pk_list))
1728 vbox.addWidget(keys)
1729 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1730 vbox.addLayout(close_button(d))
1736 def do_sign(self, address, message, signature, password):
1737 message = unicode(message.toPlainText())
1738 message = message.encode('utf-8')
1740 sig = self.wallet.sign_message(str(address.text()), message, password)
1741 signature.setText(sig)
1742 except Exception as e:
1743 self.show_message(str(e))
1745 def do_verify(self, address, message, signature):
1746 message = unicode(message.toPlainText())
1747 message = message.encode('utf-8')
1748 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1749 self.show_message(_("Signature verified"))
1751 self.show_message(_("Error: wrong signature"))
1754 def sign_verify_message(self, address=''):
1757 d.setWindowTitle(_('Sign/verify Message'))
1758 d.setMinimumSize(410, 290)
1760 layout = QGridLayout(d)
1762 message_e = QTextEdit()
1763 layout.addWidget(QLabel(_('Message')), 1, 0)
1764 layout.addWidget(message_e, 1, 1)
1765 layout.setRowStretch(2,3)
1767 address_e = QLineEdit()
1768 address_e.setText(address)
1769 layout.addWidget(QLabel(_('Address')), 2, 0)
1770 layout.addWidget(address_e, 2, 1)
1772 signature_e = QTextEdit()
1773 layout.addWidget(QLabel(_('Signature')), 3, 0)
1774 layout.addWidget(signature_e, 3, 1)
1775 layout.setRowStretch(3,1)
1777 hbox = QHBoxLayout()
1779 b = QPushButton(_("Sign"))
1780 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1783 b = QPushButton(_("Verify"))
1784 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1787 b = QPushButton(_("Close"))
1788 b.clicked.connect(d.accept)
1790 layout.addLayout(hbox, 4, 1)
1795 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1797 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1798 message_e.setText(decrypted)
1799 except Exception as e:
1800 self.show_message(str(e))
1803 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1804 message = unicode(message_e.toPlainText())
1805 message = message.encode('utf-8')
1807 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1808 encrypted_e.setText(encrypted)
1809 except Exception as e:
1810 self.show_message(str(e))
1814 def encrypt_message(self, address = ''):
1817 d.setWindowTitle(_('Encrypt/decrypt Message'))
1818 d.setMinimumSize(610, 490)
1820 layout = QGridLayout(d)
1822 message_e = QTextEdit()
1823 layout.addWidget(QLabel(_('Message')), 1, 0)
1824 layout.addWidget(message_e, 1, 1)
1825 layout.setRowStretch(2,3)
1827 pubkey_e = QLineEdit()
1829 pubkey = self.wallet.getpubkeys(address)[0]
1830 pubkey_e.setText(pubkey)
1831 layout.addWidget(QLabel(_('Public key')), 2, 0)
1832 layout.addWidget(pubkey_e, 2, 1)
1834 encrypted_e = QTextEdit()
1835 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1836 layout.addWidget(encrypted_e, 3, 1)
1837 layout.setRowStretch(3,1)
1839 hbox = QHBoxLayout()
1840 b = QPushButton(_("Encrypt"))
1841 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1844 b = QPushButton(_("Decrypt"))
1845 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1848 b = QPushButton(_("Close"))
1849 b.clicked.connect(d.accept)
1852 layout.addLayout(hbox, 4, 1)
1856 def question(self, msg):
1857 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1859 def show_message(self, msg):
1860 QMessageBox.information(self, _('Message'), msg, _('OK'))
1862 def password_dialog(self ):
1865 d.setWindowTitle(_("Enter Password"))
1870 vbox = QVBoxLayout()
1871 msg = _('Please enter your password')
1872 vbox.addWidget(QLabel(msg))
1874 grid = QGridLayout()
1876 grid.addWidget(QLabel(_('Password')), 1, 0)
1877 grid.addWidget(pw, 1, 1)
1878 vbox.addLayout(grid)
1880 vbox.addLayout(ok_cancel_buttons(d))
1883 run_hook('password_dialog', pw, grid, 1)
1884 if not d.exec_(): return
1885 return unicode(pw.text())
1894 def tx_from_text(self, txt):
1895 "json or raw hexadecimal"
1898 tx = Transaction(txt)
1904 tx_dict = json.loads(str(txt))
1905 assert "hex" in tx_dict.keys()
1906 assert "complete" in tx_dict.keys()
1907 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1908 if not tx_dict["complete"]:
1909 assert "input_info" in tx_dict.keys()
1910 input_info = json.loads(tx_dict['input_info'])
1911 tx.add_input_info(input_info)
1916 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1920 def read_tx_from_file(self):
1921 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1925 with open(fileName, "r") as f:
1926 file_content = f.read()
1927 except (ValueError, IOError, os.error), reason:
1928 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1930 return self.tx_from_text(file_content)
1934 def sign_raw_transaction(self, tx, input_info, password):
1935 self.wallet.signrawtransaction(tx, input_info, [], password)
1937 def do_process_from_text(self):
1938 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1941 tx = self.tx_from_text(text)
1943 self.show_transaction(tx)
1945 def do_process_from_file(self):
1946 tx = self.read_tx_from_file()
1948 self.show_transaction(tx)
1950 def do_process_from_txid(self):
1951 from electrum import transaction
1952 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1954 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1956 tx = transaction.Transaction(r)
1958 self.show_transaction(tx)
1960 self.show_message("unknown transaction")
1962 def do_process_from_csvReader(self, csvReader):
1967 for position, row in enumerate(csvReader):
1969 if not is_valid(address):
1970 errors.append((position, address))
1972 amount = Decimal(row[1])
1973 amount = int(100000000*amount)
1974 outputs.append((address, amount))
1975 except (ValueError, IOError, os.error), reason:
1976 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1980 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1981 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1985 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1986 except Exception as e:
1987 self.show_message(str(e))
1990 self.show_transaction(tx)
1992 def do_process_from_csv_file(self):
1993 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1997 with open(fileName, "r") as f:
1998 csvReader = csv.reader(f)
1999 self.do_process_from_csvReader(csvReader)
2000 except (ValueError, IOError, os.error), reason:
2001 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2004 def do_process_from_csv_text(self):
2005 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2006 + _("Format: address, amount. One output per line"), _("Load CSV"))
2009 f = StringIO.StringIO(text)
2010 csvReader = csv.reader(f)
2011 self.do_process_from_csvReader(csvReader)
2016 def do_export_privkeys(self, password):
2017 if not self.wallet.seed:
2018 self.show_message(_("This wallet has no seed"))
2021 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.")))
2024 select_export = _('Select file to export your private keys to')
2025 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
2027 with open(fileName, "w+") as csvfile:
2028 transaction = csv.writer(csvfile)
2029 transaction.writerow(["address", "private_key"])
2031 addresses = self.wallet.addresses(True)
2033 for addr in addresses:
2034 pk = "".join(self.wallet.get_private_key(addr, password))
2035 transaction.writerow(["%34s"%addr,pk])
2037 self.show_message(_("Private keys exported."))
2039 except (IOError, os.error), reason:
2040 export_error_label = _("Electrum was unable to produce a private key-export.")
2041 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2043 except Exception as e:
2044 self.show_message(str(e))
2048 def do_import_labels(self):
2049 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2050 if not labelsFile: return
2052 f = open(labelsFile, 'r')
2055 for key, value in json.loads(data).items():
2056 self.wallet.set_label(key, value)
2057 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2058 except (IOError, os.error), reason:
2059 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2062 def do_export_labels(self):
2063 labels = self.wallet.labels
2065 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2067 with open(fileName, 'w+') as f:
2068 json.dump(labels, f)
2069 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2070 except (IOError, os.error), reason:
2071 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2074 def do_export_history(self):
2075 from lite_window import csv_transaction
2076 csv_transaction(self.wallet)
2080 def do_import_privkey(self, password):
2081 if not self.wallet.imported_keys:
2082 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2083 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2084 + _('Are you sure you understand what you are doing?'), 3, 4)
2087 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2090 text = str(text).split()
2095 addr = self.wallet.import_key(key, password)
2096 except Exception as e:
2102 addrlist.append(addr)
2104 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2106 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2107 self.update_receive_tab()
2108 self.update_history_tab()
2111 def settings_dialog(self):
2113 d.setWindowTitle(_('Electrum Settings'))
2115 vbox = QVBoxLayout()
2116 grid = QGridLayout()
2117 grid.setColumnStretch(0,1)
2119 nz_label = QLabel(_('Display zeros') + ':')
2120 grid.addWidget(nz_label, 0, 0)
2121 nz_e = AmountEdit(None,True)
2122 nz_e.setText("%d"% self.num_zeros)
2123 grid.addWidget(nz_e, 0, 1)
2124 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2125 grid.addWidget(HelpButton(msg), 0, 2)
2126 if not self.config.is_modifiable('num_zeros'):
2127 for w in [nz_e, nz_label]: w.setEnabled(False)
2129 lang_label=QLabel(_('Language') + ':')
2130 grid.addWidget(lang_label, 1, 0)
2131 lang_combo = QComboBox()
2132 from electrum.i18n import languages
2133 lang_combo.addItems(languages.values())
2135 index = languages.keys().index(self.config.get("language",''))
2138 lang_combo.setCurrentIndex(index)
2139 grid.addWidget(lang_combo, 1, 1)
2140 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2141 if not self.config.is_modifiable('language'):
2142 for w in [lang_combo, lang_label]: w.setEnabled(False)
2145 fee_label = QLabel(_('Transaction fee') + ':')
2146 grid.addWidget(fee_label, 2, 0)
2147 fee_e = AmountEdit(self.base_unit)
2148 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2149 grid.addWidget(fee_e, 2, 1)
2150 msg = _('Fee per kilobyte of transaction.') + ' ' \
2151 + _('Recommended value') + ': ' + self.format_amount(20000)
2152 grid.addWidget(HelpButton(msg), 2, 2)
2153 if not self.config.is_modifiable('fee_per_kb'):
2154 for w in [fee_e, fee_label]: w.setEnabled(False)
2156 units = ['BTC', 'mBTC']
2157 unit_label = QLabel(_('Base unit') + ':')
2158 grid.addWidget(unit_label, 3, 0)
2159 unit_combo = QComboBox()
2160 unit_combo.addItems(units)
2161 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2162 grid.addWidget(unit_combo, 3, 1)
2163 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2164 + '\n1BTC=1000mBTC.\n' \
2165 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2167 usechange_cb = QCheckBox(_('Use change addresses'))
2168 usechange_cb.setChecked(self.wallet.use_change)
2169 grid.addWidget(usechange_cb, 4, 0)
2170 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2171 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2173 grid.setRowStretch(5,1)
2175 vbox.addLayout(grid)
2176 vbox.addLayout(ok_cancel_buttons(d))
2180 if not d.exec_(): return
2182 fee = unicode(fee_e.text())
2184 fee = self.read_amount(fee)
2186 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2189 self.wallet.set_fee(fee)
2191 nz = unicode(nz_e.text())
2196 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2199 if self.num_zeros != nz:
2201 self.config.set_key('num_zeros', nz, True)
2202 self.update_history_tab()
2203 self.update_receive_tab()
2205 usechange_result = usechange_cb.isChecked()
2206 if self.wallet.use_change != usechange_result:
2207 self.wallet.use_change = usechange_result
2208 self.wallet.storage.put('use_change', self.wallet.use_change)
2210 unit_result = units[unit_combo.currentIndex()]
2211 if self.base_unit() != unit_result:
2212 self.decimal_point = 8 if unit_result == 'BTC' else 5
2213 self.config.set_key('decimal_point', self.decimal_point, True)
2214 self.update_history_tab()
2215 self.update_status()
2217 need_restart = False
2219 lang_request = languages.keys()[lang_combo.currentIndex()]
2220 if lang_request != self.config.get('language'):
2221 self.config.set_key("language", lang_request, True)
2224 run_hook('close_settings_dialog')
2227 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2230 def run_network_dialog(self):
2231 if not self.network:
2233 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2235 def closeEvent(self, event):
2238 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2239 self.save_column_widths()
2240 self.config.set_key("console-history", self.console.history[-50:], True)
2241 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2245 def plugins_dialog(self):
2246 from electrum.plugins import plugins
2249 d.setWindowTitle(_('Electrum Plugins'))
2252 vbox = QVBoxLayout(d)
2255 scroll = QScrollArea()
2256 scroll.setEnabled(True)
2257 scroll.setWidgetResizable(True)
2258 scroll.setMinimumSize(400,250)
2259 vbox.addWidget(scroll)
2263 w.setMinimumHeight(len(plugins)*35)
2265 grid = QGridLayout()
2266 grid.setColumnStretch(0,1)
2269 def do_toggle(cb, p, w):
2272 if w: w.setEnabled(r)
2274 def mk_toggle(cb, p, w):
2275 return lambda: do_toggle(cb,p,w)
2277 for i, p in enumerate(plugins):
2279 cb = QCheckBox(p.fullname())
2280 cb.setDisabled(not p.is_available())
2281 cb.setChecked(p.is_enabled())
2282 grid.addWidget(cb, i, 0)
2283 if p.requires_settings():
2284 w = p.settings_widget(self)
2285 w.setEnabled( p.is_enabled() )
2286 grid.addWidget(w, i, 1)
2289 cb.clicked.connect(mk_toggle(cb,p,w))
2290 grid.addWidget(HelpButton(p.description()), i, 2)
2292 print_msg(_("Error: cannot display plugin"), p)
2293 traceback.print_exc(file=sys.stdout)
2294 grid.setRowStretch(i+1,1)
2296 vbox.addLayout(close_button(d))
2301 def show_account_details(self, k):
2302 account = self.wallet.accounts[k]
2305 d.setWindowTitle(_('Account Details'))
2308 vbox = QVBoxLayout(d)
2309 name = self.wallet.get_account_name(k)
2310 label = QLabel('Name: ' + name)
2311 vbox.addWidget(label)
2313 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2315 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2317 vbox.addWidget(QLabel(_('Master Public Key:')))
2320 text.setReadOnly(True)
2321 text.setMaximumHeight(170)
2322 vbox.addWidget(text)
2324 mpk_text = '\n'.join( account.get_master_pubkeys() )
2325 text.setText(mpk_text)
2327 vbox.addLayout(close_button(d))