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', 5)
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.can_create_accounts())
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 Keys"), self.show_master_public_keys)
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.pending_transactions_for_notifications) > 0:
413 # Combine the transactions if there are more then three
414 tx_amount = len(self.network.pending_transactions_for_notifications)
417 for tx in self.network.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.pending_transactions_for_notifications = []
427 for tx in self.network.pending_transactions_for_notifications:
429 self.network.pending_transactions_for_notifications.remove(tx)
430 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
432 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
434 def notify(self, message):
435 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
439 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
440 def getOpenFileName(self, title, filter = ""):
441 directory = self.config.get('io_dir', os.path.expanduser('~'))
442 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
443 if fileName and directory != os.path.dirname(fileName):
444 self.config.set_key('io_dir', os.path.dirname(fileName), True)
447 def getSaveFileName(self, title, filename, filter = ""):
448 directory = self.config.get('io_dir', os.path.expanduser('~'))
449 path = os.path.join( directory, filename )
450 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
451 if fileName and directory != os.path.dirname(fileName):
452 self.config.set_key('io_dir', os.path.dirname(fileName), True)
456 QMainWindow.close(self)
457 run_hook('close_main_window')
459 def connect_slots(self, sender):
460 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
461 self.previous_payto_e=''
463 def timer_actions(self):
464 if self.need_update.is_set():
466 self.need_update.clear()
467 run_hook('timer_actions')
469 def format_amount(self, x, is_diff=False, whitespaces=False):
470 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
472 def read_amount(self, x):
473 if x in['.', '']: return None
474 p = pow(10, self.decimal_point)
475 return int( p * Decimal(x) )
478 assert self.decimal_point in [5,8]
479 return "BTC" if self.decimal_point == 8 else "mBTC"
482 def update_status(self):
483 if self.network is None or not self.network.is_running():
485 icon = QIcon(":icons/status_disconnected.png")
487 elif self.network.is_connected():
488 if not self.wallet.up_to_date:
489 text = _("Synchronizing...")
490 icon = QIcon(":icons/status_waiting.png")
491 elif self.network.server_lag > 1:
492 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
493 icon = QIcon(":icons/status_lagging.png")
495 c, u = self.wallet.get_account_balance(self.current_account)
496 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
497 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
499 # append fiat balance and price from exchange rate plugin
501 run_hook('get_fiat_status_text', c+u, r)
506 self.tray.setToolTip(text)
507 icon = QIcon(":icons/status_connected.png")
509 text = _("Not connected")
510 icon = QIcon(":icons/status_disconnected.png")
512 self.balance_label.setText(text)
513 self.status_button.setIcon( icon )
516 def update_wallet(self):
518 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
519 self.update_history_tab()
520 self.update_receive_tab()
521 self.update_contacts_tab()
522 self.update_completions()
525 def create_history_tab(self):
526 self.history_list = l = MyTreeWidget(self)
528 for i,width in enumerate(self.column_widths['history']):
529 l.setColumnWidth(i, width)
530 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
531 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
532 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
534 l.customContextMenuRequested.connect(self.create_history_menu)
538 def create_history_menu(self, position):
539 self.history_list.selectedIndexes()
540 item = self.history_list.currentItem()
542 tx_hash = str(item.data(0, Qt.UserRole).toString())
543 if not tx_hash: return
545 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
546 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
547 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
548 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
549 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
552 def show_transaction(self, tx):
553 import transaction_dialog
554 d = transaction_dialog.TxDialog(tx, self)
557 def tx_label_clicked(self, item, column):
558 if column==2 and item.isSelected():
560 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
561 self.history_list.editItem( item, column )
562 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
565 def tx_label_changed(self, item, column):
569 tx_hash = str(item.data(0, Qt.UserRole).toString())
570 tx = self.wallet.transactions.get(tx_hash)
571 text = unicode( item.text(2) )
572 self.wallet.set_label(tx_hash, text)
574 item.setForeground(2, QBrush(QColor('black')))
576 text = self.wallet.get_default_label(tx_hash)
577 item.setText(2, text)
578 item.setForeground(2, QBrush(QColor('gray')))
582 def edit_label(self, is_recv):
583 l = self.receive_list if is_recv else self.contacts_list
584 item = l.currentItem()
585 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
586 l.editItem( item, 1 )
587 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
591 def address_label_clicked(self, item, column, l, column_addr, column_label):
592 if column == column_label and item.isSelected():
593 is_editable = item.data(0, 32).toBool()
596 addr = unicode( item.text(column_addr) )
597 label = unicode( item.text(column_label) )
598 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
599 l.editItem( item, column )
600 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
603 def address_label_changed(self, item, column, l, column_addr, column_label):
604 if column == column_label:
605 addr = unicode( item.text(column_addr) )
606 text = unicode( item.text(column_label) )
607 is_editable = item.data(0, 32).toBool()
611 changed = self.wallet.set_label(addr, text)
613 self.update_history_tab()
614 self.update_completions()
616 self.current_item_changed(item)
618 run_hook('item_changed', item, column)
621 def current_item_changed(self, a):
622 run_hook('current_item_changed', a)
626 def update_history_tab(self):
628 self.history_list.clear()
629 for item in self.wallet.get_tx_history(self.current_account):
630 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
631 time_str = _("unknown")
634 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
636 time_str = _("error")
639 time_str = 'unverified'
640 icon = QIcon(":icons/unconfirmed.png")
643 icon = QIcon(":icons/unconfirmed.png")
645 icon = QIcon(":icons/clock%d.png"%conf)
647 icon = QIcon(":icons/confirmed.png")
649 if value is not None:
650 v_str = self.format_amount(value, True, whitespaces=True)
654 balance_str = self.format_amount(balance, whitespaces=True)
657 label, is_default_label = self.wallet.get_label(tx_hash)
659 label = _('Pruned transaction outputs')
660 is_default_label = False
662 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
663 item.setFont(2, QFont(MONOSPACE_FONT))
664 item.setFont(3, QFont(MONOSPACE_FONT))
665 item.setFont(4, QFont(MONOSPACE_FONT))
667 item.setForeground(3, QBrush(QColor("#BC1E1E")))
669 item.setData(0, Qt.UserRole, tx_hash)
670 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
672 item.setForeground(2, QBrush(QColor('grey')))
674 item.setIcon(0, icon)
675 self.history_list.insertTopLevelItem(0,item)
678 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
679 run_hook('history_tab_update')
682 def create_send_tab(self):
687 grid.setColumnMinimumWidth(3,300)
688 grid.setColumnStretch(5,1)
691 self.payto_e = QLineEdit()
692 grid.addWidget(QLabel(_('Pay to')), 1, 0)
693 grid.addWidget(self.payto_e, 1, 1, 1, 3)
695 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
697 completer = QCompleter()
698 completer.setCaseSensitivity(False)
699 self.payto_e.setCompleter(completer)
700 completer.setModel(self.completions)
702 self.message_e = QLineEdit()
703 grid.addWidget(QLabel(_('Description')), 2, 0)
704 grid.addWidget(self.message_e, 2, 1, 1, 3)
705 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
707 self.from_label = QLabel(_('From'))
708 grid.addWidget(self.from_label, 3, 0)
709 self.from_list = QTreeWidget(self)
710 self.from_list.setColumnCount(2)
711 self.from_list.setColumnWidth(0, 350)
712 self.from_list.setColumnWidth(1, 50)
713 self.from_list.setHeaderHidden (True)
714 self.from_list.setMaximumHeight(80)
715 grid.addWidget(self.from_list, 3, 1, 1, 3)
716 self.set_pay_from([])
718 self.amount_e = AmountEdit(self.base_unit)
719 grid.addWidget(QLabel(_('Amount')), 4, 0)
720 grid.addWidget(self.amount_e, 4, 1, 1, 2)
721 grid.addWidget(HelpButton(
722 _('Amount to be sent.') + '\n\n' \
723 + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') \
724 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
726 self.fee_e = AmountEdit(self.base_unit)
727 grid.addWidget(QLabel(_('Fee')), 5, 0)
728 grid.addWidget(self.fee_e, 5, 1, 1, 2)
729 grid.addWidget(HelpButton(
730 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
731 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
732 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 5, 3)
734 run_hook('exchange_rate_button', grid)
736 self.send_button = EnterButton(_("Send"), self.do_send)
737 grid.addWidget(self.send_button, 6, 1)
739 b = EnterButton(_("Clear"),self.do_clear)
740 grid.addWidget(b, 6, 2)
742 self.payto_sig = QLabel('')
743 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
745 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
746 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
755 def entry_changed( is_fee ):
756 self.funds_error = False
758 if self.amount_e.is_shortcut:
759 self.amount_e.is_shortcut = False
760 sendable = self.get_sendable_balance()
761 # there is only one output because we are completely spending inputs
762 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
763 fee = self.wallet.estimated_fee(inputs, 1)
765 self.amount_e.setText( self.format_amount(amount) )
766 self.fee_e.setText( self.format_amount( fee ) )
769 amount = self.read_amount(str(self.amount_e.text()))
770 fee = self.read_amount(str(self.fee_e.text()))
772 if not is_fee: fee = None
775 # assume that there will be 2 outputs (one for change)
776 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
778 self.fee_e.setText( self.format_amount( fee ) )
781 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
785 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
786 self.funds_error = True
787 text = _( "Not enough funds" )
788 c, u = self.wallet.get_frozen_balance()
789 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
791 self.statusBar().showMessage(text)
792 self.amount_e.setPalette(palette)
793 self.fee_e.setPalette(palette)
795 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
796 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
798 run_hook('create_send_tab', grid)
802 def set_pay_from(self, l):
804 self.from_list.clear()
805 self.from_label.setHidden(len(self.pay_from) == 0)
806 self.from_list.setHidden(len(self.pay_from) == 0)
807 for addr in self.pay_from:
808 c, u = self.wallet.get_addr_balance(addr)
809 balance = self.format_amount(c + u)
810 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
813 def update_completions(self):
815 for addr,label in self.wallet.labels.items():
816 if addr in self.wallet.addressbook:
817 l.append( label + ' <' + addr + '>')
819 run_hook('update_completions', l)
820 self.completions.setStringList(l)
824 return lambda s, *args: s.do_protect(func, args)
829 label = unicode( self.message_e.text() )
830 r = unicode( self.payto_e.text() )
833 # label or alias, with address in brackets
834 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
835 to_address = m.group(2) if m else r
837 if not is_valid(to_address):
838 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
842 amount = self.read_amount(unicode( self.amount_e.text()))
844 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
847 fee = self.read_amount(unicode( self.fee_e.text()))
849 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
852 confirm_amount = self.config.get('confirm_amount', 100000000)
853 if amount >= confirm_amount:
854 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
857 confirm_fee = self.config.get('confirm_fee', 100000)
858 if fee >= confirm_fee:
859 if not self.question(_("The fee for this transaction seems unusually high.\nAre you really sure you want to pay %(fee)s in fees?")%{ 'fee' : self.format_amount(fee) + ' '+ self.base_unit()}):
862 self.send_tx(to_address, amount, fee, label)
865 def waiting_dialog(self, message):
867 d.setWindowTitle('Please wait')
869 vbox = QVBoxLayout(d)
876 def send_tx(self, to_address, amount, fee, label, password):
878 # first, create an unsigned tx
879 domain = self.get_payment_sources()
880 outputs = [(to_address, amount)]
882 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
884 except Exception as e:
885 traceback.print_exc(file=sys.stdout)
886 self.show_message(str(e))
889 # call hook to see if plugin needs gui interaction
890 run_hook('send_tx', tx)
896 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
897 self.wallet.sign_transaction(tx, keypairs, password)
898 self.signed_tx_data = (tx, fee, label)
899 self.emit(SIGNAL('send_tx2'))
900 self.tx_wait_dialog = self.waiting_dialog('Signing..')
901 threading.Thread(target=sign_thread).start()
903 # add recipient to addressbook
904 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
905 self.wallet.addressbook.append(to_address)
909 tx, fee, label = self.signed_tx_data
910 self.tx_wait_dialog.accept()
913 self.show_message(tx.error)
916 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
917 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
921 self.wallet.set_label(tx.hash(), label)
923 if not tx.is_complete():
924 self.show_transaction(tx)
928 def broadcast_thread():
929 self.tx_broadcast_result = self.wallet.sendtx(tx)
930 self.emit(SIGNAL('send_tx3'))
931 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
932 threading.Thread(target=broadcast_thread).start()
936 self.tx_broadcast_dialog.accept()
937 status, msg = self.tx_broadcast_result
939 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
941 self.update_contacts_tab()
943 QMessageBox.warning(self, _('Error'), msg, _('OK'))
950 def set_url(self, url):
952 address, amount, label, message, signature, identity, url = util.parse_url(url)
954 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URL'), _('OK'))
958 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
959 elif amount: amount = str(Decimal(amount))
962 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
965 self.mini.set_payment_fields(address, amount)
967 if label and self.wallet.labels.get(address) != label:
968 if self.question('Give label "%s" to address %s ?'%(label,address)):
969 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
970 self.wallet.addressbook.append(address)
971 self.wallet.set_label(address, label)
973 run_hook('set_url', url, self.show_message, self.question)
975 self.tabs.setCurrentIndex(1)
976 label = self.wallet.labels.get(address)
977 m_addr = label + ' <'+ address +'>' if label else address
978 self.payto_e.setText(m_addr)
980 self.message_e.setText(message)
982 self.amount_e.setText(amount)
985 self.set_frozen(self.payto_e,True)
986 self.set_frozen(self.amount_e,True)
987 self.set_frozen(self.message_e,True)
988 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
990 self.payto_sig.setVisible(False)
993 self.payto_sig.setVisible(False)
994 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
996 self.set_frozen(e,False)
998 self.set_pay_from([])
1001 def set_frozen(self,entry,frozen):
1003 entry.setReadOnly(True)
1004 entry.setFrame(False)
1005 palette = QPalette()
1006 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1007 entry.setPalette(palette)
1009 entry.setReadOnly(False)
1010 entry.setFrame(True)
1011 palette = QPalette()
1012 palette.setColor(entry.backgroundRole(), QColor('white'))
1013 entry.setPalette(palette)
1016 def set_addrs_frozen(self,addrs,freeze):
1018 if not addr: continue
1019 if addr in self.wallet.frozen_addresses and not freeze:
1020 self.wallet.unfreeze(addr)
1021 elif addr not in self.wallet.frozen_addresses and freeze:
1022 self.wallet.freeze(addr)
1023 self.update_receive_tab()
1027 def create_list_tab(self, headers):
1028 "generic tab creation method"
1029 l = MyTreeWidget(self)
1030 l.setColumnCount( len(headers) )
1031 l.setHeaderLabels( headers )
1034 vbox = QVBoxLayout()
1041 vbox.addWidget(buttons)
1043 hbox = QHBoxLayout()
1046 buttons.setLayout(hbox)
1051 def create_receive_tab(self):
1052 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1053 l.setContextMenuPolicy(Qt.CustomContextMenu)
1054 l.customContextMenuRequested.connect(self.create_receive_menu)
1055 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1056 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1057 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1058 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1059 self.receive_list = l
1060 self.receive_buttons_hbox = hbox
1067 def save_column_widths(self):
1068 self.column_widths["receive"] = []
1069 for i in range(self.receive_list.columnCount() -1):
1070 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1072 self.column_widths["history"] = []
1073 for i in range(self.history_list.columnCount() - 1):
1074 self.column_widths["history"].append(self.history_list.columnWidth(i))
1076 self.column_widths["contacts"] = []
1077 for i in range(self.contacts_list.columnCount() - 1):
1078 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1080 self.config.set_key("column_widths_2", self.column_widths, True)
1083 def create_contacts_tab(self):
1084 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1085 l.setContextMenuPolicy(Qt.CustomContextMenu)
1086 l.customContextMenuRequested.connect(self.create_contact_menu)
1087 for i,width in enumerate(self.column_widths['contacts']):
1088 l.setColumnWidth(i, width)
1090 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1091 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1092 self.contacts_list = l
1093 self.contacts_buttons_hbox = hbox
1098 def delete_imported_key(self, addr):
1099 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1100 self.wallet.delete_imported_key(addr)
1101 self.update_receive_tab()
1102 self.update_history_tab()
1104 def edit_account_label(self, k):
1105 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1107 label = unicode(text)
1108 self.wallet.set_label(k,label)
1109 self.update_receive_tab()
1111 def account_set_expanded(self, item, k, b):
1113 self.accounts_expanded[k] = b
1115 def create_account_menu(self, position, k, item):
1117 if item.isExpanded():
1118 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1120 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1121 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1122 if self.wallet.seed_version > 4:
1123 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1124 if self.wallet.account_is_pending(k):
1125 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1126 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1128 def delete_pending_account(self, k):
1129 self.wallet.delete_pending_account(k)
1130 self.update_receive_tab()
1132 def create_receive_menu(self, position):
1133 # fixme: this function apparently has a side effect.
1134 # if it is not called the menu pops up several times
1135 #self.receive_list.selectedIndexes()
1137 selected = self.receive_list.selectedItems()
1138 multi_select = len(selected) > 1
1139 addrs = [unicode(item.text(0)) for item in selected]
1140 if not multi_select:
1141 item = self.receive_list.itemAt(position)
1145 if not is_valid(addr):
1146 k = str(item.data(0,32).toString())
1148 self.create_account_menu(position, k, item)
1150 item.setExpanded(not item.isExpanded())
1154 if not multi_select:
1155 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1156 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1157 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1158 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1159 if self.wallet.seed:
1160 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1161 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1162 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1163 if addr in self.wallet.imported_keys:
1164 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1166 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1167 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1168 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1169 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1171 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1172 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1174 run_hook('receive_menu', menu, addrs)
1175 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1178 def get_sendable_balance(self):
1179 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1182 def get_payment_sources(self):
1184 return self.pay_from
1186 return self.wallet.get_account_addresses(self.current_account)
1189 def send_from_addresses(self, addrs):
1190 self.set_pay_from( addrs )
1191 self.tabs.setCurrentIndex(1)
1194 def payto(self, addr):
1196 label = self.wallet.labels.get(addr)
1197 m_addr = label + ' <' + addr + '>' if label else addr
1198 self.tabs.setCurrentIndex(1)
1199 self.payto_e.setText(m_addr)
1200 self.amount_e.setFocus()
1203 def delete_contact(self, x):
1204 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1205 self.wallet.delete_contact(x)
1206 self.wallet.set_label(x, None)
1207 self.update_history_tab()
1208 self.update_contacts_tab()
1209 self.update_completions()
1212 def create_contact_menu(self, position):
1213 item = self.contacts_list.itemAt(position)
1216 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1218 addr = unicode(item.text(0))
1219 label = unicode(item.text(1))
1220 is_editable = item.data(0,32).toBool()
1221 payto_addr = item.data(0,33).toString()
1222 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1223 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1224 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1226 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1227 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1229 run_hook('create_contact_menu', menu, item)
1230 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1233 def update_receive_item(self, item):
1234 item.setFont(0, QFont(MONOSPACE_FONT))
1235 address = str(item.data(0,0).toString())
1236 label = self.wallet.labels.get(address,'')
1237 item.setData(1,0,label)
1238 item.setData(0,32, True) # is editable
1240 run_hook('update_receive_item', address, item)
1242 if not self.wallet.is_mine(address): return
1244 c, u = self.wallet.get_addr_balance(address)
1245 balance = self.format_amount(c + u)
1246 item.setData(2,0,balance)
1248 if address in self.wallet.frozen_addresses:
1249 item.setBackgroundColor(0, QColor('lightblue'))
1252 def update_receive_tab(self):
1253 l = self.receive_list
1256 l.setColumnHidden(2, False)
1257 l.setColumnHidden(3, False)
1258 for i,width in enumerate(self.column_widths['receive']):
1259 l.setColumnWidth(i, width)
1261 if self.current_account is None:
1262 account_items = self.wallet.accounts.items()
1263 elif self.current_account != -1:
1264 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1268 for k, account in account_items:
1269 name = self.wallet.get_account_name(k)
1270 c,u = self.wallet.get_account_balance(k)
1271 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1272 l.addTopLevelItem(account_item)
1273 account_item.setExpanded(self.accounts_expanded.get(k, True))
1274 account_item.setData(0, 32, k)
1276 if not self.wallet.is_seeded(k):
1277 icon = QIcon(":icons/key.png")
1278 account_item.setIcon(0, icon)
1280 for is_change in ([0,1]):
1281 name = _("Receiving") if not is_change else _("Change")
1282 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1283 account_item.addChild(seq_item)
1284 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1286 if not is_change: seq_item.setExpanded(True)
1291 for address in account.get_addresses(is_change):
1292 h = self.wallet.history.get(address,[])
1296 if gap > self.wallet.gap_limit:
1301 c, u = self.wallet.get_addr_balance(address)
1302 num_tx = '*' if h == ['*'] else "%d"%len(h)
1303 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1304 self.update_receive_item(item)
1306 item.setBackgroundColor(1, QColor('red'))
1307 if len(h) > 0 and c == -u:
1309 seq_item.insertChild(0,used_item)
1311 used_item.addChild(item)
1313 seq_item.addChild(item)
1316 for k, addr in self.wallet.get_pending_accounts():
1317 name = self.wallet.labels.get(k,'')
1318 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1319 self.update_receive_item(item)
1320 l.addTopLevelItem(account_item)
1321 account_item.setExpanded(True)
1322 account_item.setData(0, 32, k)
1323 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1324 account_item.addChild(item)
1325 self.update_receive_item(item)
1328 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1329 c,u = self.wallet.get_imported_balance()
1330 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1331 l.addTopLevelItem(account_item)
1332 account_item.setExpanded(True)
1333 for address in self.wallet.imported_keys.keys():
1334 item = QTreeWidgetItem( [ address, '', '', ''] )
1335 self.update_receive_item(item)
1336 account_item.addChild(item)
1339 # we use column 1 because column 0 may be hidden
1340 l.setCurrentItem(l.topLevelItem(0),1)
1343 def update_contacts_tab(self):
1344 l = self.contacts_list
1347 for address in self.wallet.addressbook:
1348 label = self.wallet.labels.get(address,'')
1349 n = self.wallet.get_num_tx(address)
1350 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1351 item.setFont(0, QFont(MONOSPACE_FONT))
1352 # 32 = label can be edited (bool)
1353 item.setData(0,32, True)
1355 item.setData(0,33, address)
1356 l.addTopLevelItem(item)
1358 run_hook('update_contacts_tab', l)
1359 l.setCurrentItem(l.topLevelItem(0))
1363 def create_console_tab(self):
1364 from console import Console
1365 self.console = console = Console()
1369 def update_console(self):
1370 console = self.console
1371 console.history = self.config.get("console-history",[])
1372 console.history_index = len(console.history)
1374 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1375 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1377 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1379 def mkfunc(f, method):
1380 return lambda *args: apply( f, (method, args, self.password_dialog ))
1382 if m[0]=='_' or m in ['network','wallet']: continue
1383 methods[m] = mkfunc(c._run, m)
1385 console.updateNamespace(methods)
1388 def change_account(self,s):
1389 if s == _("All accounts"):
1390 self.current_account = None
1392 accounts = self.wallet.get_account_names()
1393 for k, v in accounts.items():
1395 self.current_account = k
1396 self.update_history_tab()
1397 self.update_status()
1398 self.update_receive_tab()
1400 def create_status_bar(self):
1403 sb.setFixedHeight(35)
1404 qtVersion = qVersion()
1406 self.balance_label = QLabel("")
1407 sb.addWidget(self.balance_label)
1409 from version_getter import UpdateLabel
1410 self.updatelabel = UpdateLabel(self.config, sb)
1412 self.account_selector = QComboBox()
1413 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1414 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1415 sb.addPermanentWidget(self.account_selector)
1417 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1418 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1420 self.lock_icon = QIcon()
1421 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1422 sb.addPermanentWidget( self.password_button )
1424 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1425 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1426 sb.addPermanentWidget( self.seed_button )
1427 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1428 sb.addPermanentWidget( self.status_button )
1430 run_hook('create_status_bar', (sb,))
1432 self.setStatusBar(sb)
1435 def update_lock_icon(self):
1436 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1437 self.password_button.setIcon( icon )
1440 def update_buttons_on_seed(self):
1441 if not self.wallet.is_watching_only():
1442 self.seed_button.show()
1443 self.password_button.show()
1444 self.send_button.setText(_("Send"))
1446 self.password_button.hide()
1447 self.seed_button.hide()
1448 self.send_button.setText(_("Create unsigned transaction"))
1451 def change_password_dialog(self):
1452 from password_dialog import PasswordDialog
1453 d = PasswordDialog(self.wallet, self)
1455 self.update_lock_icon()
1458 def new_contact_dialog(self):
1461 d.setWindowTitle(_("New Contact"))
1462 vbox = QVBoxLayout(d)
1463 vbox.addWidget(QLabel(_('New Contact')+':'))
1465 grid = QGridLayout()
1468 grid.addWidget(QLabel(_("Address")), 1, 0)
1469 grid.addWidget(line1, 1, 1)
1470 grid.addWidget(QLabel(_("Name")), 2, 0)
1471 grid.addWidget(line2, 2, 1)
1473 vbox.addLayout(grid)
1474 vbox.addLayout(ok_cancel_buttons(d))
1479 address = str(line1.text())
1480 label = unicode(line2.text())
1482 if not is_valid(address):
1483 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1486 self.wallet.add_contact(address)
1488 self.wallet.set_label(address, label)
1490 self.update_contacts_tab()
1491 self.update_history_tab()
1492 self.update_completions()
1493 self.tabs.setCurrentIndex(3)
1497 def new_account_dialog(self, password):
1499 dialog = QDialog(self)
1501 dialog.setWindowTitle(_("New Account"))
1503 vbox = QVBoxLayout()
1504 vbox.addWidget(QLabel(_('Account name')+':'))
1507 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1508 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1513 vbox.addLayout(ok_cancel_buttons(dialog))
1514 dialog.setLayout(vbox)
1518 name = str(e.text())
1521 self.wallet.create_pending_account('1of1', name, password)
1522 self.update_receive_tab()
1523 self.tabs.setCurrentIndex(2)
1528 def show_master_public_keys(self):
1530 dialog = QDialog(self)
1532 dialog.setWindowTitle(_("Master Public Keys"))
1534 main_layout = QGridLayout()
1535 mpk_dict = self.wallet.get_master_public_keys()
1537 for key, value in mpk_dict.items():
1538 main_layout.addWidget(QLabel(key), i, 0)
1539 mpk_text = QTextEdit()
1540 mpk_text.setReadOnly(True)
1541 mpk_text.setMaximumHeight(170)
1542 mpk_text.setText(value)
1543 main_layout.addWidget(mpk_text, i + 1, 0)
1546 vbox = QVBoxLayout()
1547 vbox.addLayout(main_layout)
1548 vbox.addLayout(close_button(dialog))
1550 dialog.setLayout(vbox)
1555 def show_seed_dialog(self, password):
1556 if self.wallet.is_watching_only():
1557 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1560 if self.wallet.seed:
1562 mnemonic = self.wallet.get_mnemonic(password)
1564 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1566 from seed_dialog import SeedDialog
1567 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1571 for k in self.wallet.master_private_keys.keys():
1572 pk = self.wallet.get_master_private_key(k, password)
1574 from seed_dialog import PrivateKeysDialog
1575 d = PrivateKeysDialog(self,l)
1582 def show_qrcode(self, data, title = _("QR code")):
1586 d.setWindowTitle(title)
1587 d.setMinimumSize(270, 300)
1588 vbox = QVBoxLayout()
1589 qrw = QRCodeWidget(data)
1590 vbox.addWidget(qrw, 1)
1591 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1592 hbox = QHBoxLayout()
1595 filename = os.path.join(self.config.path, "qrcode.bmp")
1598 bmp.save_qrcode(qrw.qr, filename)
1599 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1601 def copy_to_clipboard():
1602 bmp.save_qrcode(qrw.qr, filename)
1603 self.app.clipboard().setImage(QImage(filename))
1604 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1606 b = QPushButton(_("Copy"))
1608 b.clicked.connect(copy_to_clipboard)
1610 b = QPushButton(_("Save"))
1612 b.clicked.connect(print_qr)
1614 b = QPushButton(_("Close"))
1616 b.clicked.connect(d.accept)
1619 vbox.addLayout(hbox)
1624 def do_protect(self, func, args):
1625 if self.wallet.use_encryption:
1626 password = self.password_dialog()
1632 if args != (False,):
1633 args = (self,) + args + (password,)
1635 args = (self,password)
1639 def show_public_keys(self, address):
1640 if not address: return
1642 pubkey_list = self.wallet.get_public_keys(address)
1643 except Exception as e:
1644 traceback.print_exc(file=sys.stdout)
1645 self.show_message(str(e))
1649 d.setMinimumSize(600, 200)
1651 vbox = QVBoxLayout()
1652 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1653 vbox.addWidget( QLabel(_("Public key") + ':'))
1655 keys.setReadOnly(True)
1656 keys.setText('\n'.join(pubkey_list))
1657 vbox.addWidget(keys)
1658 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1659 vbox.addLayout(close_button(d))
1664 def show_private_key(self, address, password):
1665 if not address: return
1667 pk_list = self.wallet.get_private_key(address, password)
1668 except Exception as e:
1669 traceback.print_exc(file=sys.stdout)
1670 self.show_message(str(e))
1674 d.setMinimumSize(600, 200)
1676 vbox = QVBoxLayout()
1677 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1678 vbox.addWidget( QLabel(_("Private key") + ':'))
1680 keys.setReadOnly(True)
1681 keys.setText('\n'.join(pk_list))
1682 vbox.addWidget(keys)
1683 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1684 vbox.addLayout(close_button(d))
1690 def do_sign(self, address, message, signature, password):
1691 message = unicode(message.toPlainText())
1692 message = message.encode('utf-8')
1694 sig = self.wallet.sign_message(str(address.text()), message, password)
1695 signature.setText(sig)
1696 except Exception as e:
1697 self.show_message(str(e))
1699 def do_verify(self, address, message, signature):
1700 message = unicode(message.toPlainText())
1701 message = message.encode('utf-8')
1702 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1703 self.show_message(_("Signature verified"))
1705 self.show_message(_("Error: wrong signature"))
1708 def sign_verify_message(self, address=''):
1711 d.setWindowTitle(_('Sign/verify Message'))
1712 d.setMinimumSize(410, 290)
1714 layout = QGridLayout(d)
1716 message_e = QTextEdit()
1717 layout.addWidget(QLabel(_('Message')), 1, 0)
1718 layout.addWidget(message_e, 1, 1)
1719 layout.setRowStretch(2,3)
1721 address_e = QLineEdit()
1722 address_e.setText(address)
1723 layout.addWidget(QLabel(_('Address')), 2, 0)
1724 layout.addWidget(address_e, 2, 1)
1726 signature_e = QTextEdit()
1727 layout.addWidget(QLabel(_('Signature')), 3, 0)
1728 layout.addWidget(signature_e, 3, 1)
1729 layout.setRowStretch(3,1)
1731 hbox = QHBoxLayout()
1733 b = QPushButton(_("Sign"))
1734 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1737 b = QPushButton(_("Verify"))
1738 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1741 b = QPushButton(_("Close"))
1742 b.clicked.connect(d.accept)
1744 layout.addLayout(hbox, 4, 1)
1749 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1751 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1752 message_e.setText(decrypted)
1753 except Exception as e:
1754 self.show_message(str(e))
1757 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1758 message = unicode(message_e.toPlainText())
1759 message = message.encode('utf-8')
1761 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1762 encrypted_e.setText(encrypted)
1763 except Exception as e:
1764 self.show_message(str(e))
1768 def encrypt_message(self, address = ''):
1771 d.setWindowTitle(_('Encrypt/decrypt Message'))
1772 d.setMinimumSize(610, 490)
1774 layout = QGridLayout(d)
1776 message_e = QTextEdit()
1777 layout.addWidget(QLabel(_('Message')), 1, 0)
1778 layout.addWidget(message_e, 1, 1)
1779 layout.setRowStretch(2,3)
1781 pubkey_e = QLineEdit()
1783 pubkey = self.wallet.getpubkeys(address)[0]
1784 pubkey_e.setText(pubkey)
1785 layout.addWidget(QLabel(_('Public key')), 2, 0)
1786 layout.addWidget(pubkey_e, 2, 1)
1788 encrypted_e = QTextEdit()
1789 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1790 layout.addWidget(encrypted_e, 3, 1)
1791 layout.setRowStretch(3,1)
1793 hbox = QHBoxLayout()
1794 b = QPushButton(_("Encrypt"))
1795 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1798 b = QPushButton(_("Decrypt"))
1799 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1802 b = QPushButton(_("Close"))
1803 b.clicked.connect(d.accept)
1806 layout.addLayout(hbox, 4, 1)
1810 def question(self, msg):
1811 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1813 def show_message(self, msg):
1814 QMessageBox.information(self, _('Message'), msg, _('OK'))
1816 def password_dialog(self ):
1819 d.setWindowTitle(_("Enter Password"))
1824 vbox = QVBoxLayout()
1825 msg = _('Please enter your password')
1826 vbox.addWidget(QLabel(msg))
1828 grid = QGridLayout()
1830 grid.addWidget(QLabel(_('Password')), 1, 0)
1831 grid.addWidget(pw, 1, 1)
1832 vbox.addLayout(grid)
1834 vbox.addLayout(ok_cancel_buttons(d))
1837 run_hook('password_dialog', pw, grid, 1)
1838 if not d.exec_(): return
1839 return unicode(pw.text())
1848 def tx_from_text(self, txt):
1849 "json or raw hexadecimal"
1852 tx = Transaction(txt)
1858 tx_dict = json.loads(str(txt))
1859 assert "hex" in tx_dict.keys()
1860 assert "complete" in tx_dict.keys()
1861 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1862 if not tx_dict["complete"]:
1863 assert "input_info" in tx_dict.keys()
1864 input_info = json.loads(tx_dict['input_info'])
1865 tx.add_input_info(input_info)
1870 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1874 def read_tx_from_file(self):
1875 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1879 with open(fileName, "r") as f:
1880 file_content = f.read()
1881 except (ValueError, IOError, os.error), reason:
1882 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1884 return self.tx_from_text(file_content)
1888 def sign_raw_transaction(self, tx, input_info, password):
1889 self.wallet.signrawtransaction(tx, input_info, [], password)
1891 def do_process_from_text(self):
1892 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1895 tx = self.tx_from_text(text)
1897 self.show_transaction(tx)
1899 def do_process_from_file(self):
1900 tx = self.read_tx_from_file()
1902 self.show_transaction(tx)
1904 def do_process_from_txid(self):
1905 from electrum import transaction
1906 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1908 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1910 tx = transaction.Transaction(r)
1912 self.show_transaction(tx)
1914 self.show_message("unknown transaction")
1916 def do_process_from_csvReader(self, csvReader):
1921 for position, row in enumerate(csvReader):
1923 if not is_valid(address):
1924 errors.append((position, address))
1926 amount = Decimal(row[1])
1927 amount = int(100000000*amount)
1928 outputs.append((address, amount))
1929 except (ValueError, IOError, os.error), reason:
1930 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1934 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1935 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1939 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1940 except Exception as e:
1941 self.show_message(str(e))
1944 self.show_transaction(tx)
1946 def do_process_from_csv_file(self):
1947 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1951 with open(fileName, "r") as f:
1952 csvReader = csv.reader(f)
1953 self.do_process_from_csvReader(csvReader)
1954 except (ValueError, IOError, os.error), reason:
1955 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1958 def do_process_from_csv_text(self):
1959 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1960 + _("Format: address, amount. One output per line"), _("Load CSV"))
1963 f = StringIO.StringIO(text)
1964 csvReader = csv.reader(f)
1965 self.do_process_from_csvReader(csvReader)
1970 def do_export_privkeys(self, password):
1971 if not self.wallet.seed:
1972 self.show_message(_("This wallet has no seed"))
1975 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.")))
1978 select_export = _('Select file to export your private keys to')
1979 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1981 with open(fileName, "w+") as csvfile:
1982 transaction = csv.writer(csvfile)
1983 transaction.writerow(["address", "private_key"])
1985 addresses = self.wallet.addresses(True)
1987 for addr in addresses:
1988 pk = "".join(self.wallet.get_private_key(addr, password))
1989 transaction.writerow(["%34s"%addr,pk])
1991 self.show_message(_("Private keys exported."))
1993 except (IOError, os.error), reason:
1994 export_error_label = _("Electrum was unable to produce a private key-export.")
1995 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1997 except Exception as e:
1998 self.show_message(str(e))
2002 def do_import_labels(self):
2003 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2004 if not labelsFile: return
2006 f = open(labelsFile, 'r')
2009 for key, value in json.loads(data).items():
2010 self.wallet.set_label(key, value)
2011 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2012 except (IOError, os.error), reason:
2013 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2016 def do_export_labels(self):
2017 labels = self.wallet.labels
2019 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2021 with open(fileName, 'w+') as f:
2022 json.dump(labels, f)
2023 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2024 except (IOError, os.error), reason:
2025 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2028 def do_export_history(self):
2029 wallet = self.wallet
2030 select_export = _('Select file to export your wallet transactions to')
2031 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
2036 with open(fileName, "w+") as csvfile:
2037 transaction = csv.writer(csvfile)
2038 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2039 for item in wallet.get_tx_history():
2040 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2042 if timestamp is not None:
2044 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2045 except [RuntimeError, TypeError, NameError] as reason:
2046 time_string = "unknown"
2049 time_string = "unknown"
2051 time_string = "pending"
2053 if value is not None:
2054 value_string = format_satoshis(value, True)
2059 fee_string = format_satoshis(fee, True)
2064 label, is_default_label = wallet.get_label(tx_hash)
2065 label = label.encode('utf-8')
2069 balance_string = format_satoshis(balance, False)
2070 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2071 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
2073 except (IOError, os.error), reason:
2074 export_error_label = _("Electrum was unable to produce a transaction export.")
2075 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
2080 def do_import_privkey(self, password):
2081 if not self.wallet.imported_keys:
2082 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2083 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2084 + _('Are you sure you understand what you are doing?'), 3, 4)
2087 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2090 text = str(text).split()
2095 addr = self.wallet.import_key(key, password)
2096 except Exception as e:
2102 addrlist.append(addr)
2104 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2106 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2107 self.update_receive_tab()
2108 self.update_history_tab()
2111 def settings_dialog(self):
2113 d.setWindowTitle(_('Electrum Settings'))
2115 vbox = QVBoxLayout()
2116 grid = QGridLayout()
2117 grid.setColumnStretch(0,1)
2119 nz_label = QLabel(_('Display zeros') + ':')
2120 grid.addWidget(nz_label, 0, 0)
2121 nz_e = AmountEdit(None,True)
2122 nz_e.setText("%d"% self.num_zeros)
2123 grid.addWidget(nz_e, 0, 1)
2124 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2125 grid.addWidget(HelpButton(msg), 0, 2)
2126 if not self.config.is_modifiable('num_zeros'):
2127 for w in [nz_e, nz_label]: w.setEnabled(False)
2129 lang_label=QLabel(_('Language') + ':')
2130 grid.addWidget(lang_label, 1, 0)
2131 lang_combo = QComboBox()
2132 from electrum.i18n import languages
2133 lang_combo.addItems(languages.values())
2135 index = languages.keys().index(self.config.get("language",''))
2138 lang_combo.setCurrentIndex(index)
2139 grid.addWidget(lang_combo, 1, 1)
2140 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2141 if not self.config.is_modifiable('language'):
2142 for w in [lang_combo, lang_label]: w.setEnabled(False)
2145 fee_label = QLabel(_('Transaction fee') + ':')
2146 grid.addWidget(fee_label, 2, 0)
2147 fee_e = AmountEdit(self.base_unit)
2148 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2149 grid.addWidget(fee_e, 2, 1)
2150 msg = _('Fee per kilobyte of transaction.') + ' ' \
2151 + _('Recommended value') + ': ' + self.format_amount(20000)
2152 grid.addWidget(HelpButton(msg), 2, 2)
2153 if not self.config.is_modifiable('fee_per_kb'):
2154 for w in [fee_e, fee_label]: w.setEnabled(False)
2156 units = ['BTC', 'mBTC']
2157 unit_label = QLabel(_('Base unit') + ':')
2158 grid.addWidget(unit_label, 3, 0)
2159 unit_combo = QComboBox()
2160 unit_combo.addItems(units)
2161 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2162 grid.addWidget(unit_combo, 3, 1)
2163 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2164 + '\n1BTC=1000mBTC.\n' \
2165 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2167 usechange_cb = QCheckBox(_('Use change addresses'))
2168 usechange_cb.setChecked(self.wallet.use_change)
2169 grid.addWidget(usechange_cb, 4, 0)
2170 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2171 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2173 grid.setRowStretch(5,1)
2175 vbox.addLayout(grid)
2176 vbox.addLayout(ok_cancel_buttons(d))
2180 if not d.exec_(): return
2182 fee = unicode(fee_e.text())
2184 fee = self.read_amount(fee)
2186 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2189 self.wallet.set_fee(fee)
2191 nz = unicode(nz_e.text())
2196 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2199 if self.num_zeros != nz:
2201 self.config.set_key('num_zeros', nz, True)
2202 self.update_history_tab()
2203 self.update_receive_tab()
2205 usechange_result = usechange_cb.isChecked()
2206 if self.wallet.use_change != usechange_result:
2207 self.wallet.use_change = usechange_result
2208 self.wallet.storage.put('use_change', self.wallet.use_change)
2210 unit_result = units[unit_combo.currentIndex()]
2211 if self.base_unit() != unit_result:
2212 self.decimal_point = 8 if unit_result == 'BTC' else 5
2213 self.config.set_key('decimal_point', self.decimal_point, True)
2214 self.update_history_tab()
2215 self.update_status()
2217 need_restart = False
2219 lang_request = languages.keys()[lang_combo.currentIndex()]
2220 if lang_request != self.config.get('language'):
2221 self.config.set_key("language", lang_request, True)
2224 run_hook('close_settings_dialog')
2227 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2230 def run_network_dialog(self):
2231 if not self.network:
2233 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2235 def closeEvent(self, event):
2238 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2239 self.save_column_widths()
2240 self.config.set_key("console-history", self.console.history[-50:], True)
2241 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2245 def plugins_dialog(self):
2246 from electrum.plugins import plugins
2249 d.setWindowTitle(_('Electrum Plugins'))
2252 vbox = QVBoxLayout(d)
2255 scroll = QScrollArea()
2256 scroll.setEnabled(True)
2257 scroll.setWidgetResizable(True)
2258 scroll.setMinimumSize(400,250)
2259 vbox.addWidget(scroll)
2263 w.setMinimumHeight(len(plugins)*35)
2265 grid = QGridLayout()
2266 grid.setColumnStretch(0,1)
2269 def do_toggle(cb, p, w):
2272 if w: w.setEnabled(r)
2274 def mk_toggle(cb, p, w):
2275 return lambda: do_toggle(cb,p,w)
2277 for i, p in enumerate(plugins):
2279 cb = QCheckBox(p.fullname())
2280 cb.setDisabled(not p.is_available())
2281 cb.setChecked(p.is_enabled())
2282 grid.addWidget(cb, i, 0)
2283 if p.requires_settings():
2284 w = p.settings_widget(self)
2285 w.setEnabled( p.is_enabled() )
2286 grid.addWidget(w, i, 1)
2289 cb.clicked.connect(mk_toggle(cb,p,w))
2290 grid.addWidget(HelpButton(p.description()), i, 2)
2292 print_msg(_("Error: cannot display plugin"), p)
2293 traceback.print_exc(file=sys.stdout)
2294 grid.setRowStretch(i+1,1)
2296 vbox.addLayout(close_button(d))
2301 def show_account_details(self, k):
2302 account = self.wallet.accounts[k]
2305 d.setWindowTitle(_('Account Details'))
2308 vbox = QVBoxLayout(d)
2309 name = self.wallet.get_account_name(k)
2310 label = QLabel('Name: ' + name)
2311 vbox.addWidget(label)
2313 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2315 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2317 vbox.addWidget(QLabel(_('Master Public Key:')))
2320 text.setReadOnly(True)
2321 text.setMaximumHeight(170)
2322 vbox.addWidget(text)
2324 mpk_text = '\n'.join( account.get_master_pubkeys() )
2325 text.setText(mpk_text)
2327 vbox.addLayout(close_button(d))