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):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.gui_object = gui_object
111 self.tray = gui_object.tray
112 self.go_lite = gui_object.go_lite
115 self.create_status_bar()
116 self.need_update = threading.Event()
118 self.decimal_point = config.get('decimal_point', 5)
119 self.num_zeros = int(config.get('num_zeros',0))
121 set_language(config.get('language'))
123 self.funds_error = False
124 self.completions = QStringListModel()
126 self.tabs = tabs = QTabWidget(self)
127 self.column_widths = self.config.get("column_widths_2", default_column_widths )
128 tabs.addTab(self.create_history_tab(), _('History') )
129 tabs.addTab(self.create_send_tab(), _('Send') )
130 tabs.addTab(self.create_receive_tab(), _('Receive') )
131 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
132 tabs.addTab(self.create_console_tab(), _('Console') )
133 tabs.setMinimumSize(600, 400)
134 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
135 self.setCentralWidget(tabs)
137 g = self.config.get("winpos-qt",[100, 100, 840, 400])
138 self.setGeometry(g[0], g[1], g[2], g[3])
139 if self.config.get("is_maximized"):
142 self.setWindowIcon(QIcon(":icons/electrum.png"))
145 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
146 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
147 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
148 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
149 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
151 for i in range(tabs.count()):
152 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
154 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
155 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
156 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
157 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
158 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
160 self.history_list.setFocus(True)
164 self.network.register_callback('updated', lambda: self.need_update.set())
165 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
166 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
167 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
168 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
170 # set initial message
171 self.console.showMessage(self.network.banner)
176 def update_account_selector(self):
178 accounts = self.wallet.get_account_names()
179 self.account_selector.clear()
180 if len(accounts) > 1:
181 self.account_selector.addItems([_("All accounts")] + accounts.values())
182 self.account_selector.setCurrentIndex(0)
183 self.account_selector.show()
185 self.account_selector.hide()
188 def load_wallet(self, wallet):
192 self.update_wallet_format()
194 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
195 self.current_account = self.wallet.storage.get("current_account", None)
196 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
197 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
198 self.setWindowTitle( title )
200 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
201 self.notify_transactions()
202 self.update_account_selector()
204 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
205 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
206 self.password_menu.setEnabled(not self.wallet.is_watching_only())
207 self.seed_menu.setEnabled(self.wallet.has_seed())
208 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
209 self.import_menu.setEnabled(self.wallet.can_import())
211 self.update_lock_icon()
212 self.update_buttons_on_seed()
213 self.update_console()
215 run_hook('load_wallet', wallet)
218 def update_wallet_format(self):
219 # convert old-format imported keys
220 if self.wallet.imported_keys:
221 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
223 self.wallet.convert_imported_keys(password)
225 self.show_message("error")
228 def open_wallet(self):
229 wallet_folder = self.wallet.storage.path
230 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
234 storage = WalletStorage({'wallet_path': filename})
235 if not storage.file_exists:
236 self.show_message("file not found "+ filename)
239 self.wallet.stop_threads()
242 wallet = Wallet(storage)
243 wallet.start_threads(self.network)
245 self.load_wallet(wallet)
249 def backup_wallet(self):
251 path = self.wallet.storage.path
252 wallet_folder = os.path.dirname(path)
253 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
257 new_path = os.path.join(wallet_folder, filename)
260 shutil.copy2(path, new_path)
261 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
262 except (IOError, os.error), reason:
263 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
266 def new_wallet(self):
269 wallet_folder = os.path.dirname(self.wallet.storage.path)
270 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
273 filename = os.path.join(wallet_folder, filename)
275 storage = WalletStorage({'wallet_path': filename})
276 if storage.file_exists:
277 QMessageBox.critical(None, "Error", _("File exists"))
280 wizard = installwizard.InstallWizard(self.config, self.network, storage)
281 wallet = wizard.run('new')
283 self.load_wallet(wallet)
287 def init_menubar(self):
290 file_menu = menubar.addMenu(_("&File"))
291 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
292 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
293 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
294 file_menu.addAction(_("&Quit"), self.close)
296 wallet_menu = menubar.addMenu(_("&Wallet"))
297 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
298 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
300 wallet_menu.addSeparator()
302 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
303 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
304 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
306 wallet_menu.addSeparator()
307 labels_menu = wallet_menu.addMenu(_("&Labels"))
308 labels_menu.addAction(_("&Import"), self.do_import_labels)
309 labels_menu.addAction(_("&Export"), self.do_export_labels)
311 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
312 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
313 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
314 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
315 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
317 tools_menu = menubar.addMenu(_("&Tools"))
319 # Settings / Preferences are all reserved keywords in OSX using this as work around
320 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
321 tools_menu.addAction(_("&Network"), self.run_network_dialog)
322 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
323 tools_menu.addSeparator()
324 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
325 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
326 tools_menu.addSeparator()
328 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
329 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
330 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
332 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
333 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
334 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
335 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
337 help_menu = menubar.addMenu(_("&Help"))
338 help_menu.addAction(_("&About"), self.show_about)
339 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
340 help_menu.addSeparator()
341 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
342 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
344 self.setMenuBar(menubar)
346 def show_about(self):
347 QMessageBox.about(self, "Electrum",
348 _("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."))
350 def show_report_bug(self):
351 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
352 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
355 def notify_transactions(self):
356 if not self.network or not self.network.is_connected():
359 print_error("Notifying GUI")
360 if len(self.network.pending_transactions_for_notifications) > 0:
361 # Combine the transactions if there are more then three
362 tx_amount = len(self.network.pending_transactions_for_notifications)
365 for tx in self.network.pending_transactions_for_notifications:
366 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
370 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
371 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
373 self.network.pending_transactions_for_notifications = []
375 for tx in self.network.pending_transactions_for_notifications:
377 self.network.pending_transactions_for_notifications.remove(tx)
378 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
380 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
382 def notify(self, message):
383 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
387 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
388 def getOpenFileName(self, title, filter = ""):
389 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
390 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
391 if fileName and directory != os.path.dirname(fileName):
392 self.config.set_key('io_dir', os.path.dirname(fileName), True)
395 def getSaveFileName(self, title, filename, filter = ""):
396 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
397 path = os.path.join( directory, filename )
398 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
399 if fileName and directory != os.path.dirname(fileName):
400 self.config.set_key('io_dir', os.path.dirname(fileName), True)
404 QMainWindow.close(self)
405 run_hook('close_main_window')
407 def connect_slots(self, sender):
408 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
409 self.previous_payto_e=''
411 def timer_actions(self):
412 if self.need_update.is_set():
414 self.need_update.clear()
415 run_hook('timer_actions')
417 def format_amount(self, x, is_diff=False, whitespaces=False):
418 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
420 def read_amount(self, x):
421 if x in['.', '']: return None
422 p = pow(10, self.decimal_point)
423 return int( p * Decimal(x) )
426 assert self.decimal_point in [5,8]
427 return "BTC" if self.decimal_point == 8 else "mBTC"
430 def update_status(self):
431 if self.network is None or not self.network.is_running():
433 icon = QIcon(":icons/status_disconnected.png")
435 elif self.network.is_connected():
436 if not self.wallet.up_to_date:
437 text = _("Synchronizing...")
438 icon = QIcon(":icons/status_waiting.png")
439 elif self.network.server_lag > 1:
440 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
441 icon = QIcon(":icons/status_lagging.png")
443 c, u = self.wallet.get_account_balance(self.current_account)
444 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
445 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
447 # append fiat balance and price from exchange rate plugin
449 run_hook('get_fiat_status_text', c+u, r)
454 self.tray.setToolTip(text)
455 icon = QIcon(":icons/status_connected.png")
457 text = _("Not connected")
458 icon = QIcon(":icons/status_disconnected.png")
460 self.balance_label.setText(text)
461 self.status_button.setIcon( icon )
464 def update_wallet(self):
466 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
467 self.update_history_tab()
468 self.update_receive_tab()
469 self.update_contacts_tab()
470 self.update_completions()
473 def create_history_tab(self):
474 self.history_list = l = MyTreeWidget(self)
476 for i,width in enumerate(self.column_widths['history']):
477 l.setColumnWidth(i, width)
478 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
479 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
480 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
482 l.customContextMenuRequested.connect(self.create_history_menu)
486 def create_history_menu(self, position):
487 self.history_list.selectedIndexes()
488 item = self.history_list.currentItem()
489 be = self.config.get('block_explorer', 'Blockchain.info')
490 if be == 'Blockchain.info':
491 block_explorer = 'https://blockchain.info/tx/'
492 elif be == 'Blockr.io':
493 block_explorer = 'https://blockr.io/tx/info/'
494 elif be == 'Insight.is':
495 block_explorer = 'http://live.insight.is/tx/'
497 tx_hash = str(item.data(0, Qt.UserRole).toString())
498 if not tx_hash: return
500 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
501 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
502 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
503 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
504 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
507 def show_transaction(self, tx):
508 import transaction_dialog
509 d = transaction_dialog.TxDialog(tx, self)
512 def tx_label_clicked(self, item, column):
513 if column==2 and item.isSelected():
515 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
516 self.history_list.editItem( item, column )
517 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
520 def tx_label_changed(self, item, column):
524 tx_hash = str(item.data(0, Qt.UserRole).toString())
525 tx = self.wallet.transactions.get(tx_hash)
526 text = unicode( item.text(2) )
527 self.wallet.set_label(tx_hash, text)
529 item.setForeground(2, QBrush(QColor('black')))
531 text = self.wallet.get_default_label(tx_hash)
532 item.setText(2, text)
533 item.setForeground(2, QBrush(QColor('gray')))
537 def edit_label(self, is_recv):
538 l = self.receive_list if is_recv else self.contacts_list
539 item = l.currentItem()
540 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
541 l.editItem( item, 1 )
542 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
546 def address_label_clicked(self, item, column, l, column_addr, column_label):
547 if column == column_label and item.isSelected():
548 is_editable = item.data(0, 32).toBool()
551 addr = unicode( item.text(column_addr) )
552 label = unicode( item.text(column_label) )
553 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
554 l.editItem( item, column )
555 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 def address_label_changed(self, item, column, l, column_addr, column_label):
559 if column == column_label:
560 addr = unicode( item.text(column_addr) )
561 text = unicode( item.text(column_label) )
562 is_editable = item.data(0, 32).toBool()
566 changed = self.wallet.set_label(addr, text)
568 self.update_history_tab()
569 self.update_completions()
571 self.current_item_changed(item)
573 run_hook('item_changed', item, column)
576 def current_item_changed(self, a):
577 run_hook('current_item_changed', a)
581 def update_history_tab(self):
583 self.history_list.clear()
584 for item in self.wallet.get_tx_history(self.current_account):
585 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
586 time_str = _("unknown")
589 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
591 time_str = _("error")
594 time_str = 'unverified'
595 icon = QIcon(":icons/unconfirmed.png")
598 icon = QIcon(":icons/unconfirmed.png")
600 icon = QIcon(":icons/clock%d.png"%conf)
602 icon = QIcon(":icons/confirmed.png")
604 if value is not None:
605 v_str = self.format_amount(value, True, whitespaces=True)
609 balance_str = self.format_amount(balance, whitespaces=True)
612 label, is_default_label = self.wallet.get_label(tx_hash)
614 label = _('Pruned transaction outputs')
615 is_default_label = False
617 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
618 item.setFont(2, QFont(MONOSPACE_FONT))
619 item.setFont(3, QFont(MONOSPACE_FONT))
620 item.setFont(4, QFont(MONOSPACE_FONT))
622 item.setForeground(3, QBrush(QColor("#BC1E1E")))
624 item.setData(0, Qt.UserRole, tx_hash)
625 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
627 item.setForeground(2, QBrush(QColor('grey')))
629 item.setIcon(0, icon)
630 self.history_list.insertTopLevelItem(0,item)
633 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
634 run_hook('history_tab_update')
637 def create_send_tab(self):
642 grid.setColumnMinimumWidth(3,300)
643 grid.setColumnStretch(5,1)
646 self.payto_e = QLineEdit()
647 self.payto_help = 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)'))
648 grid.addWidget(QLabel(_('Pay to')), 1, 0)
649 grid.addWidget(self.payto_e, 1, 1, 1, 3)
650 grid.addWidget(self.payto_help, 1, 4)
652 completer = QCompleter()
653 completer.setCaseSensitivity(False)
654 self.payto_e.setCompleter(completer)
655 completer.setModel(self.completions)
657 self.message_e = QLineEdit()
658 self.message_help = 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.'))
659 grid.addWidget(QLabel(_('Description')), 2, 0)
660 grid.addWidget(self.message_e, 2, 1, 1, 3)
661 grid.addWidget(self.message_help, 2, 4)
663 self.from_label = QLabel(_('From'))
664 grid.addWidget(self.from_label, 3, 0)
665 self.from_list = QTreeWidget(self)
666 self.from_list.setColumnCount(2)
667 self.from_list.setColumnWidth(0, 350)
668 self.from_list.setColumnWidth(1, 50)
669 self.from_list.setHeaderHidden (True)
670 self.from_list.setMaximumHeight(80)
671 grid.addWidget(self.from_list, 3, 1, 1, 3)
672 self.set_pay_from([])
674 self.amount_e = AmountEdit(self.base_unit)
675 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
676 + _('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.') \
677 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
678 grid.addWidget(QLabel(_('Amount')), 4, 0)
679 grid.addWidget(self.amount_e, 4, 1, 1, 2)
680 grid.addWidget(self.amount_help, 4, 3)
682 self.fee_e = AmountEdit(self.base_unit)
683 grid.addWidget(QLabel(_('Fee')), 5, 0)
684 grid.addWidget(self.fee_e, 5, 1, 1, 2)
685 grid.addWidget(HelpButton(
686 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
687 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
688 + _('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)
690 run_hook('exchange_rate_button', grid)
692 self.send_button = EnterButton(_("Send"), self.do_send)
693 grid.addWidget(self.send_button, 6, 1)
695 b = EnterButton(_("Clear"),self.do_clear)
696 grid.addWidget(b, 6, 2)
698 self.payto_sig = QLabel('')
699 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
701 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
702 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
711 def entry_changed( is_fee ):
712 self.funds_error = False
714 if self.amount_e.is_shortcut:
715 self.amount_e.is_shortcut = False
716 sendable = self.get_sendable_balance()
717 # there is only one output because we are completely spending inputs
718 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
719 fee = self.wallet.estimated_fee(inputs, 1)
721 self.amount_e.setText( self.format_amount(amount) )
722 self.fee_e.setText( self.format_amount( fee ) )
725 amount = self.read_amount(str(self.amount_e.text()))
726 fee = self.read_amount(str(self.fee_e.text()))
728 if not is_fee: fee = None
731 # assume that there will be 2 outputs (one for change)
732 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
734 self.fee_e.setText( self.format_amount( fee ) )
737 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
741 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
742 self.funds_error = True
743 text = _( "Not enough funds" )
744 c, u = self.wallet.get_frozen_balance()
745 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
747 self.statusBar().showMessage(text)
748 self.amount_e.setPalette(palette)
749 self.fee_e.setPalette(palette)
751 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
752 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
754 run_hook('create_send_tab', grid)
758 def set_pay_from(self, l):
760 self.from_list.clear()
761 self.from_label.setHidden(len(self.pay_from) == 0)
762 self.from_list.setHidden(len(self.pay_from) == 0)
763 for addr in self.pay_from:
764 c, u = self.wallet.get_addr_balance(addr)
765 balance = self.format_amount(c + u)
766 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
769 def update_completions(self):
771 for addr,label in self.wallet.labels.items():
772 if addr in self.wallet.addressbook:
773 l.append( label + ' <' + addr + '>')
775 run_hook('update_completions', l)
776 self.completions.setStringList(l)
780 return lambda s, *args: s.do_protect(func, args)
784 label = unicode( self.message_e.text() )
786 if self.gui_object.payment_request:
787 outputs = self.gui_object.payment_request.outputs
788 amount = self.gui_object.payment_request.get_amount()
791 r = unicode( self.payto_e.text() )
794 # label or alias, with address in brackets
795 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
796 to_address = m.group(2) if m else r
797 if not is_valid(to_address):
798 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
802 amount = self.read_amount(unicode( self.amount_e.text()))
804 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
807 outputs = [(to_address, amount)]
810 fee = self.read_amount(unicode( self.fee_e.text()))
812 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
815 confirm_amount = self.config.get('confirm_amount', 100000000)
816 if amount >= confirm_amount:
817 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
820 confirm_fee = self.config.get('confirm_fee', 100000)
821 if fee >= confirm_fee:
822 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()}):
825 self.send_tx(outputs, fee, label)
830 def send_tx(self, outputs, fee, label, password):
831 self.send_button.setDisabled(True)
833 # first, create an unsigned tx
834 domain = self.get_payment_sources()
836 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
838 except Exception as e:
839 traceback.print_exc(file=sys.stdout)
840 self.show_message(str(e))
841 self.send_button.setDisabled(False)
844 # call hook to see if plugin needs gui interaction
845 run_hook('send_tx', tx)
851 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
852 self.wallet.sign_transaction(tx, keypairs, password)
853 return tx, fee, label
855 def sign_done(tx, fee, label):
857 self.show_message(tx.error)
858 self.send_button.setDisabled(False)
860 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
861 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
862 self.send_button.setDisabled(False)
865 self.wallet.set_label(tx.hash(), label)
867 if not self.gui_object.payment_request:
868 if not tx.is_complete() or self.config.get('show_before_broadcast'):
869 self.show_transaction(tx)
871 self.send_button.setDisabled(False)
874 self.broadcast_transaction(tx)
876 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
877 self.waiting_dialog.start()
881 def broadcast_transaction(self, tx):
883 def broadcast_thread():
884 if self.gui_object.payment_request:
885 refund_address = self.wallet.addresses()[0]
886 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
887 self.gui_object.payment_request = None
889 status, msg = self.wallet.sendtx(tx)
892 def broadcast_done(status, msg):
894 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
897 QMessageBox.warning(self, _('Error'), msg, _('OK'))
898 self.send_button.setDisabled(False)
900 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
901 self.waiting_dialog.start()
905 def prepare_for_payment_request(self):
906 style = "QWidget { background-color:none;border:none;}"
907 self.tabs.setCurrentIndex(1)
908 for e in [self.payto_e, self.amount_e, self.message_e]:
910 e.setStyleSheet(style)
911 for h in [self.payto_help, self.amount_help, self.message_help]:
913 self.payto_e.setText(_("please wait..."))
916 def payment_request_ok(self):
917 self.payto_e.setText(self.gui_object.payment_request.domain)
918 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
919 self.message_e.setText(self.gui_object.payment_request.memo)
921 def payment_request_error(self):
923 self.show_message(self.gui_object.payment_request.error)
926 def set_send(self, address, amount, label, message):
928 if label and self.wallet.labels.get(address) != label:
929 if self.question('Give label "%s" to address %s ?'%(label,address)):
930 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
931 self.wallet.addressbook.append(address)
932 self.wallet.set_label(address, label)
934 self.tabs.setCurrentIndex(1)
935 label = self.wallet.labels.get(address)
936 m_addr = label + ' <'+ address +'>' if label else address
937 self.payto_e.setText(m_addr)
939 self.message_e.setText(message)
941 self.amount_e.setText(amount)
945 self.payto_sig.setVisible(False)
946 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
948 self.set_frozen(e,False)
950 for h in [self.payto_help, self.amount_help, self.message_help]:
953 self.set_pay_from([])
956 def set_frozen(self,entry,frozen):
958 entry.setReadOnly(True)
959 entry.setFrame(False)
961 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
962 entry.setPalette(palette)
964 entry.setReadOnly(False)
967 palette.setColor(entry.backgroundRole(), QColor('white'))
968 entry.setPalette(palette)
971 def set_addrs_frozen(self,addrs,freeze):
973 if not addr: continue
974 if addr in self.wallet.frozen_addresses and not freeze:
975 self.wallet.unfreeze(addr)
976 elif addr not in self.wallet.frozen_addresses and freeze:
977 self.wallet.freeze(addr)
978 self.update_receive_tab()
982 def create_list_tab(self, headers):
983 "generic tab creation method"
984 l = MyTreeWidget(self)
985 l.setColumnCount( len(headers) )
986 l.setHeaderLabels( headers )
996 vbox.addWidget(buttons)
1001 buttons.setLayout(hbox)
1006 def create_receive_tab(self):
1007 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1008 l.setContextMenuPolicy(Qt.CustomContextMenu)
1009 l.customContextMenuRequested.connect(self.create_receive_menu)
1010 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1011 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1012 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1013 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1014 self.receive_list = l
1015 self.receive_buttons_hbox = hbox
1022 def save_column_widths(self):
1023 self.column_widths["receive"] = []
1024 for i in range(self.receive_list.columnCount() -1):
1025 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1027 self.column_widths["history"] = []
1028 for i in range(self.history_list.columnCount() - 1):
1029 self.column_widths["history"].append(self.history_list.columnWidth(i))
1031 self.column_widths["contacts"] = []
1032 for i in range(self.contacts_list.columnCount() - 1):
1033 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1035 self.config.set_key("column_widths_2", self.column_widths, True)
1038 def create_contacts_tab(self):
1039 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1040 l.setContextMenuPolicy(Qt.CustomContextMenu)
1041 l.customContextMenuRequested.connect(self.create_contact_menu)
1042 for i,width in enumerate(self.column_widths['contacts']):
1043 l.setColumnWidth(i, width)
1045 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1046 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1047 self.contacts_list = l
1048 self.contacts_buttons_hbox = hbox
1053 def delete_imported_key(self, addr):
1054 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1055 self.wallet.delete_imported_key(addr)
1056 self.update_receive_tab()
1057 self.update_history_tab()
1059 def edit_account_label(self, k):
1060 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1062 label = unicode(text)
1063 self.wallet.set_label(k,label)
1064 self.update_receive_tab()
1066 def account_set_expanded(self, item, k, b):
1068 self.accounts_expanded[k] = b
1070 def create_account_menu(self, position, k, item):
1072 if item.isExpanded():
1073 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1075 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1076 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1077 if self.wallet.seed_version > 4:
1078 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1079 if self.wallet.account_is_pending(k):
1080 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1081 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1083 def delete_pending_account(self, k):
1084 self.wallet.delete_pending_account(k)
1085 self.update_receive_tab()
1087 def create_receive_menu(self, position):
1088 # fixme: this function apparently has a side effect.
1089 # if it is not called the menu pops up several times
1090 #self.receive_list.selectedIndexes()
1092 selected = self.receive_list.selectedItems()
1093 multi_select = len(selected) > 1
1094 addrs = [unicode(item.text(0)) for item in selected]
1095 if not multi_select:
1096 item = self.receive_list.itemAt(position)
1100 if not is_valid(addr):
1101 k = str(item.data(0,32).toString())
1103 self.create_account_menu(position, k, item)
1105 item.setExpanded(not item.isExpanded())
1109 if not multi_select:
1110 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1111 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1112 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1113 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1114 if not self.wallet.is_watching_only():
1115 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1116 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1117 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1118 if self.wallet.is_imported(addr):
1119 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1121 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1122 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1123 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1124 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1126 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1127 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1129 run_hook('receive_menu', menu, addrs)
1130 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1133 def get_sendable_balance(self):
1134 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1137 def get_payment_sources(self):
1139 return self.pay_from
1141 return self.wallet.get_account_addresses(self.current_account)
1144 def send_from_addresses(self, addrs):
1145 self.set_pay_from( addrs )
1146 self.tabs.setCurrentIndex(1)
1149 def payto(self, addr):
1151 label = self.wallet.labels.get(addr)
1152 m_addr = label + ' <' + addr + '>' if label else addr
1153 self.tabs.setCurrentIndex(1)
1154 self.payto_e.setText(m_addr)
1155 self.amount_e.setFocus()
1158 def delete_contact(self, x):
1159 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1160 self.wallet.delete_contact(x)
1161 self.wallet.set_label(x, None)
1162 self.update_history_tab()
1163 self.update_contacts_tab()
1164 self.update_completions()
1167 def create_contact_menu(self, position):
1168 item = self.contacts_list.itemAt(position)
1171 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1173 addr = unicode(item.text(0))
1174 label = unicode(item.text(1))
1175 is_editable = item.data(0,32).toBool()
1176 payto_addr = item.data(0,33).toString()
1177 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1178 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1179 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1181 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1182 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1184 run_hook('create_contact_menu', menu, item)
1185 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1188 def update_receive_item(self, item):
1189 item.setFont(0, QFont(MONOSPACE_FONT))
1190 address = str(item.data(0,0).toString())
1191 label = self.wallet.labels.get(address,'')
1192 item.setData(1,0,label)
1193 item.setData(0,32, True) # is editable
1195 run_hook('update_receive_item', address, item)
1197 if not self.wallet.is_mine(address): return
1199 c, u = self.wallet.get_addr_balance(address)
1200 balance = self.format_amount(c + u)
1201 item.setData(2,0,balance)
1203 if address in self.wallet.frozen_addresses:
1204 item.setBackgroundColor(0, QColor('lightblue'))
1207 def update_receive_tab(self):
1208 l = self.receive_list
1209 # extend the syntax for consistency
1210 l.addChild = l.addTopLevelItem
1211 l.insertChild = l.insertTopLevelItem
1214 for i,width in enumerate(self.column_widths['receive']):
1215 l.setColumnWidth(i, width)
1217 accounts = self.wallet.get_accounts()
1218 if self.current_account is None:
1219 account_items = sorted(accounts.items())
1221 account_items = [(self.current_account, accounts.get(self.current_account))]
1224 for k, account in account_items:
1226 if len(accounts) > 1:
1227 name = self.wallet.get_account_name(k)
1228 c,u = self.wallet.get_account_balance(k)
1229 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1230 l.addTopLevelItem(account_item)
1231 account_item.setExpanded(self.accounts_expanded.get(k, True))
1232 account_item.setData(0, 32, k)
1236 sequences = [0,1] if account.has_change() else [0]
1237 for is_change in sequences:
1238 if len(sequences) > 1:
1239 name = _("Receiving") if not is_change else _("Change")
1240 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1241 account_item.addChild(seq_item)
1243 seq_item.setExpanded(True)
1245 seq_item = account_item
1247 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1253 for address in account.get_addresses(is_change):
1255 num, is_used = self.wallet.is_used(address)
1258 if gap > self.wallet.gap_limit:
1263 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1264 self.update_receive_item(item)
1266 item.setBackgroundColor(1, QColor('red'))
1270 seq_item.insertChild(0,used_item)
1272 used_item.addChild(item)
1274 seq_item.addChild(item)
1276 # we use column 1 because column 0 may be hidden
1277 l.setCurrentItem(l.topLevelItem(0),1)
1280 def update_contacts_tab(self):
1281 l = self.contacts_list
1284 for address in self.wallet.addressbook:
1285 label = self.wallet.labels.get(address,'')
1286 n = self.wallet.get_num_tx(address)
1287 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1288 item.setFont(0, QFont(MONOSPACE_FONT))
1289 # 32 = label can be edited (bool)
1290 item.setData(0,32, True)
1292 item.setData(0,33, address)
1293 l.addTopLevelItem(item)
1295 run_hook('update_contacts_tab', l)
1296 l.setCurrentItem(l.topLevelItem(0))
1300 def create_console_tab(self):
1301 from console import Console
1302 self.console = console = Console()
1306 def update_console(self):
1307 console = self.console
1308 console.history = self.config.get("console-history",[])
1309 console.history_index = len(console.history)
1311 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1312 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1314 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1316 def mkfunc(f, method):
1317 return lambda *args: apply( f, (method, args, self.password_dialog ))
1319 if m[0]=='_' or m in ['network','wallet']: continue
1320 methods[m] = mkfunc(c._run, m)
1322 console.updateNamespace(methods)
1325 def change_account(self,s):
1326 if s == _("All accounts"):
1327 self.current_account = None
1329 accounts = self.wallet.get_account_names()
1330 for k, v in accounts.items():
1332 self.current_account = k
1333 self.update_history_tab()
1334 self.update_status()
1335 self.update_receive_tab()
1337 def create_status_bar(self):
1340 sb.setFixedHeight(35)
1341 qtVersion = qVersion()
1343 self.balance_label = QLabel("")
1344 sb.addWidget(self.balance_label)
1346 from version_getter import UpdateLabel
1347 self.updatelabel = UpdateLabel(self.config, sb)
1349 self.account_selector = QComboBox()
1350 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1351 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1352 sb.addPermanentWidget(self.account_selector)
1354 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1355 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1357 self.lock_icon = QIcon()
1358 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1359 sb.addPermanentWidget( self.password_button )
1361 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1362 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1363 sb.addPermanentWidget( self.seed_button )
1364 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1365 sb.addPermanentWidget( self.status_button )
1367 run_hook('create_status_bar', (sb,))
1369 self.setStatusBar(sb)
1372 def update_lock_icon(self):
1373 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1374 self.password_button.setIcon( icon )
1377 def update_buttons_on_seed(self):
1378 if self.wallet.has_seed():
1379 self.seed_button.show()
1381 self.seed_button.hide()
1383 if not self.wallet.is_watching_only():
1384 self.password_button.show()
1385 self.send_button.setText(_("Send"))
1387 self.password_button.hide()
1388 self.send_button.setText(_("Create unsigned transaction"))
1391 def change_password_dialog(self):
1392 from password_dialog import PasswordDialog
1393 d = PasswordDialog(self.wallet, self)
1395 self.update_lock_icon()
1398 def new_contact_dialog(self):
1401 d.setWindowTitle(_("New Contact"))
1402 vbox = QVBoxLayout(d)
1403 vbox.addWidget(QLabel(_('New Contact')+':'))
1405 grid = QGridLayout()
1408 grid.addWidget(QLabel(_("Address")), 1, 0)
1409 grid.addWidget(line1, 1, 1)
1410 grid.addWidget(QLabel(_("Name")), 2, 0)
1411 grid.addWidget(line2, 2, 1)
1413 vbox.addLayout(grid)
1414 vbox.addLayout(ok_cancel_buttons(d))
1419 address = str(line1.text())
1420 label = unicode(line2.text())
1422 if not is_valid(address):
1423 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1426 self.wallet.add_contact(address)
1428 self.wallet.set_label(address, label)
1430 self.update_contacts_tab()
1431 self.update_history_tab()
1432 self.update_completions()
1433 self.tabs.setCurrentIndex(3)
1437 def new_account_dialog(self, password):
1439 dialog = QDialog(self)
1441 dialog.setWindowTitle(_("New Account"))
1443 vbox = QVBoxLayout()
1444 vbox.addWidget(QLabel(_('Account name')+':'))
1447 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1448 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1453 vbox.addLayout(ok_cancel_buttons(dialog))
1454 dialog.setLayout(vbox)
1458 name = str(e.text())
1461 self.wallet.create_pending_account(name, password)
1462 self.update_receive_tab()
1463 self.tabs.setCurrentIndex(2)
1468 def show_master_public_keys(self):
1470 dialog = QDialog(self)
1472 dialog.setWindowTitle(_("Master Public Keys"))
1474 main_layout = QGridLayout()
1475 mpk_dict = self.wallet.get_master_public_keys()
1477 for key, value in mpk_dict.items():
1478 main_layout.addWidget(QLabel(key), i, 0)
1479 mpk_text = QTextEdit()
1480 mpk_text.setReadOnly(True)
1481 mpk_text.setMaximumHeight(170)
1482 mpk_text.setText(value)
1483 main_layout.addWidget(mpk_text, i + 1, 0)
1486 vbox = QVBoxLayout()
1487 vbox.addLayout(main_layout)
1488 vbox.addLayout(close_button(dialog))
1490 dialog.setLayout(vbox)
1495 def show_seed_dialog(self, password):
1496 if not self.wallet.has_seed():
1497 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1501 mnemonic = self.wallet.get_mnemonic(password)
1503 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1505 from seed_dialog import SeedDialog
1506 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1511 def show_qrcode(self, data, title = _("QR code")):
1515 d.setWindowTitle(title)
1516 d.setMinimumSize(270, 300)
1517 vbox = QVBoxLayout()
1518 qrw = QRCodeWidget(data)
1519 vbox.addWidget(qrw, 1)
1520 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1521 hbox = QHBoxLayout()
1524 filename = os.path.join(self.config.path, "qrcode.bmp")
1527 bmp.save_qrcode(qrw.qr, filename)
1528 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1530 def copy_to_clipboard():
1531 bmp.save_qrcode(qrw.qr, filename)
1532 self.app.clipboard().setImage(QImage(filename))
1533 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1535 b = QPushButton(_("Copy"))
1537 b.clicked.connect(copy_to_clipboard)
1539 b = QPushButton(_("Save"))
1541 b.clicked.connect(print_qr)
1543 b = QPushButton(_("Close"))
1545 b.clicked.connect(d.accept)
1548 vbox.addLayout(hbox)
1553 def do_protect(self, func, args):
1554 if self.wallet.use_encryption:
1555 password = self.password_dialog()
1561 if args != (False,):
1562 args = (self,) + args + (password,)
1564 args = (self,password)
1568 def show_public_keys(self, address):
1569 if not address: return
1571 pubkey_list = self.wallet.get_public_keys(address)
1572 except Exception as e:
1573 traceback.print_exc(file=sys.stdout)
1574 self.show_message(str(e))
1578 d.setMinimumSize(600, 200)
1580 vbox = QVBoxLayout()
1581 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1582 vbox.addWidget( QLabel(_("Public key") + ':'))
1584 keys.setReadOnly(True)
1585 keys.setText('\n'.join(pubkey_list))
1586 vbox.addWidget(keys)
1587 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1588 vbox.addLayout(close_button(d))
1593 def show_private_key(self, address, password):
1594 if not address: return
1596 pk_list = self.wallet.get_private_key(address, password)
1597 except Exception as e:
1598 traceback.print_exc(file=sys.stdout)
1599 self.show_message(str(e))
1603 d.setMinimumSize(600, 200)
1605 vbox = QVBoxLayout()
1606 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1607 vbox.addWidget( QLabel(_("Private key") + ':'))
1609 keys.setReadOnly(True)
1610 keys.setText('\n'.join(pk_list))
1611 vbox.addWidget(keys)
1612 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1613 vbox.addLayout(close_button(d))
1619 def do_sign(self, address, message, signature, password):
1620 message = unicode(message.toPlainText())
1621 message = message.encode('utf-8')
1623 sig = self.wallet.sign_message(str(address.text()), message, password)
1624 signature.setText(sig)
1625 except Exception as e:
1626 self.show_message(str(e))
1628 def do_verify(self, address, message, signature):
1629 message = unicode(message.toPlainText())
1630 message = message.encode('utf-8')
1631 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1632 self.show_message(_("Signature verified"))
1634 self.show_message(_("Error: wrong signature"))
1637 def sign_verify_message(self, address=''):
1640 d.setWindowTitle(_('Sign/verify Message'))
1641 d.setMinimumSize(410, 290)
1643 layout = QGridLayout(d)
1645 message_e = QTextEdit()
1646 layout.addWidget(QLabel(_('Message')), 1, 0)
1647 layout.addWidget(message_e, 1, 1)
1648 layout.setRowStretch(2,3)
1650 address_e = QLineEdit()
1651 address_e.setText(address)
1652 layout.addWidget(QLabel(_('Address')), 2, 0)
1653 layout.addWidget(address_e, 2, 1)
1655 signature_e = QTextEdit()
1656 layout.addWidget(QLabel(_('Signature')), 3, 0)
1657 layout.addWidget(signature_e, 3, 1)
1658 layout.setRowStretch(3,1)
1660 hbox = QHBoxLayout()
1662 b = QPushButton(_("Sign"))
1663 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1666 b = QPushButton(_("Verify"))
1667 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1670 b = QPushButton(_("Close"))
1671 b.clicked.connect(d.accept)
1673 layout.addLayout(hbox, 4, 1)
1678 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1680 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1681 message_e.setText(decrypted)
1682 except Exception as e:
1683 self.show_message(str(e))
1686 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1687 message = unicode(message_e.toPlainText())
1688 message = message.encode('utf-8')
1690 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1691 encrypted_e.setText(encrypted)
1692 except Exception as e:
1693 self.show_message(str(e))
1697 def encrypt_message(self, address = ''):
1700 d.setWindowTitle(_('Encrypt/decrypt Message'))
1701 d.setMinimumSize(610, 490)
1703 layout = QGridLayout(d)
1705 message_e = QTextEdit()
1706 layout.addWidget(QLabel(_('Message')), 1, 0)
1707 layout.addWidget(message_e, 1, 1)
1708 layout.setRowStretch(2,3)
1710 pubkey_e = QLineEdit()
1712 pubkey = self.wallet.getpubkeys(address)[0]
1713 pubkey_e.setText(pubkey)
1714 layout.addWidget(QLabel(_('Public key')), 2, 0)
1715 layout.addWidget(pubkey_e, 2, 1)
1717 encrypted_e = QTextEdit()
1718 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1719 layout.addWidget(encrypted_e, 3, 1)
1720 layout.setRowStretch(3,1)
1722 hbox = QHBoxLayout()
1723 b = QPushButton(_("Encrypt"))
1724 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1727 b = QPushButton(_("Decrypt"))
1728 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1731 b = QPushButton(_("Close"))
1732 b.clicked.connect(d.accept)
1735 layout.addLayout(hbox, 4, 1)
1739 def question(self, msg):
1740 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1742 def show_message(self, msg):
1743 QMessageBox.information(self, _('Message'), msg, _('OK'))
1745 def password_dialog(self, msg=None):
1748 d.setWindowTitle(_("Enter Password"))
1753 vbox = QVBoxLayout()
1755 msg = _('Please enter your password')
1756 vbox.addWidget(QLabel(msg))
1758 grid = QGridLayout()
1760 grid.addWidget(QLabel(_('Password')), 1, 0)
1761 grid.addWidget(pw, 1, 1)
1762 vbox.addLayout(grid)
1764 vbox.addLayout(ok_cancel_buttons(d))
1767 run_hook('password_dialog', pw, grid, 1)
1768 if not d.exec_(): return
1769 return unicode(pw.text())
1778 def tx_from_text(self, txt):
1779 "json or raw hexadecimal"
1782 tx = Transaction(txt)
1788 tx_dict = json.loads(str(txt))
1789 assert "hex" in tx_dict.keys()
1790 tx = Transaction(tx_dict["hex"])
1791 if tx_dict.has_key("input_info"):
1792 input_info = json.loads(tx_dict['input_info'])
1793 tx.add_input_info(input_info)
1796 traceback.print_exc(file=sys.stdout)
1799 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1803 def read_tx_from_file(self):
1804 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1808 with open(fileName, "r") as f:
1809 file_content = f.read()
1810 except (ValueError, IOError, os.error), reason:
1811 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1813 return self.tx_from_text(file_content)
1817 def sign_raw_transaction(self, tx, input_info, password):
1818 self.wallet.signrawtransaction(tx, input_info, [], password)
1820 def do_process_from_text(self):
1821 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1824 tx = self.tx_from_text(text)
1826 self.show_transaction(tx)
1828 def do_process_from_file(self):
1829 tx = self.read_tx_from_file()
1831 self.show_transaction(tx)
1833 def do_process_from_txid(self):
1834 from electrum import transaction
1835 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1837 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1839 tx = transaction.Transaction(r)
1841 self.show_transaction(tx)
1843 self.show_message("unknown transaction")
1845 def do_process_from_csvReader(self, csvReader):
1850 for position, row in enumerate(csvReader):
1852 if not is_valid(address):
1853 errors.append((position, address))
1855 amount = Decimal(row[1])
1856 amount = int(100000000*amount)
1857 outputs.append((address, amount))
1858 except (ValueError, IOError, os.error), reason:
1859 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1863 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1864 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1868 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1869 except Exception as e:
1870 self.show_message(str(e))
1873 self.show_transaction(tx)
1875 def do_process_from_csv_file(self):
1876 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1880 with open(fileName, "r") as f:
1881 csvReader = csv.reader(f)
1882 self.do_process_from_csvReader(csvReader)
1883 except (ValueError, IOError, os.error), reason:
1884 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1887 def do_process_from_csv_text(self):
1888 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1889 + _("Format: address, amount. One output per line"), _("Load CSV"))
1892 f = StringIO.StringIO(text)
1893 csvReader = csv.reader(f)
1894 self.do_process_from_csvReader(csvReader)
1899 def export_privkeys_dialog(self, password):
1900 if self.wallet.is_watching_only():
1901 self.show_message(_("This is a watching-only wallet"))
1905 d.setWindowTitle(_('Private keys'))
1906 d.setMinimumSize(850, 300)
1907 vbox = QVBoxLayout(d)
1909 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1910 _("Exposing a single private key can compromise your entire wallet!"),
1911 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1912 vbox.addWidget(QLabel(msg))
1918 defaultname = 'electrum-private-keys.csv'
1919 select_msg = _('Select file to export your private keys to')
1920 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1921 vbox.addLayout(hbox)
1923 h, b = ok_cancel_buttons2(d, _('Export'))
1928 addresses = self.wallet.addresses(True)
1930 def privkeys_thread():
1931 for addr in addresses:
1935 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1936 d.emit(SIGNAL('computing_privkeys'))
1937 d.emit(SIGNAL('show_privkeys'))
1939 def show_privkeys():
1940 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1944 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1945 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1946 threading.Thread(target=privkeys_thread).start()
1952 filename = filename_e.text()
1957 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1958 except (IOError, os.error), reason:
1959 export_error_label = _("Electrum was unable to produce a private key-export.")
1960 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1962 except Exception as e:
1963 self.show_message(str(e))
1966 self.show_message(_("Private keys exported."))
1969 def do_export_privkeys(self, fileName, pklist, is_csv):
1970 with open(fileName, "w+") as f:
1972 transaction = csv.writer(f)
1973 transaction.writerow(["address", "private_key"])
1974 for addr, pk in pklist.items():
1975 transaction.writerow(["%34s"%addr,pk])
1978 f.write(json.dumps(pklist, indent = 4))
1981 def do_import_labels(self):
1982 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1983 if not labelsFile: return
1985 f = open(labelsFile, 'r')
1988 for key, value in json.loads(data).items():
1989 self.wallet.set_label(key, value)
1990 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1991 except (IOError, os.error), reason:
1992 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1995 def do_export_labels(self):
1996 labels = self.wallet.labels
1998 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2000 with open(fileName, 'w+') as f:
2001 json.dump(labels, f)
2002 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2003 except (IOError, os.error), reason:
2004 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2007 def export_history_dialog(self):
2010 d.setWindowTitle(_('Export History'))
2011 d.setMinimumSize(400, 200)
2012 vbox = QVBoxLayout(d)
2014 defaultname = os.path.expanduser('~/electrum-history.csv')
2015 select_msg = _('Select file to export your wallet transactions to')
2017 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2018 vbox.addLayout(hbox)
2022 h, b = ok_cancel_buttons2(d, _('Export'))
2027 filename = filename_e.text()
2032 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2033 except (IOError, os.error), reason:
2034 export_error_label = _("Electrum was unable to produce a transaction export.")
2035 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2038 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2041 def do_export_history(self, wallet, fileName, is_csv):
2042 history = wallet.get_tx_history()
2044 for item in history:
2045 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2047 if timestamp is not None:
2049 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2050 except [RuntimeError, TypeError, NameError] as reason:
2051 time_string = "unknown"
2054 time_string = "unknown"
2056 time_string = "pending"
2058 if value is not None:
2059 value_string = format_satoshis(value, True)
2064 fee_string = format_satoshis(fee, True)
2069 label, is_default_label = wallet.get_label(tx_hash)
2070 label = label.encode('utf-8')
2074 balance_string = format_satoshis(balance, False)
2076 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2078 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2080 with open(fileName, "w+") as f:
2082 transaction = csv.writer(f)
2083 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2085 transaction.writerow(line)
2088 f.write(json.dumps(lines, indent = 4))
2091 def sweep_key_dialog(self):
2093 d.setWindowTitle(_('Sweep private keys'))
2094 d.setMinimumSize(600, 300)
2096 vbox = QVBoxLayout(d)
2097 vbox.addWidget(QLabel(_("Enter private keys")))
2099 keys_e = QTextEdit()
2100 keys_e.setTabChangesFocus(True)
2101 vbox.addWidget(keys_e)
2103 h, address_e = address_field(self.wallet.addresses())
2107 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2108 vbox.addLayout(hbox)
2109 button.setEnabled(False)
2112 addr = str(address_e.text())
2113 if bitcoin.is_address(addr):
2117 pk = str(keys_e.toPlainText()).strip()
2118 if Wallet.is_private_key(pk):
2121 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2122 keys_e.textChanged.connect(f)
2123 address_e.textChanged.connect(f)
2127 fee = self.wallet.fee
2128 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2129 self.show_transaction(tx)
2133 def do_import_privkey(self, password):
2134 if not self.wallet.has_imported_keys():
2135 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2136 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2137 + _('Are you sure you understand what you are doing?'), 3, 4)
2140 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2143 text = str(text).split()
2148 addr = self.wallet.import_key(key, password)
2149 except Exception as e:
2155 addrlist.append(addr)
2157 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2159 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2160 self.update_receive_tab()
2161 self.update_history_tab()
2164 def settings_dialog(self):
2166 d.setWindowTitle(_('Electrum Settings'))
2168 vbox = QVBoxLayout()
2169 grid = QGridLayout()
2170 grid.setColumnStretch(0,1)
2172 nz_label = QLabel(_('Display zeros') + ':')
2173 grid.addWidget(nz_label, 0, 0)
2174 nz_e = AmountEdit(None,True)
2175 nz_e.setText("%d"% self.num_zeros)
2176 grid.addWidget(nz_e, 0, 1)
2177 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2178 grid.addWidget(HelpButton(msg), 0, 2)
2179 if not self.config.is_modifiable('num_zeros'):
2180 for w in [nz_e, nz_label]: w.setEnabled(False)
2182 lang_label=QLabel(_('Language') + ':')
2183 grid.addWidget(lang_label, 1, 0)
2184 lang_combo = QComboBox()
2185 from electrum.i18n import languages
2186 lang_combo.addItems(languages.values())
2188 index = languages.keys().index(self.config.get("language",''))
2191 lang_combo.setCurrentIndex(index)
2192 grid.addWidget(lang_combo, 1, 1)
2193 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2194 if not self.config.is_modifiable('language'):
2195 for w in [lang_combo, lang_label]: w.setEnabled(False)
2198 fee_label = QLabel(_('Transaction fee') + ':')
2199 grid.addWidget(fee_label, 2, 0)
2200 fee_e = AmountEdit(self.base_unit)
2201 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2202 grid.addWidget(fee_e, 2, 1)
2203 msg = _('Fee per kilobyte of transaction.') + ' ' \
2204 + _('Recommended value') + ': ' + self.format_amount(20000)
2205 grid.addWidget(HelpButton(msg), 2, 2)
2206 if not self.config.is_modifiable('fee_per_kb'):
2207 for w in [fee_e, fee_label]: w.setEnabled(False)
2209 units = ['BTC', 'mBTC']
2210 unit_label = QLabel(_('Base unit') + ':')
2211 grid.addWidget(unit_label, 3, 0)
2212 unit_combo = QComboBox()
2213 unit_combo.addItems(units)
2214 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2215 grid.addWidget(unit_combo, 3, 1)
2216 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2217 + '\n1BTC=1000mBTC.\n' \
2218 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2220 usechange_cb = QCheckBox(_('Use change addresses'))
2221 usechange_cb.setChecked(self.wallet.use_change)
2222 grid.addWidget(usechange_cb, 4, 0)
2223 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2224 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2226 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2227 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2228 grid.addWidget(block_ex_label, 5, 0)
2229 block_ex_combo = QComboBox()
2230 block_ex_combo.addItems(block_explorers)
2231 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2232 grid.addWidget(block_ex_combo, 5, 1)
2233 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2235 show_tx = self.config.get('show_before_broadcast', False)
2236 showtx_cb = QCheckBox(_('Show before broadcast'))
2237 showtx_cb.setChecked(show_tx)
2238 grid.addWidget(showtx_cb, 6, 0)
2239 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2241 vbox.addLayout(grid)
2243 vbox.addLayout(ok_cancel_buttons(d))
2247 if not d.exec_(): return
2249 fee = unicode(fee_e.text())
2251 fee = self.read_amount(fee)
2253 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2256 self.wallet.set_fee(fee)
2258 nz = unicode(nz_e.text())
2263 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2266 if self.num_zeros != nz:
2268 self.config.set_key('num_zeros', nz, True)
2269 self.update_history_tab()
2270 self.update_receive_tab()
2272 usechange_result = usechange_cb.isChecked()
2273 if self.wallet.use_change != usechange_result:
2274 self.wallet.use_change = usechange_result
2275 self.wallet.storage.put('use_change', self.wallet.use_change)
2277 if showtx_cb.isChecked() != show_tx:
2278 self.config.set_key('show_before_broadcast', not show_tx)
2280 unit_result = units[unit_combo.currentIndex()]
2281 if self.base_unit() != unit_result:
2282 self.decimal_point = 8 if unit_result == 'BTC' else 5
2283 self.config.set_key('decimal_point', self.decimal_point, True)
2284 self.update_history_tab()
2285 self.update_status()
2287 need_restart = False
2289 lang_request = languages.keys()[lang_combo.currentIndex()]
2290 if lang_request != self.config.get('language'):
2291 self.config.set_key("language", lang_request, True)
2294 be_result = block_explorers[block_ex_combo.currentIndex()]
2295 self.config.set_key('block_explorer', be_result, True)
2297 run_hook('close_settings_dialog')
2300 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2303 def run_network_dialog(self):
2304 if not self.network:
2306 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2308 def closeEvent(self, event):
2310 self.config.set_key("is_maximized", self.isMaximized())
2311 if not self.isMaximized():
2313 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2314 self.save_column_widths()
2315 self.config.set_key("console-history", self.console.history[-50:], True)
2316 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2320 def plugins_dialog(self):
2321 from electrum.plugins import plugins
2324 d.setWindowTitle(_('Electrum Plugins'))
2327 vbox = QVBoxLayout(d)
2330 scroll = QScrollArea()
2331 scroll.setEnabled(True)
2332 scroll.setWidgetResizable(True)
2333 scroll.setMinimumSize(400,250)
2334 vbox.addWidget(scroll)
2338 w.setMinimumHeight(len(plugins)*35)
2340 grid = QGridLayout()
2341 grid.setColumnStretch(0,1)
2344 def do_toggle(cb, p, w):
2347 if w: w.setEnabled(r)
2349 def mk_toggle(cb, p, w):
2350 return lambda: do_toggle(cb,p,w)
2352 for i, p in enumerate(plugins):
2354 cb = QCheckBox(p.fullname())
2355 cb.setDisabled(not p.is_available())
2356 cb.setChecked(p.is_enabled())
2357 grid.addWidget(cb, i, 0)
2358 if p.requires_settings():
2359 w = p.settings_widget(self)
2360 w.setEnabled( p.is_enabled() )
2361 grid.addWidget(w, i, 1)
2364 cb.clicked.connect(mk_toggle(cb,p,w))
2365 grid.addWidget(HelpButton(p.description()), i, 2)
2367 print_msg(_("Error: cannot display plugin"), p)
2368 traceback.print_exc(file=sys.stdout)
2369 grid.setRowStretch(i+1,1)
2371 vbox.addLayout(close_button(d))
2376 def show_account_details(self, k):
2377 account = self.wallet.accounts[k]
2380 d.setWindowTitle(_('Account Details'))
2383 vbox = QVBoxLayout(d)
2384 name = self.wallet.get_account_name(k)
2385 label = QLabel('Name: ' + name)
2386 vbox.addWidget(label)
2388 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2390 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2392 vbox.addWidget(QLabel(_('Master Public Key:')))
2395 text.setReadOnly(True)
2396 text.setMaximumHeight(170)
2397 vbox.addWidget(text)
2399 mpk_text = '\n'.join( account.get_master_pubkeys() )
2400 text.setText(mpk_text)
2402 vbox.addLayout(close_button(d))