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):
951 address, amount, label, message, signature, identity, url = util.parse_url(url)
954 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
955 elif amount: amount = str(Decimal(amount))
958 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
961 self.mini.set_payment_fields(address, amount)
963 if label and self.wallet.labels.get(address) != label:
964 if self.question('Give label "%s" to address %s ?'%(label,address)):
965 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
966 self.wallet.addressbook.append(address)
967 self.wallet.set_label(address, label)
969 run_hook('set_url', url, self.show_message, self.question)
971 self.tabs.setCurrentIndex(1)
972 label = self.wallet.labels.get(address)
973 m_addr = label + ' <'+ address +'>' if label else address
974 self.payto_e.setText(m_addr)
976 self.message_e.setText(message)
978 self.amount_e.setText(amount)
981 self.set_frozen(self.payto_e,True)
982 self.set_frozen(self.amount_e,True)
983 self.set_frozen(self.message_e,True)
984 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
986 self.payto_sig.setVisible(False)
989 self.payto_sig.setVisible(False)
990 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
992 self.set_frozen(e,False)
994 self.set_pay_from([])
997 def set_frozen(self,entry,frozen):
999 entry.setReadOnly(True)
1000 entry.setFrame(False)
1001 palette = QPalette()
1002 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1003 entry.setPalette(palette)
1005 entry.setReadOnly(False)
1006 entry.setFrame(True)
1007 palette = QPalette()
1008 palette.setColor(entry.backgroundRole(), QColor('white'))
1009 entry.setPalette(palette)
1012 def set_addrs_frozen(self,addrs,freeze):
1014 if not addr: continue
1015 if addr in self.wallet.frozen_addresses and not freeze:
1016 self.wallet.unfreeze(addr)
1017 elif addr not in self.wallet.frozen_addresses and freeze:
1018 self.wallet.freeze(addr)
1019 self.update_receive_tab()
1023 def create_list_tab(self, headers):
1024 "generic tab creation method"
1025 l = MyTreeWidget(self)
1026 l.setColumnCount( len(headers) )
1027 l.setHeaderLabels( headers )
1030 vbox = QVBoxLayout()
1037 vbox.addWidget(buttons)
1039 hbox = QHBoxLayout()
1042 buttons.setLayout(hbox)
1047 def create_receive_tab(self):
1048 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1049 l.setContextMenuPolicy(Qt.CustomContextMenu)
1050 l.customContextMenuRequested.connect(self.create_receive_menu)
1051 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1052 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1053 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1054 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1055 self.receive_list = l
1056 self.receive_buttons_hbox = hbox
1063 def save_column_widths(self):
1064 self.column_widths["receive"] = []
1065 for i in range(self.receive_list.columnCount() -1):
1066 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1068 self.column_widths["history"] = []
1069 for i in range(self.history_list.columnCount() - 1):
1070 self.column_widths["history"].append(self.history_list.columnWidth(i))
1072 self.column_widths["contacts"] = []
1073 for i in range(self.contacts_list.columnCount() - 1):
1074 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1076 self.config.set_key("column_widths_2", self.column_widths, True)
1079 def create_contacts_tab(self):
1080 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1081 l.setContextMenuPolicy(Qt.CustomContextMenu)
1082 l.customContextMenuRequested.connect(self.create_contact_menu)
1083 for i,width in enumerate(self.column_widths['contacts']):
1084 l.setColumnWidth(i, width)
1086 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1087 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1088 self.contacts_list = l
1089 self.contacts_buttons_hbox = hbox
1094 def delete_imported_key(self, addr):
1095 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1096 self.wallet.delete_imported_key(addr)
1097 self.update_receive_tab()
1098 self.update_history_tab()
1100 def edit_account_label(self, k):
1101 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1103 label = unicode(text)
1104 self.wallet.set_label(k,label)
1105 self.update_receive_tab()
1107 def account_set_expanded(self, item, k, b):
1109 self.accounts_expanded[k] = b
1111 def create_account_menu(self, position, k, item):
1113 if item.isExpanded():
1114 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1116 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1117 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1118 if self.wallet.seed_version > 4:
1119 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1120 if self.wallet.account_is_pending(k):
1121 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1122 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1124 def delete_pending_account(self, k):
1125 self.wallet.delete_pending_account(k)
1126 self.update_receive_tab()
1128 def create_receive_menu(self, position):
1129 # fixme: this function apparently has a side effect.
1130 # if it is not called the menu pops up several times
1131 #self.receive_list.selectedIndexes()
1133 selected = self.receive_list.selectedItems()
1134 multi_select = len(selected) > 1
1135 addrs = [unicode(item.text(0)) for item in selected]
1136 if not multi_select:
1137 item = self.receive_list.itemAt(position)
1141 if not is_valid(addr):
1142 k = str(item.data(0,32).toString())
1144 self.create_account_menu(position, k, item)
1146 item.setExpanded(not item.isExpanded())
1150 if not multi_select:
1151 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1152 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1153 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1154 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1155 if self.wallet.seed:
1156 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1157 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1158 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1159 if addr in self.wallet.imported_keys:
1160 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1162 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1163 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1164 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1165 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1167 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1168 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1170 run_hook('receive_menu', menu, addrs)
1171 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1174 def get_sendable_balance(self):
1175 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1178 def get_payment_sources(self):
1180 return self.pay_from
1182 return self.wallet.get_account_addresses(self.current_account)
1185 def send_from_addresses(self, addrs):
1186 self.set_pay_from( addrs )
1187 self.tabs.setCurrentIndex(1)
1190 def payto(self, addr):
1192 label = self.wallet.labels.get(addr)
1193 m_addr = label + ' <' + addr + '>' if label else addr
1194 self.tabs.setCurrentIndex(1)
1195 self.payto_e.setText(m_addr)
1196 self.amount_e.setFocus()
1199 def delete_contact(self, x):
1200 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1201 self.wallet.delete_contact(x)
1202 self.wallet.set_label(x, None)
1203 self.update_history_tab()
1204 self.update_contacts_tab()
1205 self.update_completions()
1208 def create_contact_menu(self, position):
1209 item = self.contacts_list.itemAt(position)
1212 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1214 addr = unicode(item.text(0))
1215 label = unicode(item.text(1))
1216 is_editable = item.data(0,32).toBool()
1217 payto_addr = item.data(0,33).toString()
1218 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1219 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1220 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1222 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1223 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1225 run_hook('create_contact_menu', menu, item)
1226 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1229 def update_receive_item(self, item):
1230 item.setFont(0, QFont(MONOSPACE_FONT))
1231 address = str(item.data(0,0).toString())
1232 label = self.wallet.labels.get(address,'')
1233 item.setData(1,0,label)
1234 item.setData(0,32, True) # is editable
1236 run_hook('update_receive_item', address, item)
1238 if not self.wallet.is_mine(address): return
1240 c, u = self.wallet.get_addr_balance(address)
1241 balance = self.format_amount(c + u)
1242 item.setData(2,0,balance)
1244 if address in self.wallet.frozen_addresses:
1245 item.setBackgroundColor(0, QColor('lightblue'))
1248 def update_receive_tab(self):
1249 l = self.receive_list
1252 l.setColumnHidden(2, False)
1253 l.setColumnHidden(3, False)
1254 for i,width in enumerate(self.column_widths['receive']):
1255 l.setColumnWidth(i, width)
1257 if self.current_account is None:
1258 account_items = self.wallet.accounts.items()
1259 elif self.current_account != -1:
1260 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1264 for k, account in account_items:
1265 name = self.wallet.get_account_name(k)
1266 c,u = self.wallet.get_account_balance(k)
1267 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1268 l.addTopLevelItem(account_item)
1269 account_item.setExpanded(self.accounts_expanded.get(k, True))
1270 account_item.setData(0, 32, k)
1272 if not self.wallet.is_seeded(k):
1273 icon = QIcon(":icons/key.png")
1274 account_item.setIcon(0, icon)
1276 for is_change in ([0,1]):
1277 name = _("Receiving") if not is_change else _("Change")
1278 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1279 account_item.addChild(seq_item)
1280 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1282 if not is_change: seq_item.setExpanded(True)
1287 for address in account.get_addresses(is_change):
1288 h = self.wallet.history.get(address,[])
1292 if gap > self.wallet.gap_limit:
1297 c, u = self.wallet.get_addr_balance(address)
1298 num_tx = '*' if h == ['*'] else "%d"%len(h)
1299 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1300 self.update_receive_item(item)
1302 item.setBackgroundColor(1, QColor('red'))
1303 if len(h) > 0 and c == -u:
1305 seq_item.insertChild(0,used_item)
1307 used_item.addChild(item)
1309 seq_item.addChild(item)
1312 for k, addr in self.wallet.get_pending_accounts():
1313 name = self.wallet.labels.get(k,'')
1314 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1315 self.update_receive_item(item)
1316 l.addTopLevelItem(account_item)
1317 account_item.setExpanded(True)
1318 account_item.setData(0, 32, k)
1319 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1320 account_item.addChild(item)
1321 self.update_receive_item(item)
1324 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1325 c,u = self.wallet.get_imported_balance()
1326 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1327 l.addTopLevelItem(account_item)
1328 account_item.setExpanded(True)
1329 for address in self.wallet.imported_keys.keys():
1330 item = QTreeWidgetItem( [ address, '', '', ''] )
1331 self.update_receive_item(item)
1332 account_item.addChild(item)
1335 # we use column 1 because column 0 may be hidden
1336 l.setCurrentItem(l.topLevelItem(0),1)
1339 def update_contacts_tab(self):
1340 l = self.contacts_list
1343 for address in self.wallet.addressbook:
1344 label = self.wallet.labels.get(address,'')
1345 n = self.wallet.get_num_tx(address)
1346 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1347 item.setFont(0, QFont(MONOSPACE_FONT))
1348 # 32 = label can be edited (bool)
1349 item.setData(0,32, True)
1351 item.setData(0,33, address)
1352 l.addTopLevelItem(item)
1354 run_hook('update_contacts_tab', l)
1355 l.setCurrentItem(l.topLevelItem(0))
1359 def create_console_tab(self):
1360 from console import Console
1361 self.console = console = Console()
1365 def update_console(self):
1366 console = self.console
1367 console.history = self.config.get("console-history",[])
1368 console.history_index = len(console.history)
1370 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1371 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1373 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1375 def mkfunc(f, method):
1376 return lambda *args: apply( f, (method, args, self.password_dialog ))
1378 if m[0]=='_' or m in ['network','wallet']: continue
1379 methods[m] = mkfunc(c._run, m)
1381 console.updateNamespace(methods)
1384 def change_account(self,s):
1385 if s == _("All accounts"):
1386 self.current_account = None
1388 accounts = self.wallet.get_account_names()
1389 for k, v in accounts.items():
1391 self.current_account = k
1392 self.update_history_tab()
1393 self.update_status()
1394 self.update_receive_tab()
1396 def create_status_bar(self):
1399 sb.setFixedHeight(35)
1400 qtVersion = qVersion()
1402 self.balance_label = QLabel("")
1403 sb.addWidget(self.balance_label)
1405 from version_getter import UpdateLabel
1406 self.updatelabel = UpdateLabel(self.config, sb)
1408 self.account_selector = QComboBox()
1409 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1410 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1411 sb.addPermanentWidget(self.account_selector)
1413 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1414 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1416 self.lock_icon = QIcon()
1417 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1418 sb.addPermanentWidget( self.password_button )
1420 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1421 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1422 sb.addPermanentWidget( self.seed_button )
1423 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1424 sb.addPermanentWidget( self.status_button )
1426 run_hook('create_status_bar', (sb,))
1428 self.setStatusBar(sb)
1431 def update_lock_icon(self):
1432 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1433 self.password_button.setIcon( icon )
1436 def update_buttons_on_seed(self):
1437 if not self.wallet.is_watching_only():
1438 self.seed_button.show()
1439 self.password_button.show()
1440 self.send_button.setText(_("Send"))
1442 self.password_button.hide()
1443 self.seed_button.hide()
1444 self.send_button.setText(_("Create unsigned transaction"))
1447 def change_password_dialog(self):
1448 from password_dialog import PasswordDialog
1449 d = PasswordDialog(self.wallet, self)
1451 self.update_lock_icon()
1454 def new_contact_dialog(self):
1457 d.setWindowTitle(_("New Contact"))
1458 vbox = QVBoxLayout(d)
1459 vbox.addWidget(QLabel(_('New Contact')+':'))
1461 grid = QGridLayout()
1464 grid.addWidget(QLabel(_("Address")), 1, 0)
1465 grid.addWidget(line1, 1, 1)
1466 grid.addWidget(QLabel(_("Name")), 2, 0)
1467 grid.addWidget(line2, 2, 1)
1469 vbox.addLayout(grid)
1470 vbox.addLayout(ok_cancel_buttons(d))
1475 address = str(line1.text())
1476 label = unicode(line2.text())
1478 if not is_valid(address):
1479 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1482 self.wallet.add_contact(address)
1484 self.wallet.set_label(address, label)
1486 self.update_contacts_tab()
1487 self.update_history_tab()
1488 self.update_completions()
1489 self.tabs.setCurrentIndex(3)
1493 def new_account_dialog(self, password):
1495 dialog = QDialog(self)
1497 dialog.setWindowTitle(_("New Account"))
1499 vbox = QVBoxLayout()
1500 vbox.addWidget(QLabel(_('Account name')+':'))
1503 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1504 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1509 vbox.addLayout(ok_cancel_buttons(dialog))
1510 dialog.setLayout(vbox)
1514 name = str(e.text())
1517 self.wallet.create_pending_account('1of1', name, password)
1518 self.update_receive_tab()
1519 self.tabs.setCurrentIndex(2)
1523 def show_master_public_key_old(self):
1524 dialog = QDialog(self)
1526 dialog.setWindowTitle(_("Master Public Key"))
1528 main_text = QTextEdit()
1529 main_text.setText(self.wallet.get_master_public_key())
1530 main_text.setReadOnly(True)
1531 main_text.setMaximumHeight(170)
1532 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1534 ok_button = QPushButton(_("OK"))
1535 ok_button.setDefault(True)
1536 ok_button.clicked.connect(dialog.accept)
1538 main_layout = QGridLayout()
1539 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1541 main_layout.addWidget(main_text, 1, 0)
1542 main_layout.addWidget(qrw, 1, 1 )
1544 vbox = QVBoxLayout()
1545 vbox.addLayout(main_layout)
1546 vbox.addLayout(close_button(dialog))
1547 dialog.setLayout(vbox)
1551 def show_master_public_key(self):
1553 if self.wallet.seed_version == 4:
1554 self.show_master_public_key_old()
1557 dialog = QDialog(self)
1559 dialog.setWindowTitle(_("Master Public Keys"))
1561 mpk_text = QTextEdit()
1562 mpk_text.setReadOnly(True)
1563 mpk_text.setMaximumHeight(170)
1564 mpk_qrw = QRCodeWidget()
1566 main_layout = QGridLayout()
1568 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1569 main_layout.addWidget(mpk_text, 1, 1)
1570 main_layout.addWidget(mpk_qrw, 1, 2)
1573 xpub = self.wallet.master_public_keys[str(key)]
1574 mpk_text.setText(xpub)
1575 mpk_qrw.set_addr(xpub)
1578 key_selector = QComboBox()
1579 keys = sorted(self.wallet.master_public_keys.keys())
1580 key_selector.addItems(keys)
1582 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1583 main_layout.addWidget(key_selector, 0, 1)
1584 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1588 vbox = QVBoxLayout()
1589 vbox.addLayout(main_layout)
1590 vbox.addLayout(close_button(dialog))
1592 dialog.setLayout(vbox)
1597 def show_seed_dialog(self, password):
1598 if self.wallet.is_watching_only():
1599 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1602 if self.wallet.seed:
1604 mnemonic = self.wallet.get_mnemonic(password)
1606 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1608 from seed_dialog import SeedDialog
1609 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1613 for k in self.wallet.master_private_keys.keys():
1614 pk = self.wallet.get_master_private_key(k, password)
1616 from seed_dialog import PrivateKeysDialog
1617 d = PrivateKeysDialog(self,l)
1624 def show_qrcode(self, data, title = _("QR code")):
1628 d.setWindowTitle(title)
1629 d.setMinimumSize(270, 300)
1630 vbox = QVBoxLayout()
1631 qrw = QRCodeWidget(data)
1632 vbox.addWidget(qrw, 1)
1633 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1634 hbox = QHBoxLayout()
1637 filename = os.path.join(self.config.path, "qrcode.bmp")
1640 bmp.save_qrcode(qrw.qr, filename)
1641 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1643 def copy_to_clipboard():
1644 bmp.save_qrcode(qrw.qr, filename)
1645 self.app.clipboard().setImage(QImage(filename))
1646 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1648 b = QPushButton(_("Copy"))
1650 b.clicked.connect(copy_to_clipboard)
1652 b = QPushButton(_("Save"))
1654 b.clicked.connect(print_qr)
1656 b = QPushButton(_("Close"))
1658 b.clicked.connect(d.accept)
1661 vbox.addLayout(hbox)
1666 def do_protect(self, func, args):
1667 if self.wallet.use_encryption:
1668 password = self.password_dialog()
1674 if args != (False,):
1675 args = (self,) + args + (password,)
1677 args = (self,password)
1681 def show_public_keys(self, address):
1682 if not address: return
1684 pubkey_list = self.wallet.get_public_keys(address)
1685 except Exception as e:
1686 traceback.print_exc(file=sys.stdout)
1687 self.show_message(str(e))
1691 d.setMinimumSize(600, 200)
1693 vbox = QVBoxLayout()
1694 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1695 vbox.addWidget( QLabel(_("Public key") + ':'))
1697 keys.setReadOnly(True)
1698 keys.setText('\n'.join(pubkey_list))
1699 vbox.addWidget(keys)
1700 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1701 vbox.addLayout(close_button(d))
1706 def show_private_key(self, address, password):
1707 if not address: return
1709 pk_list = self.wallet.get_private_key(address, password)
1710 except Exception as e:
1711 traceback.print_exc(file=sys.stdout)
1712 self.show_message(str(e))
1716 d.setMinimumSize(600, 200)
1718 vbox = QVBoxLayout()
1719 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1720 vbox.addWidget( QLabel(_("Private key") + ':'))
1722 keys.setReadOnly(True)
1723 keys.setText('\n'.join(pk_list))
1724 vbox.addWidget(keys)
1725 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1726 vbox.addLayout(close_button(d))
1732 def do_sign(self, address, message, signature, password):
1733 message = unicode(message.toPlainText())
1734 message = message.encode('utf-8')
1736 sig = self.wallet.sign_message(str(address.text()), message, password)
1737 signature.setText(sig)
1738 except Exception as e:
1739 self.show_message(str(e))
1741 def do_verify(self, address, message, signature):
1742 message = unicode(message.toPlainText())
1743 message = message.encode('utf-8')
1744 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1745 self.show_message(_("Signature verified"))
1747 self.show_message(_("Error: wrong signature"))
1750 def sign_verify_message(self, address=''):
1753 d.setWindowTitle(_('Sign/verify Message'))
1754 d.setMinimumSize(410, 290)
1756 layout = QGridLayout(d)
1758 message_e = QTextEdit()
1759 layout.addWidget(QLabel(_('Message')), 1, 0)
1760 layout.addWidget(message_e, 1, 1)
1761 layout.setRowStretch(2,3)
1763 address_e = QLineEdit()
1764 address_e.setText(address)
1765 layout.addWidget(QLabel(_('Address')), 2, 0)
1766 layout.addWidget(address_e, 2, 1)
1768 signature_e = QTextEdit()
1769 layout.addWidget(QLabel(_('Signature')), 3, 0)
1770 layout.addWidget(signature_e, 3, 1)
1771 layout.setRowStretch(3,1)
1773 hbox = QHBoxLayout()
1775 b = QPushButton(_("Sign"))
1776 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1779 b = QPushButton(_("Verify"))
1780 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1783 b = QPushButton(_("Close"))
1784 b.clicked.connect(d.accept)
1786 layout.addLayout(hbox, 4, 1)
1791 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1793 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1794 message_e.setText(decrypted)
1795 except Exception as e:
1796 self.show_message(str(e))
1799 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1800 message = unicode(message_e.toPlainText())
1801 message = message.encode('utf-8')
1803 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1804 encrypted_e.setText(encrypted)
1805 except Exception as e:
1806 self.show_message(str(e))
1810 def encrypt_message(self, address = ''):
1813 d.setWindowTitle(_('Encrypt/decrypt Message'))
1814 d.setMinimumSize(610, 490)
1816 layout = QGridLayout(d)
1818 message_e = QTextEdit()
1819 layout.addWidget(QLabel(_('Message')), 1, 0)
1820 layout.addWidget(message_e, 1, 1)
1821 layout.setRowStretch(2,3)
1823 pubkey_e = QLineEdit()
1825 pubkey = self.wallet.getpubkeys(address)[0]
1826 pubkey_e.setText(pubkey)
1827 layout.addWidget(QLabel(_('Public key')), 2, 0)
1828 layout.addWidget(pubkey_e, 2, 1)
1830 encrypted_e = QTextEdit()
1831 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1832 layout.addWidget(encrypted_e, 3, 1)
1833 layout.setRowStretch(3,1)
1835 hbox = QHBoxLayout()
1836 b = QPushButton(_("Encrypt"))
1837 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1840 b = QPushButton(_("Decrypt"))
1841 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1844 b = QPushButton(_("Close"))
1845 b.clicked.connect(d.accept)
1848 layout.addLayout(hbox, 4, 1)
1852 def question(self, msg):
1853 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1855 def show_message(self, msg):
1856 QMessageBox.information(self, _('Message'), msg, _('OK'))
1858 def password_dialog(self ):
1861 d.setWindowTitle(_("Enter Password"))
1866 vbox = QVBoxLayout()
1867 msg = _('Please enter your password')
1868 vbox.addWidget(QLabel(msg))
1870 grid = QGridLayout()
1872 grid.addWidget(QLabel(_('Password')), 1, 0)
1873 grid.addWidget(pw, 1, 1)
1874 vbox.addLayout(grid)
1876 vbox.addLayout(ok_cancel_buttons(d))
1879 run_hook('password_dialog', pw, grid, 1)
1880 if not d.exec_(): return
1881 return unicode(pw.text())
1890 def tx_from_text(self, txt):
1891 "json or raw hexadecimal"
1894 tx = Transaction(txt)
1900 tx_dict = json.loads(str(txt))
1901 assert "hex" in tx_dict.keys()
1902 assert "complete" in tx_dict.keys()
1903 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1904 if not tx_dict["complete"]:
1905 assert "input_info" in tx_dict.keys()
1906 input_info = json.loads(tx_dict['input_info'])
1907 tx.add_input_info(input_info)
1912 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1916 def read_tx_from_file(self):
1917 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1921 with open(fileName, "r") as f:
1922 file_content = f.read()
1923 except (ValueError, IOError, os.error), reason:
1924 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1926 return self.tx_from_text(file_content)
1930 def sign_raw_transaction(self, tx, input_info, password):
1931 self.wallet.signrawtransaction(tx, input_info, [], password)
1933 def do_process_from_text(self):
1934 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1937 tx = self.tx_from_text(text)
1939 self.show_transaction(tx)
1941 def do_process_from_file(self):
1942 tx = self.read_tx_from_file()
1944 self.show_transaction(tx)
1946 def do_process_from_txid(self):
1947 from electrum import transaction
1948 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1950 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1952 tx = transaction.Transaction(r)
1954 self.show_transaction(tx)
1956 self.show_message("unknown transaction")
1958 def do_process_from_csvReader(self, csvReader):
1963 for position, row in enumerate(csvReader):
1965 if not is_valid(address):
1966 errors.append((position, address))
1968 amount = Decimal(row[1])
1969 amount = int(100000000*amount)
1970 outputs.append((address, amount))
1971 except (ValueError, IOError, os.error), reason:
1972 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1976 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1977 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1981 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1982 except Exception as e:
1983 self.show_message(str(e))
1986 self.show_transaction(tx)
1988 def do_process_from_csv_file(self):
1989 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1993 with open(fileName, "r") as f:
1994 csvReader = csv.reader(f)
1995 self.do_process_from_csvReader(csvReader)
1996 except (ValueError, IOError, os.error), reason:
1997 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2000 def do_process_from_csv_text(self):
2001 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2002 + _("Format: address, amount. One output per line"), _("Load CSV"))
2005 f = StringIO.StringIO(text)
2006 csvReader = csv.reader(f)
2007 self.do_process_from_csvReader(csvReader)
2012 def do_export_privkeys(self, password):
2013 if not self.wallet.seed:
2014 self.show_message(_("This wallet has no seed"))
2017 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.")))
2020 select_export = _('Select file to export your private keys to')
2021 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
2023 with open(fileName, "w+") as csvfile:
2024 transaction = csv.writer(csvfile)
2025 transaction.writerow(["address", "private_key"])
2027 addresses = self.wallet.addresses(True)
2029 for addr in addresses:
2030 pk = "".join(self.wallet.get_private_key(addr, password))
2031 transaction.writerow(["%34s"%addr,pk])
2033 self.show_message(_("Private keys exported."))
2035 except (IOError, os.error), reason:
2036 export_error_label = _("Electrum was unable to produce a private key-export.")
2037 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2039 except Exception as e:
2040 self.show_message(str(e))
2044 def do_import_labels(self):
2045 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2046 if not labelsFile: return
2048 f = open(labelsFile, 'r')
2051 for key, value in json.loads(data).items():
2052 self.wallet.set_label(key, value)
2053 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2054 except (IOError, os.error), reason:
2055 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2058 def do_export_labels(self):
2059 labels = self.wallet.labels
2061 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2063 with open(fileName, 'w+') as f:
2064 json.dump(labels, f)
2065 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2066 except (IOError, os.error), reason:
2067 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2070 def do_export_history(self):
2071 from lite_window import csv_transaction
2072 csv_transaction(self.wallet)
2076 def do_import_privkey(self, password):
2077 if not self.wallet.imported_keys:
2078 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2079 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2080 + _('Are you sure you understand what you are doing?'), 3, 4)
2083 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2086 text = str(text).split()
2091 addr = self.wallet.import_key(key, password)
2092 except Exception as e:
2098 addrlist.append(addr)
2100 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2102 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2103 self.update_receive_tab()
2104 self.update_history_tab()
2107 def settings_dialog(self):
2109 d.setWindowTitle(_('Electrum Settings'))
2111 vbox = QVBoxLayout()
2112 grid = QGridLayout()
2113 grid.setColumnStretch(0,1)
2115 nz_label = QLabel(_('Display zeros') + ':')
2116 grid.addWidget(nz_label, 0, 0)
2117 nz_e = AmountEdit(None,True)
2118 nz_e.setText("%d"% self.num_zeros)
2119 grid.addWidget(nz_e, 0, 1)
2120 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2121 grid.addWidget(HelpButton(msg), 0, 2)
2122 if not self.config.is_modifiable('num_zeros'):
2123 for w in [nz_e, nz_label]: w.setEnabled(False)
2125 lang_label=QLabel(_('Language') + ':')
2126 grid.addWidget(lang_label, 1, 0)
2127 lang_combo = QComboBox()
2128 from electrum.i18n import languages
2129 lang_combo.addItems(languages.values())
2131 index = languages.keys().index(self.config.get("language",''))
2134 lang_combo.setCurrentIndex(index)
2135 grid.addWidget(lang_combo, 1, 1)
2136 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2137 if not self.config.is_modifiable('language'):
2138 for w in [lang_combo, lang_label]: w.setEnabled(False)
2141 fee_label = QLabel(_('Transaction fee') + ':')
2142 grid.addWidget(fee_label, 2, 0)
2143 fee_e = AmountEdit(self.base_unit)
2144 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2145 grid.addWidget(fee_e, 2, 1)
2146 msg = _('Fee per kilobyte of transaction.') + ' ' \
2147 + _('Recommended value') + ': ' + self.format_amount(20000)
2148 grid.addWidget(HelpButton(msg), 2, 2)
2149 if not self.config.is_modifiable('fee_per_kb'):
2150 for w in [fee_e, fee_label]: w.setEnabled(False)
2152 units = ['BTC', 'mBTC']
2153 unit_label = QLabel(_('Base unit') + ':')
2154 grid.addWidget(unit_label, 3, 0)
2155 unit_combo = QComboBox()
2156 unit_combo.addItems(units)
2157 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2158 grid.addWidget(unit_combo, 3, 1)
2159 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2160 + '\n1BTC=1000mBTC.\n' \
2161 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2163 usechange_cb = QCheckBox(_('Use change addresses'))
2164 usechange_cb.setChecked(self.wallet.use_change)
2165 grid.addWidget(usechange_cb, 4, 0)
2166 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2167 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2169 grid.setRowStretch(5,1)
2171 vbox.addLayout(grid)
2172 vbox.addLayout(ok_cancel_buttons(d))
2176 if not d.exec_(): return
2178 fee = unicode(fee_e.text())
2180 fee = self.read_amount(fee)
2182 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2185 self.wallet.set_fee(fee)
2187 nz = unicode(nz_e.text())
2192 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2195 if self.num_zeros != nz:
2197 self.config.set_key('num_zeros', nz, True)
2198 self.update_history_tab()
2199 self.update_receive_tab()
2201 usechange_result = usechange_cb.isChecked()
2202 if self.wallet.use_change != usechange_result:
2203 self.wallet.use_change = usechange_result
2204 self.wallet.storage.put('use_change', self.wallet.use_change)
2206 unit_result = units[unit_combo.currentIndex()]
2207 if self.base_unit() != unit_result:
2208 self.decimal_point = 8 if unit_result == 'BTC' else 5
2209 self.config.set_key('decimal_point', self.decimal_point, True)
2210 self.update_history_tab()
2211 self.update_status()
2213 need_restart = False
2215 lang_request = languages.keys()[lang_combo.currentIndex()]
2216 if lang_request != self.config.get('language'):
2217 self.config.set_key("language", lang_request, True)
2220 run_hook('close_settings_dialog')
2223 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2226 def run_network_dialog(self):
2227 if not self.network:
2229 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2231 def closeEvent(self, event):
2234 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2235 self.save_column_widths()
2236 self.config.set_key("console-history", self.console.history[-50:], True)
2237 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2241 def plugins_dialog(self):
2242 from electrum.plugins import plugins
2245 d.setWindowTitle(_('Electrum Plugins'))
2248 vbox = QVBoxLayout(d)
2251 scroll = QScrollArea()
2252 scroll.setEnabled(True)
2253 scroll.setWidgetResizable(True)
2254 scroll.setMinimumSize(400,250)
2255 vbox.addWidget(scroll)
2259 w.setMinimumHeight(len(plugins)*35)
2261 grid = QGridLayout()
2262 grid.setColumnStretch(0,1)
2265 def do_toggle(cb, p, w):
2268 if w: w.setEnabled(r)
2270 def mk_toggle(cb, p, w):
2271 return lambda: do_toggle(cb,p,w)
2273 for i, p in enumerate(plugins):
2275 cb = QCheckBox(p.fullname())
2276 cb.setDisabled(not p.is_available())
2277 cb.setChecked(p.is_enabled())
2278 grid.addWidget(cb, i, 0)
2279 if p.requires_settings():
2280 w = p.settings_widget(self)
2281 w.setEnabled( p.is_enabled() )
2282 grid.addWidget(w, i, 1)
2285 cb.clicked.connect(mk_toggle(cb,p,w))
2286 grid.addWidget(HelpButton(p.description()), i, 2)
2288 print_msg(_("Error: cannot display plugin"), p)
2289 traceback.print_exc(file=sys.stdout)
2290 grid.setRowStretch(i+1,1)
2292 vbox.addLayout(close_button(d))
2297 def show_account_details(self, k):
2298 account = self.wallet.accounts[k]
2301 d.setWindowTitle(_('Account Details'))
2304 vbox = QVBoxLayout(d)
2305 name = self.wallet.get_account_name(k)
2306 label = QLabel('Name: ' + name)
2307 vbox.addWidget(label)
2309 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2311 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2313 vbox.addWidget(QLabel(_('Master Public Key:')))
2316 text.setReadOnly(True)
2317 text.setMaximumHeight(170)
2318 vbox.addWidget(text)
2320 mpk_text = '\n'.join( account.get_master_pubkeys() )
2321 text.setText(mpk_text)
2323 vbox.addLayout(close_button(d))