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 self.config.set_key('default_wallet_path', self.wallet.storage.path, True)
271 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
272 self.notify_transactions()
273 self.update_account_selector()
274 self.new_account.setEnabled(self.wallet.seed_version>4)
275 self.update_lock_icon()
276 self.update_buttons_on_seed()
277 self.update_console()
279 run_hook('load_wallet', wallet)
282 def open_wallet(self):
283 wallet_folder = self.wallet.storage.path
284 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
288 storage = WalletStorage({'wallet_path': filename})
289 if not storage.file_exists:
290 self.show_message("file not found "+ filename)
293 self.wallet.stop_threads()
296 wallet = Wallet(storage)
297 wallet.start_threads(self.network)
299 self.load_wallet(wallet)
303 def backup_wallet(self):
305 path = self.wallet.storage.path
306 wallet_folder = os.path.dirname(path)
307 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
311 new_path = os.path.join(wallet_folder, filename)
314 shutil.copy2(path, new_path)
315 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
316 except (IOError, os.error), reason:
317 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
320 def new_wallet(self):
323 wallet_folder = os.path.dirname(self.wallet.storage.path)
324 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
327 filename = os.path.join(wallet_folder, filename)
329 storage = WalletStorage({'wallet_path': filename})
330 if storage.file_exists:
331 QMessageBox.critical(None, "Error", _("File exists"))
334 wizard = installwizard.InstallWizard(self.config, self.network, storage)
335 wallet = wizard.run()
337 self.load_wallet(wallet)
341 def init_menubar(self):
344 file_menu = menubar.addMenu(_("&File"))
345 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
346 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
347 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
348 file_menu.addAction(_("&Quit"), self.close)
350 wallet_menu = menubar.addMenu(_("&Wallet"))
351 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
352 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
354 wallet_menu.addSeparator()
356 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
357 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
358 wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
360 wallet_menu.addSeparator()
361 labels_menu = wallet_menu.addMenu(_("&Labels"))
362 labels_menu.addAction(_("&Import"), self.do_import_labels)
363 labels_menu.addAction(_("&Export"), self.do_export_labels)
365 keys_menu = wallet_menu.addMenu(_("&Private keys"))
366 keys_menu.addAction(_("&Import"), self.do_import_privkey)
367 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
369 wallet_menu.addAction(_("&Export History"), self.do_export_history)
371 tools_menu = menubar.addMenu(_("&Tools"))
373 # Settings / Preferences are all reserved keywords in OSX using this as work around
374 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
375 tools_menu.addAction(_("&Network"), self.run_network_dialog)
376 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
377 tools_menu.addSeparator()
378 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
379 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
380 tools_menu.addSeparator()
382 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
383 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
384 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
386 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
387 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
388 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
389 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
391 help_menu = menubar.addMenu(_("&Help"))
392 help_menu.addAction(_("&About"), self.show_about)
393 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
394 help_menu.addSeparator()
395 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
396 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
398 self.setMenuBar(menubar)
400 def show_about(self):
401 QMessageBox.about(self, "Electrum",
402 _("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."))
404 def show_report_bug(self):
405 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
406 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
409 def notify_transactions(self):
410 if not self.network or not self.network.is_connected():
413 print_error("Notifying GUI")
414 if len(self.network.interface.pending_transactions_for_notifications) > 0:
415 # Combine the transactions if there are more then three
416 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
419 for tx in self.network.interface.pending_transactions_for_notifications:
420 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
424 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
425 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
427 self.network.interface.pending_transactions_for_notifications = []
429 for tx in self.network.interface.pending_transactions_for_notifications:
431 self.network.interface.pending_transactions_for_notifications.remove(tx)
432 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
434 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
436 def notify(self, message):
437 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
441 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
442 def getOpenFileName(self, title, filter = ""):
443 directory = self.config.get('io_dir', os.path.expanduser('~'))
444 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
445 if fileName and directory != os.path.dirname(fileName):
446 self.config.set_key('io_dir', os.path.dirname(fileName), True)
449 def getSaveFileName(self, title, filename, filter = ""):
450 directory = self.config.get('io_dir', os.path.expanduser('~'))
451 path = os.path.join( directory, filename )
452 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
453 if fileName and directory != os.path.dirname(fileName):
454 self.config.set_key('io_dir', os.path.dirname(fileName), True)
458 QMainWindow.close(self)
459 run_hook('close_main_window')
461 def connect_slots(self, sender):
462 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
463 self.previous_payto_e=''
465 def timer_actions(self):
466 if self.need_update.is_set():
468 self.need_update.clear()
469 run_hook('timer_actions')
471 def format_amount(self, x, is_diff=False, whitespaces=False):
472 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
474 def read_amount(self, x):
475 if x in['.', '']: return None
476 p = pow(10, self.decimal_point)
477 return int( p * Decimal(x) )
480 assert self.decimal_point in [5,8]
481 return "BTC" if self.decimal_point == 8 else "mBTC"
484 def update_status(self):
485 if self.network is None or not self.network.is_running():
487 icon = QIcon(":icons/status_disconnected.png")
489 elif self.network.is_connected():
490 if not self.wallet.up_to_date:
491 text = _("Synchronizing...")
492 icon = QIcon(":icons/status_waiting.png")
493 elif self.network.server_lag > 1:
494 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
495 icon = QIcon(":icons/status_lagging.png")
497 c, u = self.wallet.get_account_balance(self.current_account)
498 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
499 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
501 # append fiat balance and price from exchange rate plugin
503 run_hook('get_fiat_status_text', c+u, r)
508 self.tray.setToolTip(text)
509 icon = QIcon(":icons/status_connected.png")
511 text = _("Not connected")
512 icon = QIcon(":icons/status_disconnected.png")
514 self.balance_label.setText(text)
515 self.status_button.setIcon( icon )
518 def update_wallet(self):
520 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
521 self.update_history_tab()
522 self.update_receive_tab()
523 self.update_contacts_tab()
524 self.update_completions()
527 def create_history_tab(self):
528 self.history_list = l = MyTreeWidget(self)
530 for i,width in enumerate(self.column_widths['history']):
531 l.setColumnWidth(i, width)
532 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
533 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
534 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
536 l.customContextMenuRequested.connect(self.create_history_menu)
540 def create_history_menu(self, position):
541 self.history_list.selectedIndexes()
542 item = self.history_list.currentItem()
544 tx_hash = str(item.data(0, Qt.UserRole).toString())
545 if not tx_hash: return
547 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
548 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
549 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
550 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
551 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
554 def show_transaction(self, tx):
555 import transaction_dialog
556 d = transaction_dialog.TxDialog(tx, self)
559 def tx_label_clicked(self, item, column):
560 if column==2 and item.isSelected():
562 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
563 self.history_list.editItem( item, column )
564 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
567 def tx_label_changed(self, item, column):
571 tx_hash = str(item.data(0, Qt.UserRole).toString())
572 tx = self.wallet.transactions.get(tx_hash)
573 text = unicode( item.text(2) )
574 self.wallet.set_label(tx_hash, text)
576 item.setForeground(2, QBrush(QColor('black')))
578 text = self.wallet.get_default_label(tx_hash)
579 item.setText(2, text)
580 item.setForeground(2, QBrush(QColor('gray')))
584 def edit_label(self, is_recv):
585 l = self.receive_list if is_recv else self.contacts_list
586 item = l.currentItem()
587 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
588 l.editItem( item, 1 )
589 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
593 def address_label_clicked(self, item, column, l, column_addr, column_label):
594 if column == column_label and item.isSelected():
595 is_editable = item.data(0, 32).toBool()
598 addr = unicode( item.text(column_addr) )
599 label = unicode( item.text(column_label) )
600 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
601 l.editItem( item, column )
602 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
605 def address_label_changed(self, item, column, l, column_addr, column_label):
606 if column == column_label:
607 addr = unicode( item.text(column_addr) )
608 text = unicode( item.text(column_label) )
609 is_editable = item.data(0, 32).toBool()
613 changed = self.wallet.set_label(addr, text)
615 self.update_history_tab()
616 self.update_completions()
618 self.current_item_changed(item)
620 run_hook('item_changed', item, column)
623 def current_item_changed(self, a):
624 run_hook('current_item_changed', a)
628 def update_history_tab(self):
630 self.history_list.clear()
631 for item in self.wallet.get_tx_history(self.current_account):
632 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
633 time_str = _("unknown")
636 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
638 time_str = _("error")
641 time_str = 'unverified'
642 icon = QIcon(":icons/unconfirmed.png")
645 icon = QIcon(":icons/unconfirmed.png")
647 icon = QIcon(":icons/clock%d.png"%conf)
649 icon = QIcon(":icons/confirmed.png")
651 if value is not None:
652 v_str = self.format_amount(value, True, whitespaces=True)
656 balance_str = self.format_amount(balance, whitespaces=True)
659 label, is_default_label = self.wallet.get_label(tx_hash)
661 label = _('Pruned transaction outputs')
662 is_default_label = False
664 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
665 item.setFont(2, QFont(MONOSPACE_FONT))
666 item.setFont(3, QFont(MONOSPACE_FONT))
667 item.setFont(4, QFont(MONOSPACE_FONT))
669 item.setForeground(3, QBrush(QColor("#BC1E1E")))
671 item.setData(0, Qt.UserRole, tx_hash)
672 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
674 item.setForeground(2, QBrush(QColor('grey')))
676 item.setIcon(0, icon)
677 self.history_list.insertTopLevelItem(0,item)
680 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
681 run_hook('history_tab_update')
684 def create_send_tab(self):
689 grid.setColumnMinimumWidth(3,300)
690 grid.setColumnStretch(5,1)
693 self.payto_e = QLineEdit()
694 grid.addWidget(QLabel(_('Pay to')), 1, 0)
695 grid.addWidget(self.payto_e, 1, 1, 1, 3)
697 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)
699 completer = QCompleter()
700 completer.setCaseSensitivity(False)
701 self.payto_e.setCompleter(completer)
702 completer.setModel(self.completions)
704 self.message_e = QLineEdit()
705 grid.addWidget(QLabel(_('Description')), 2, 0)
706 grid.addWidget(self.message_e, 2, 1, 1, 3)
707 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)
709 self.from_label = QLabel(_('From'))
710 grid.addWidget(self.from_label, 3, 0)
711 self.from_list = QTreeWidget(self)
712 self.from_list.setColumnCount(2)
713 self.from_list.setColumnWidth(0, 350)
714 self.from_list.setColumnWidth(1, 50)
715 self.from_list.setHeaderHidden (True)
716 self.from_list.setMaximumHeight(80)
717 grid.addWidget(self.from_list, 3, 1, 1, 3)
718 self.set_pay_from([])
720 self.amount_e = AmountEdit(self.base_unit)
721 grid.addWidget(QLabel(_('Amount')), 4, 0)
722 grid.addWidget(self.amount_e, 4, 1, 1, 2)
723 grid.addWidget(HelpButton(
724 _('Amount to be sent.') + '\n\n' \
725 + _('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.') \
726 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
728 self.fee_e = AmountEdit(self.base_unit)
729 grid.addWidget(QLabel(_('Fee')), 5, 0)
730 grid.addWidget(self.fee_e, 5, 1, 1, 2)
731 grid.addWidget(HelpButton(
732 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
733 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
734 + _('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)
736 run_hook('exchange_rate_button', grid)
738 self.send_button = EnterButton(_("Send"), self.do_send)
739 grid.addWidget(self.send_button, 6, 1)
741 b = EnterButton(_("Clear"),self.do_clear)
742 grid.addWidget(b, 6, 2)
744 self.payto_sig = QLabel('')
745 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
747 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
748 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
757 def entry_changed( is_fee ):
758 self.funds_error = False
760 if self.amount_e.is_shortcut:
761 self.amount_e.is_shortcut = False
762 sendable = self.get_sendable_balance()
763 # there is only one output because we are completely spending inputs
764 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
765 fee = self.wallet.estimated_fee(inputs, 1)
767 self.amount_e.setText( self.format_amount(amount) )
768 self.fee_e.setText( self.format_amount( fee ) )
771 amount = self.read_amount(str(self.amount_e.text()))
772 fee = self.read_amount(str(self.fee_e.text()))
774 if not is_fee: fee = None
777 # assume that there will be 2 outputs (one for change)
778 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
780 self.fee_e.setText( self.format_amount( fee ) )
783 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
787 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
788 self.funds_error = True
789 text = _( "Not enough funds" )
790 c, u = self.wallet.get_frozen_balance()
791 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
793 self.statusBar().showMessage(text)
794 self.amount_e.setPalette(palette)
795 self.fee_e.setPalette(palette)
797 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
798 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
800 run_hook('create_send_tab', grid)
804 def set_pay_from(self, l):
806 self.from_list.clear()
807 self.from_label.setHidden(len(self.pay_from) == 0)
808 self.from_list.setHidden(len(self.pay_from) == 0)
809 for addr in self.pay_from:
810 c, u = self.wallet.get_addr_balance(addr)
811 balance = self.format_amount(c + u)
812 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
815 def update_completions(self):
817 for addr,label in self.wallet.labels.items():
818 if addr in self.wallet.addressbook:
819 l.append( label + ' <' + addr + '>')
821 run_hook('update_completions', l)
822 self.completions.setStringList(l)
826 return lambda s, *args: s.do_protect(func, args)
831 label = unicode( self.message_e.text() )
832 r = unicode( self.payto_e.text() )
835 # label or alias, with address in brackets
836 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
837 to_address = m.group(2) if m else r
839 if not is_valid(to_address):
840 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
844 amount = self.read_amount(unicode( self.amount_e.text()))
846 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
849 fee = self.read_amount(unicode( self.fee_e.text()))
851 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
854 confirm_amount = self.config.get('confirm_amount', 100000000)
855 if amount >= confirm_amount:
856 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
859 confirm_fee = self.config.get('confirm_fee', 100000)
860 if fee >= confirm_fee:
861 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()}):
864 self.send_tx(to_address, amount, fee, label)
867 def waiting_dialog(self, message):
869 d.setWindowTitle('Please wait')
871 vbox = QVBoxLayout(d)
878 def send_tx(self, to_address, amount, fee, label, password):
880 # first, create an unsigned tx
881 domain = self.get_payment_sources()
882 outputs = [(to_address, amount)]
884 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
886 except Exception as e:
887 traceback.print_exc(file=sys.stdout)
888 self.show_message(str(e))
891 # call hook to see if plugin needs gui interaction
892 run_hook('send_tx', tx)
898 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
899 self.wallet.sign_transaction(tx, keypairs, password)
900 self.signed_tx_data = (tx, fee, label)
901 self.emit(SIGNAL('send_tx2'))
902 self.tx_wait_dialog = self.waiting_dialog('Signing..')
903 threading.Thread(target=sign_thread).start()
905 # add recipient to addressbook
906 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
907 self.wallet.addressbook.append(to_address)
911 tx, fee, label = self.signed_tx_data
912 self.tx_wait_dialog.accept()
915 self.show_message(tx.error)
918 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
919 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
923 self.wallet.set_label(tx.hash(), label)
925 if not tx.is_complete:
926 self.show_transaction(tx)
930 def broadcast_thread():
931 self.tx_broadcast_result = self.wallet.sendtx(tx)
932 self.emit(SIGNAL('send_tx3'))
933 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
934 threading.Thread(target=broadcast_thread).start()
938 self.tx_broadcast_dialog.accept()
939 status, msg = self.tx_broadcast_result
941 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
943 self.update_contacts_tab()
945 QMessageBox.warning(self, _('Error'), msg, _('OK'))
952 def set_url(self, url):
953 address, amount, label, message, signature, identity, url = util.parse_url(url)
956 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
957 elif amount: amount = str(Decimal(amount))
960 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
963 self.mini.set_payment_fields(address, amount)
965 if label and self.wallet.labels.get(address) != label:
966 if self.question('Give label "%s" to address %s ?'%(label,address)):
967 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
968 self.wallet.addressbook.append(address)
969 self.wallet.set_label(address, label)
971 run_hook('set_url', url, self.show_message, self.question)
973 self.tabs.setCurrentIndex(1)
974 label = self.wallet.labels.get(address)
975 m_addr = label + ' <'+ address +'>' if label else address
976 self.payto_e.setText(m_addr)
978 self.message_e.setText(message)
980 self.amount_e.setText(amount)
983 self.set_frozen(self.payto_e,True)
984 self.set_frozen(self.amount_e,True)
985 self.set_frozen(self.message_e,True)
986 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
988 self.payto_sig.setVisible(False)
991 self.payto_sig.setVisible(False)
992 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
994 self.set_frozen(e,False)
996 self.set_pay_from([])
999 def set_frozen(self,entry,frozen):
1001 entry.setReadOnly(True)
1002 entry.setFrame(False)
1003 palette = QPalette()
1004 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1005 entry.setPalette(palette)
1007 entry.setReadOnly(False)
1008 entry.setFrame(True)
1009 palette = QPalette()
1010 palette.setColor(entry.backgroundRole(), QColor('white'))
1011 entry.setPalette(palette)
1014 def set_addrs_frozen(self,addrs,freeze):
1016 if not addr: continue
1017 if addr in self.wallet.frozen_addresses and not freeze:
1018 self.wallet.unfreeze(addr)
1019 elif addr not in self.wallet.frozen_addresses and freeze:
1020 self.wallet.freeze(addr)
1021 self.update_receive_tab()
1025 def create_list_tab(self, headers):
1026 "generic tab creation method"
1027 l = MyTreeWidget(self)
1028 l.setColumnCount( len(headers) )
1029 l.setHeaderLabels( headers )
1032 vbox = QVBoxLayout()
1039 vbox.addWidget(buttons)
1041 hbox = QHBoxLayout()
1044 buttons.setLayout(hbox)
1049 def create_receive_tab(self):
1050 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1051 l.setContextMenuPolicy(Qt.CustomContextMenu)
1052 l.customContextMenuRequested.connect(self.create_receive_menu)
1053 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1054 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1055 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1056 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1057 self.receive_list = l
1058 self.receive_buttons_hbox = hbox
1065 def save_column_widths(self):
1066 self.column_widths["receive"] = []
1067 for i in range(self.receive_list.columnCount() -1):
1068 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1070 self.column_widths["history"] = []
1071 for i in range(self.history_list.columnCount() - 1):
1072 self.column_widths["history"].append(self.history_list.columnWidth(i))
1074 self.column_widths["contacts"] = []
1075 for i in range(self.contacts_list.columnCount() - 1):
1076 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1078 self.config.set_key("column_widths_2", self.column_widths, True)
1081 def create_contacts_tab(self):
1082 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1083 l.setContextMenuPolicy(Qt.CustomContextMenu)
1084 l.customContextMenuRequested.connect(self.create_contact_menu)
1085 for i,width in enumerate(self.column_widths['contacts']):
1086 l.setColumnWidth(i, width)
1088 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1089 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1090 self.contacts_list = l
1091 self.contacts_buttons_hbox = hbox
1096 def delete_imported_key(self, addr):
1097 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1098 self.wallet.delete_imported_key(addr)
1099 self.update_receive_tab()
1100 self.update_history_tab()
1102 def edit_account_label(self, k):
1103 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1105 label = unicode(text)
1106 self.wallet.set_label(k,label)
1107 self.update_receive_tab()
1109 def account_set_expanded(self, item, k, b):
1111 self.accounts_expanded[k] = b
1113 def create_account_menu(self, position, k, item):
1115 if item.isExpanded():
1116 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1118 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1119 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1120 if self.wallet.seed_version > 4:
1121 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1122 if self.wallet.account_is_pending(k):
1123 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1124 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1126 def delete_pending_account(self, k):
1127 self.wallet.delete_pending_account(k)
1128 self.update_receive_tab()
1130 def create_receive_menu(self, position):
1131 # fixme: this function apparently has a side effect.
1132 # if it is not called the menu pops up several times
1133 #self.receive_list.selectedIndexes()
1135 selected = self.receive_list.selectedItems()
1136 multi_select = len(selected) > 1
1137 addrs = [unicode(item.text(0)) for item in selected]
1138 if not multi_select:
1139 item = self.receive_list.itemAt(position)
1143 if not is_valid(addr):
1144 k = str(item.data(0,32).toString())
1146 self.create_account_menu(position, k, item)
1148 item.setExpanded(not item.isExpanded())
1152 if not multi_select:
1153 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1154 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1155 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1156 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1157 if self.wallet.seed:
1158 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1159 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1160 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1161 if addr in self.wallet.imported_keys:
1162 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1164 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1165 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1166 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1167 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1169 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1170 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1172 run_hook('receive_menu', menu, addrs)
1173 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1176 def get_sendable_balance(self):
1177 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1180 def get_payment_sources(self):
1182 return self.pay_from
1184 return self.wallet.get_account_addresses(self.current_account)
1187 def send_from_addresses(self, addrs):
1188 self.set_pay_from( addrs )
1189 self.tabs.setCurrentIndex(1)
1192 def payto(self, addr):
1194 label = self.wallet.labels.get(addr)
1195 m_addr = label + ' <' + addr + '>' if label else addr
1196 self.tabs.setCurrentIndex(1)
1197 self.payto_e.setText(m_addr)
1198 self.amount_e.setFocus()
1201 def delete_contact(self, x):
1202 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1203 self.wallet.delete_contact(x)
1204 self.wallet.set_label(x, None)
1205 self.update_history_tab()
1206 self.update_contacts_tab()
1207 self.update_completions()
1210 def create_contact_menu(self, position):
1211 item = self.contacts_list.itemAt(position)
1214 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1216 addr = unicode(item.text(0))
1217 label = unicode(item.text(1))
1218 is_editable = item.data(0,32).toBool()
1219 payto_addr = item.data(0,33).toString()
1220 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1221 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1222 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1224 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1225 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1227 run_hook('create_contact_menu', menu, item)
1228 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1231 def update_receive_item(self, item):
1232 item.setFont(0, QFont(MONOSPACE_FONT))
1233 address = str(item.data(0,0).toString())
1234 label = self.wallet.labels.get(address,'')
1235 item.setData(1,0,label)
1236 item.setData(0,32, True) # is editable
1238 run_hook('update_receive_item', address, item)
1240 if not self.wallet.is_mine(address): return
1242 c, u = self.wallet.get_addr_balance(address)
1243 balance = self.format_amount(c + u)
1244 item.setData(2,0,balance)
1246 if address in self.wallet.frozen_addresses:
1247 item.setBackgroundColor(0, QColor('lightblue'))
1250 def update_receive_tab(self):
1251 l = self.receive_list
1254 l.setColumnHidden(2, False)
1255 l.setColumnHidden(3, False)
1256 for i,width in enumerate(self.column_widths['receive']):
1257 l.setColumnWidth(i, width)
1259 if self.current_account is None:
1260 account_items = self.wallet.accounts.items()
1261 elif self.current_account != -1:
1262 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1266 for k, account in account_items:
1267 name = self.wallet.get_account_name(k)
1268 c,u = self.wallet.get_account_balance(k)
1269 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1270 l.addTopLevelItem(account_item)
1271 account_item.setExpanded(self.accounts_expanded.get(k, True))
1272 account_item.setData(0, 32, k)
1274 if not self.wallet.is_seeded(k):
1275 icon = QIcon(":icons/key.png")
1276 account_item.setIcon(0, icon)
1278 for is_change in ([0,1]):
1279 name = _("Receiving") if not is_change else _("Change")
1280 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1281 account_item.addChild(seq_item)
1282 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1284 if not is_change: seq_item.setExpanded(True)
1289 for address in account.get_addresses(is_change):
1290 h = self.wallet.history.get(address,[])
1294 if gap > self.wallet.gap_limit:
1299 c, u = self.wallet.get_addr_balance(address)
1300 num_tx = '*' if h == ['*'] else "%d"%len(h)
1301 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1302 self.update_receive_item(item)
1304 item.setBackgroundColor(1, QColor('red'))
1305 if len(h) > 0 and c == -u:
1307 seq_item.insertChild(0,used_item)
1309 used_item.addChild(item)
1311 seq_item.addChild(item)
1314 for k, addr in self.wallet.get_pending_accounts():
1315 name = self.wallet.labels.get(k,'')
1316 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1317 self.update_receive_item(item)
1318 l.addTopLevelItem(account_item)
1319 account_item.setExpanded(True)
1320 account_item.setData(0, 32, k)
1321 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1322 account_item.addChild(item)
1323 self.update_receive_item(item)
1326 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1327 c,u = self.wallet.get_imported_balance()
1328 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1329 l.addTopLevelItem(account_item)
1330 account_item.setExpanded(True)
1331 for address in self.wallet.imported_keys.keys():
1332 item = QTreeWidgetItem( [ address, '', '', ''] )
1333 self.update_receive_item(item)
1334 account_item.addChild(item)
1337 # we use column 1 because column 0 may be hidden
1338 l.setCurrentItem(l.topLevelItem(0),1)
1341 def update_contacts_tab(self):
1342 l = self.contacts_list
1345 for address in self.wallet.addressbook:
1346 label = self.wallet.labels.get(address,'')
1347 n = self.wallet.get_num_tx(address)
1348 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1349 item.setFont(0, QFont(MONOSPACE_FONT))
1350 # 32 = label can be edited (bool)
1351 item.setData(0,32, True)
1353 item.setData(0,33, address)
1354 l.addTopLevelItem(item)
1356 run_hook('update_contacts_tab', l)
1357 l.setCurrentItem(l.topLevelItem(0))
1361 def create_console_tab(self):
1362 from console import Console
1363 self.console = console = Console()
1367 def update_console(self):
1368 console = self.console
1369 console.history = self.config.get("console-history",[])
1370 console.history_index = len(console.history)
1372 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1373 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1375 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1377 def mkfunc(f, method):
1378 return lambda *args: apply( f, (method, args, self.password_dialog ))
1380 if m[0]=='_' or m in ['network','wallet']: continue
1381 methods[m] = mkfunc(c._run, m)
1383 console.updateNamespace(methods)
1386 def change_account(self,s):
1387 if s == _("All accounts"):
1388 self.current_account = None
1390 accounts = self.wallet.get_account_names()
1391 for k, v in accounts.items():
1393 self.current_account = k
1394 self.update_history_tab()
1395 self.update_status()
1396 self.update_receive_tab()
1398 def create_status_bar(self):
1401 sb.setFixedHeight(35)
1402 qtVersion = qVersion()
1404 self.balance_label = QLabel("")
1405 sb.addWidget(self.balance_label)
1407 from version_getter import UpdateLabel
1408 self.updatelabel = UpdateLabel(self.config, sb)
1410 self.account_selector = QComboBox()
1411 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1412 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1413 sb.addPermanentWidget(self.account_selector)
1415 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1416 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1418 self.lock_icon = QIcon()
1419 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1420 sb.addPermanentWidget( self.password_button )
1422 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1423 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1424 sb.addPermanentWidget( self.seed_button )
1425 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1426 sb.addPermanentWidget( self.status_button )
1428 run_hook('create_status_bar', (sb,))
1430 self.setStatusBar(sb)
1433 def update_lock_icon(self):
1434 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1435 self.password_button.setIcon( icon )
1438 def update_buttons_on_seed(self):
1439 if not self.wallet.is_watching_only():
1440 self.seed_button.show()
1441 self.password_button.show()
1442 self.send_button.setText(_("Send"))
1444 self.password_button.hide()
1445 self.seed_button.hide()
1446 self.send_button.setText(_("Create unsigned transaction"))
1449 def change_password_dialog(self):
1450 from password_dialog import PasswordDialog
1451 d = PasswordDialog(self.wallet, self)
1453 self.update_lock_icon()
1456 def new_contact_dialog(self):
1459 d.setWindowTitle(_("New Contact"))
1460 vbox = QVBoxLayout(d)
1461 vbox.addWidget(QLabel(_('New Contact')+':'))
1463 grid = QGridLayout()
1466 grid.addWidget(QLabel(_("Address")), 1, 0)
1467 grid.addWidget(line1, 1, 1)
1468 grid.addWidget(QLabel(_("Name")), 2, 0)
1469 grid.addWidget(line2, 2, 1)
1471 vbox.addLayout(grid)
1472 vbox.addLayout(ok_cancel_buttons(d))
1477 address = str(line1.text())
1478 label = unicode(line2.text())
1480 if not is_valid(address):
1481 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1484 self.wallet.add_contact(address)
1486 self.wallet.set_label(address, label)
1488 self.update_contacts_tab()
1489 self.update_history_tab()
1490 self.update_completions()
1491 self.tabs.setCurrentIndex(3)
1495 def new_account_dialog(self, password):
1497 dialog = QDialog(self)
1499 dialog.setWindowTitle(_("New Account"))
1501 vbox = QVBoxLayout()
1502 vbox.addWidget(QLabel(_('Account name')+':'))
1505 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1506 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1511 vbox.addLayout(ok_cancel_buttons(dialog))
1512 dialog.setLayout(vbox)
1516 name = str(e.text())
1519 self.wallet.create_pending_account('1of1', name, password)
1520 self.update_receive_tab()
1521 self.tabs.setCurrentIndex(2)
1525 def show_master_public_key_old(self):
1526 dialog = QDialog(self)
1528 dialog.setWindowTitle(_("Master Public Key"))
1530 main_text = QTextEdit()
1531 main_text.setText(self.wallet.get_master_public_key())
1532 main_text.setReadOnly(True)
1533 main_text.setMaximumHeight(170)
1534 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1536 ok_button = QPushButton(_("OK"))
1537 ok_button.setDefault(True)
1538 ok_button.clicked.connect(dialog.accept)
1540 main_layout = QGridLayout()
1541 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1543 main_layout.addWidget(main_text, 1, 0)
1544 main_layout.addWidget(qrw, 1, 1 )
1546 vbox = QVBoxLayout()
1547 vbox.addLayout(main_layout)
1548 vbox.addLayout(close_button(dialog))
1549 dialog.setLayout(vbox)
1553 def show_master_public_key(self):
1555 if self.wallet.seed_version == 4:
1556 self.show_master_public_key_old()
1559 dialog = QDialog(self)
1561 dialog.setWindowTitle(_("Master Public Keys"))
1563 mpk_text = QTextEdit()
1564 mpk_text.setReadOnly(True)
1565 mpk_text.setMaximumHeight(170)
1566 mpk_qrw = QRCodeWidget()
1568 main_layout = QGridLayout()
1570 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1571 main_layout.addWidget(mpk_text, 1, 1)
1572 main_layout.addWidget(mpk_qrw, 1, 2)
1575 xpub = self.wallet.master_public_keys[str(key)]
1576 mpk_text.setText(xpub)
1577 mpk_qrw.set_addr(xpub)
1580 key_selector = QComboBox()
1581 keys = sorted(self.wallet.master_public_keys.keys())
1582 key_selector.addItems(keys)
1584 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1585 main_layout.addWidget(key_selector, 0, 1)
1586 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1590 vbox = QVBoxLayout()
1591 vbox.addLayout(main_layout)
1592 vbox.addLayout(close_button(dialog))
1594 dialog.setLayout(vbox)
1599 def show_seed_dialog(self, password):
1600 if self.wallet.is_watching_only():
1601 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1604 if self.wallet.seed:
1606 mnemonic = self.wallet.get_mnemonic(password)
1608 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1610 from seed_dialog import SeedDialog
1611 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1615 for k in self.wallet.master_private_keys.keys():
1616 pk = self.wallet.get_master_private_key(k, password)
1618 from seed_dialog import PrivateKeysDialog
1619 d = PrivateKeysDialog(self,l)
1626 def show_qrcode(self, data, title = _("QR code")):
1630 d.setWindowTitle(title)
1631 d.setMinimumSize(270, 300)
1632 vbox = QVBoxLayout()
1633 qrw = QRCodeWidget(data)
1634 vbox.addWidget(qrw, 1)
1635 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1636 hbox = QHBoxLayout()
1639 filename = os.path.join(self.config.path, "qrcode.bmp")
1642 bmp.save_qrcode(qrw.qr, filename)
1643 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1645 def copy_to_clipboard():
1646 bmp.save_qrcode(qrw.qr, filename)
1647 self.app.clipboard().setImage(QImage(filename))
1648 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1650 b = QPushButton(_("Copy"))
1652 b.clicked.connect(copy_to_clipboard)
1654 b = QPushButton(_("Save"))
1656 b.clicked.connect(print_qr)
1658 b = QPushButton(_("Close"))
1660 b.clicked.connect(d.accept)
1663 vbox.addLayout(hbox)
1668 def do_protect(self, func, args):
1669 if self.wallet.use_encryption:
1670 password = self.password_dialog()
1676 if args != (False,):
1677 args = (self,) + args + (password,)
1679 args = (self,password)
1683 def show_public_keys(self, address):
1684 if not address: return
1686 pubkey_list = self.wallet.get_public_keys(address)
1687 except Exception as e:
1688 traceback.print_exc(file=sys.stdout)
1689 self.show_message(str(e))
1693 d.setMinimumSize(600, 200)
1695 vbox = QVBoxLayout()
1696 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1697 vbox.addWidget( QLabel(_("Public key") + ':'))
1699 keys.setReadOnly(True)
1700 keys.setText('\n'.join(pubkey_list))
1701 vbox.addWidget(keys)
1702 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1703 vbox.addLayout(close_button(d))
1708 def show_private_key(self, address, password):
1709 if not address: return
1711 pk_list = self.wallet.get_private_key(address, password)
1712 except Exception as e:
1713 traceback.print_exc(file=sys.stdout)
1714 self.show_message(str(e))
1718 d.setMinimumSize(600, 200)
1720 vbox = QVBoxLayout()
1721 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1722 vbox.addWidget( QLabel(_("Private key") + ':'))
1724 keys.setReadOnly(True)
1725 keys.setText('\n'.join(pk_list))
1726 vbox.addWidget(keys)
1727 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1728 vbox.addLayout(close_button(d))
1734 def do_sign(self, address, message, signature, password):
1735 message = unicode(message.toPlainText())
1736 message = message.encode('utf-8')
1738 sig = self.wallet.sign_message(str(address.text()), message, password)
1739 signature.setText(sig)
1740 except Exception as e:
1741 self.show_message(str(e))
1743 def do_verify(self, address, message, signature):
1744 message = unicode(message.toPlainText())
1745 message = message.encode('utf-8')
1746 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1747 self.show_message(_("Signature verified"))
1749 self.show_message(_("Error: wrong signature"))
1752 def sign_verify_message(self, address=''):
1755 d.setWindowTitle(_('Sign/verify Message'))
1756 d.setMinimumSize(410, 290)
1758 layout = QGridLayout(d)
1760 message_e = QTextEdit()
1761 layout.addWidget(QLabel(_('Message')), 1, 0)
1762 layout.addWidget(message_e, 1, 1)
1763 layout.setRowStretch(2,3)
1765 address_e = QLineEdit()
1766 address_e.setText(address)
1767 layout.addWidget(QLabel(_('Address')), 2, 0)
1768 layout.addWidget(address_e, 2, 1)
1770 signature_e = QTextEdit()
1771 layout.addWidget(QLabel(_('Signature')), 3, 0)
1772 layout.addWidget(signature_e, 3, 1)
1773 layout.setRowStretch(3,1)
1775 hbox = QHBoxLayout()
1777 b = QPushButton(_("Sign"))
1778 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1781 b = QPushButton(_("Verify"))
1782 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1785 b = QPushButton(_("Close"))
1786 b.clicked.connect(d.accept)
1788 layout.addLayout(hbox, 4, 1)
1793 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1795 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1796 message_e.setText(decrypted)
1797 except Exception as e:
1798 self.show_message(str(e))
1801 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1802 message = unicode(message_e.toPlainText())
1803 message = message.encode('utf-8')
1805 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1806 encrypted_e.setText(encrypted)
1807 except Exception as e:
1808 self.show_message(str(e))
1812 def encrypt_message(self, address = ''):
1815 d.setWindowTitle(_('Encrypt/decrypt Message'))
1816 d.setMinimumSize(610, 490)
1818 layout = QGridLayout(d)
1820 message_e = QTextEdit()
1821 layout.addWidget(QLabel(_('Message')), 1, 0)
1822 layout.addWidget(message_e, 1, 1)
1823 layout.setRowStretch(2,3)
1825 pubkey_e = QLineEdit()
1827 pubkey = self.wallet.getpubkeys(address)[0]
1828 pubkey_e.setText(pubkey)
1829 layout.addWidget(QLabel(_('Public key')), 2, 0)
1830 layout.addWidget(pubkey_e, 2, 1)
1832 encrypted_e = QTextEdit()
1833 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1834 layout.addWidget(encrypted_e, 3, 1)
1835 layout.setRowStretch(3,1)
1837 hbox = QHBoxLayout()
1838 b = QPushButton(_("Encrypt"))
1839 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1842 b = QPushButton(_("Decrypt"))
1843 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1846 b = QPushButton(_("Close"))
1847 b.clicked.connect(d.accept)
1850 layout.addLayout(hbox, 4, 1)
1854 def question(self, msg):
1855 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1857 def show_message(self, msg):
1858 QMessageBox.information(self, _('Message'), msg, _('OK'))
1860 def password_dialog(self ):
1863 d.setWindowTitle(_("Enter Password"))
1868 vbox = QVBoxLayout()
1869 msg = _('Please enter your password')
1870 vbox.addWidget(QLabel(msg))
1872 grid = QGridLayout()
1874 grid.addWidget(QLabel(_('Password')), 1, 0)
1875 grid.addWidget(pw, 1, 1)
1876 vbox.addLayout(grid)
1878 vbox.addLayout(ok_cancel_buttons(d))
1881 run_hook('password_dialog', pw, grid, 1)
1882 if not d.exec_(): return
1883 return unicode(pw.text())
1892 def tx_from_text(self, txt):
1893 "json or raw hexadecimal"
1896 tx = Transaction(txt)
1902 tx_dict = json.loads(str(txt))
1903 assert "hex" in tx_dict.keys()
1904 assert "complete" in tx_dict.keys()
1905 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1906 if not tx_dict["complete"]:
1907 assert "input_info" in tx_dict.keys()
1908 input_info = json.loads(tx_dict['input_info'])
1909 tx.add_input_info(input_info)
1914 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1918 def read_tx_from_file(self):
1919 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1923 with open(fileName, "r") as f:
1924 file_content = f.read()
1925 except (ValueError, IOError, os.error), reason:
1926 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1928 return self.tx_from_text(file_content)
1932 def sign_raw_transaction(self, tx, input_info, password):
1933 self.wallet.signrawtransaction(tx, input_info, [], password)
1935 def do_process_from_text(self):
1936 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1939 tx = self.tx_from_text(text)
1941 self.show_transaction(tx)
1943 def do_process_from_file(self):
1944 tx = self.read_tx_from_file()
1946 self.show_transaction(tx)
1948 def do_process_from_txid(self):
1949 from electrum import transaction
1950 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1952 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1954 tx = transaction.Transaction(r)
1956 self.show_transaction(tx)
1958 self.show_message("unknown transaction")
1960 def do_process_from_csvReader(self, csvReader):
1965 for position, row in enumerate(csvReader):
1967 if not is_valid(address):
1968 errors.append((position, address))
1970 amount = Decimal(row[1])
1971 amount = int(100000000*amount)
1972 outputs.append((address, amount))
1973 except (ValueError, IOError, os.error), reason:
1974 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1978 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1979 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1983 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1984 except Exception as e:
1985 self.show_message(str(e))
1988 self.show_transaction(tx)
1990 def do_process_from_csv_file(self):
1991 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1995 with open(fileName, "r") as f:
1996 csvReader = csv.reader(f)
1997 self.do_process_from_csvReader(csvReader)
1998 except (ValueError, IOError, os.error), reason:
1999 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2002 def do_process_from_csv_text(self):
2003 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2004 + _("Format: address, amount. One output per line"), _("Load CSV"))
2007 f = StringIO.StringIO(text)
2008 csvReader = csv.reader(f)
2009 self.do_process_from_csvReader(csvReader)
2014 def do_export_privkeys(self, password):
2015 if not self.wallet.seed:
2016 self.show_message(_("This wallet has no seed"))
2019 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.")))
2022 select_export = _('Select file to export your private keys to')
2023 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
2025 with open(fileName, "w+") as csvfile:
2026 transaction = csv.writer(csvfile)
2027 transaction.writerow(["address", "private_key"])
2029 addresses = self.wallet.addresses(True)
2031 for addr in addresses:
2032 pk = "".join(self.wallet.get_private_key(addr, password))
2033 transaction.writerow(["%34s"%addr,pk])
2035 self.show_message(_("Private keys exported."))
2037 except (IOError, os.error), reason:
2038 export_error_label = _("Electrum was unable to produce a private key-export.")
2039 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2041 except Exception as e:
2042 self.show_message(str(e))
2046 def do_import_labels(self):
2047 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2048 if not labelsFile: return
2050 f = open(labelsFile, 'r')
2053 for key, value in json.loads(data).items():
2054 self.wallet.set_label(key, value)
2055 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2056 except (IOError, os.error), reason:
2057 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2060 def do_export_labels(self):
2061 labels = self.wallet.labels
2063 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2065 with open(fileName, 'w+') as f:
2066 json.dump(labels, f)
2067 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2068 except (IOError, os.error), reason:
2069 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2072 def do_export_history(self):
2073 from lite_window import csv_transaction
2074 csv_transaction(self.wallet)
2078 def do_import_privkey(self, password):
2079 if not self.wallet.imported_keys:
2080 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2081 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2082 + _('Are you sure you understand what you are doing?'), 3, 4)
2085 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2088 text = str(text).split()
2093 addr = self.wallet.import_key(key, password)
2094 except Exception as e:
2100 addrlist.append(addr)
2102 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2104 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2105 self.update_receive_tab()
2106 self.update_history_tab()
2109 def settings_dialog(self):
2111 d.setWindowTitle(_('Electrum Settings'))
2113 vbox = QVBoxLayout()
2114 grid = QGridLayout()
2115 grid.setColumnStretch(0,1)
2117 nz_label = QLabel(_('Display zeros') + ':')
2118 grid.addWidget(nz_label, 0, 0)
2119 nz_e = AmountEdit(None,True)
2120 nz_e.setText("%d"% self.num_zeros)
2121 grid.addWidget(nz_e, 0, 1)
2122 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2123 grid.addWidget(HelpButton(msg), 0, 2)
2124 if not self.config.is_modifiable('num_zeros'):
2125 for w in [nz_e, nz_label]: w.setEnabled(False)
2127 lang_label=QLabel(_('Language') + ':')
2128 grid.addWidget(lang_label, 1, 0)
2129 lang_combo = QComboBox()
2130 from electrum.i18n import languages
2131 lang_combo.addItems(languages.values())
2133 index = languages.keys().index(self.config.get("language",''))
2136 lang_combo.setCurrentIndex(index)
2137 grid.addWidget(lang_combo, 1, 1)
2138 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2139 if not self.config.is_modifiable('language'):
2140 for w in [lang_combo, lang_label]: w.setEnabled(False)
2143 fee_label = QLabel(_('Transaction fee') + ':')
2144 grid.addWidget(fee_label, 2, 0)
2145 fee_e = AmountEdit(self.base_unit)
2146 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2147 grid.addWidget(fee_e, 2, 1)
2148 msg = _('Fee per kilobyte of transaction.') + ' ' \
2149 + _('Recommended value') + ': ' + self.format_amount(20000)
2150 grid.addWidget(HelpButton(msg), 2, 2)
2151 if not self.config.is_modifiable('fee_per_kb'):
2152 for w in [fee_e, fee_label]: w.setEnabled(False)
2154 units = ['BTC', 'mBTC']
2155 unit_label = QLabel(_('Base unit') + ':')
2156 grid.addWidget(unit_label, 3, 0)
2157 unit_combo = QComboBox()
2158 unit_combo.addItems(units)
2159 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2160 grid.addWidget(unit_combo, 3, 1)
2161 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2162 + '\n1BTC=1000mBTC.\n' \
2163 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2165 usechange_cb = QCheckBox(_('Use change addresses'))
2166 usechange_cb.setChecked(self.wallet.use_change)
2167 grid.addWidget(usechange_cb, 4, 0)
2168 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2169 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2171 grid.setRowStretch(5,1)
2173 vbox.addLayout(grid)
2174 vbox.addLayout(ok_cancel_buttons(d))
2178 if not d.exec_(): return
2180 fee = unicode(fee_e.text())
2182 fee = self.read_amount(fee)
2184 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2187 self.wallet.set_fee(fee)
2189 nz = unicode(nz_e.text())
2194 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2197 if self.num_zeros != nz:
2199 self.config.set_key('num_zeros', nz, True)
2200 self.update_history_tab()
2201 self.update_receive_tab()
2203 usechange_result = usechange_cb.isChecked()
2204 if self.wallet.use_change != usechange_result:
2205 self.wallet.use_change = usechange_result
2206 self.wallet.storage.put('use_change', self.wallet.use_change)
2208 unit_result = units[unit_combo.currentIndex()]
2209 if self.base_unit() != unit_result:
2210 self.decimal_point = 8 if unit_result == 'BTC' else 5
2211 self.config.set_key('decimal_point', self.decimal_point, True)
2212 self.update_history_tab()
2213 self.update_status()
2215 need_restart = False
2217 lang_request = languages.keys()[lang_combo.currentIndex()]
2218 if lang_request != self.config.get('language'):
2219 self.config.set_key("language", lang_request, True)
2222 run_hook('close_settings_dialog')
2225 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2228 def run_network_dialog(self):
2229 if not self.network:
2231 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2233 def closeEvent(self, event):
2236 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2237 self.save_column_widths()
2238 self.config.set_key("console-history", self.console.history[-50:], True)
2239 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2243 def plugins_dialog(self):
2244 from electrum.plugins import plugins
2247 d.setWindowTitle(_('Electrum Plugins'))
2250 vbox = QVBoxLayout(d)
2253 scroll = QScrollArea()
2254 scroll.setEnabled(True)
2255 scroll.setWidgetResizable(True)
2256 scroll.setMinimumSize(400,250)
2257 vbox.addWidget(scroll)
2261 w.setMinimumHeight(len(plugins)*35)
2263 grid = QGridLayout()
2264 grid.setColumnStretch(0,1)
2267 def do_toggle(cb, p, w):
2270 if w: w.setEnabled(r)
2272 def mk_toggle(cb, p, w):
2273 return lambda: do_toggle(cb,p,w)
2275 for i, p in enumerate(plugins):
2277 cb = QCheckBox(p.fullname())
2278 cb.setDisabled(not p.is_available())
2279 cb.setChecked(p.is_enabled())
2280 grid.addWidget(cb, i, 0)
2281 if p.requires_settings():
2282 w = p.settings_widget(self)
2283 w.setEnabled( p.is_enabled() )
2284 grid.addWidget(w, i, 1)
2287 cb.clicked.connect(mk_toggle(cb,p,w))
2288 grid.addWidget(HelpButton(p.description()), i, 2)
2290 print_msg(_("Error: cannot display plugin"), p)
2291 traceback.print_exc(file=sys.stdout)
2292 grid.setRowStretch(i+1,1)
2294 vbox.addLayout(close_button(d))
2299 def show_account_details(self, k):
2300 account = self.wallet.accounts[k]
2303 d.setWindowTitle(_('Account Details'))
2306 vbox = QVBoxLayout(d)
2307 name = self.wallet.get_account_name(k)
2308 label = QLabel('Name: ' + name)
2309 vbox.addWidget(label)
2311 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2313 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2315 vbox.addWidget(QLabel(_('Master Public Key:')))
2318 text.setReadOnly(True)
2319 text.setMaximumHeight(170)
2320 vbox.addWidget(text)
2322 mpk_text = '\n'.join( account.get_master_pubkeys() )
2323 text.setText(mpk_text)
2325 vbox.addLayout(close_button(d))