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
30 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
32 from PyQt4.QtGui import *
33 from PyQt4.QtCore import *
34 import PyQt4.QtCore as QtCore
36 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
41 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
43 from electrum.wallet import format_satoshis
44 from electrum import Transaction
45 from electrum import mnemonic
46 from electrum import util, bitcoin, commands, Interface, Wallet
47 from electrum import SimpleConfig, Wallet, WalletStorage
50 from electrum import bmp, pyqrnative
53 from amountedit import AmountEdit
54 from network_dialog import NetworkDialog
55 from qrcodewidget import QRCodeWidget
57 from decimal import Decimal
65 if platform.system() == 'Windows':
66 MONOSPACE_FONT = 'Lucida Console'
67 elif platform.system() == 'Darwin':
68 MONOSPACE_FONT = 'Monaco'
70 MONOSPACE_FONT = 'monospace'
72 from electrum import ELECTRUM_VERSION
78 class MyTreeWidget(QTreeWidget):
79 def __init__(self, parent):
80 QTreeWidget.__init__(self, parent)
83 for i in range(0,self.viewport().height()/5):
84 if self.itemAt(QPoint(0,i*5)) == item:
89 if self.itemAt(QPoint(0,i*5 + j)) != item:
91 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
93 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
98 class StatusBarButton(QPushButton):
99 def __init__(self, icon, tooltip, func):
100 QPushButton.__init__(self, icon, '')
101 self.setToolTip(tooltip)
103 self.setMaximumWidth(25)
104 self.clicked.connect(func)
106 self.setIconSize(QSize(25,25))
108 def keyPressEvent(self, e):
109 if e.key() == QtCore.Qt.Key_Return:
121 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
123 class ElectrumWindow(QMainWindow):
124 def changeEvent(self, event):
125 flags = self.windowFlags();
126 if event and event.type() == QtCore.QEvent.WindowStateChange:
127 if self.windowState() & QtCore.Qt.WindowMinimized:
128 self.build_menu(True)
129 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
130 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
131 # Electrum from closing.
132 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
133 # self.setWindowFlags(flags & ~Qt.ToolTip)
134 elif event.oldState() & QtCore.Qt.WindowMinimized:
135 self.build_menu(False)
136 #self.setWindowFlags(flags | Qt.ToolTip)
138 def build_menu(self, is_hidden = False):
140 if self.isMinimized():
141 m.addAction(_("Show"), self.showNormal)
143 m.addAction(_("Hide"), self.showMinimized)
146 m.addAction(_("Exit Electrum"), self.close)
147 self.tray.setContextMenu(m)
149 def tray_activated(self, reason):
150 if reason == QSystemTrayIcon.DoubleClick:
154 def __init__(self, config, network, go_lite):
155 QMainWindow.__init__(self)
158 self.network = network
159 self.go_lite = go_lite
162 self._close_electrum = False
164 self.current_account = self.config.get("current_account", None)
166 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
167 self.tray = QSystemTrayIcon(self.icon, self)
168 self.tray.setToolTip('Electrum')
169 self.tray.activated.connect(self.tray_activated)
173 self.create_status_bar()
175 self.need_update = threading.Event()
177 self.expert_mode = config.get('classic_expert_mode', False)
178 self.decimal_point = config.get('decimal_point', 8)
179 self.num_zeros = int(config.get('num_zeros',0))
181 set_language(config.get('language'))
183 self.funds_error = False
184 self.completions = QStringListModel()
186 self.tabs = tabs = QTabWidget(self)
187 self.column_widths = self.config.get("column_widths", default_column_widths )
188 tabs.addTab(self.create_history_tab(), _('History') )
189 tabs.addTab(self.create_send_tab(), _('Send') )
190 tabs.addTab(self.create_receive_tab(), _('Receive') )
191 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
192 tabs.addTab(self.create_console_tab(), _('Console') )
193 tabs.setMinimumSize(600, 400)
194 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
195 self.setCentralWidget(tabs)
197 g = self.config.get("winpos-qt",[100, 100, 840, 400])
198 self.setGeometry(g[0], g[1], g[2], g[3])
202 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
203 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
204 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
205 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
206 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
208 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
209 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
210 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
212 self.history_list.setFocus(True)
214 self.exchanger = exchange_rate.Exchanger(self)
215 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
217 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
218 if platform.system() == 'Windows':
219 n = 3 if self.wallet.seed else 2
220 tabs.setCurrentIndex (n)
221 tabs.setCurrentIndex (0)
223 # plugins that need to change the GUI do it here
224 self.run_hook('init')
229 def load_wallet(self, wallet):
233 self.network.register_callback('updated', lambda: self.need_update.set())
234 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
235 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
236 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
237 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
238 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
239 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
240 self.setWindowTitle( title )
242 # set initial message
243 self.console.showMessage(self.network.banner)
244 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
245 self.notify_transactions()
248 accounts = self.wallet.get_account_names()
249 self.account_selector.clear()
250 if len(accounts) > 1:
251 self.account_selector.addItems([_("All accounts")] + accounts.values())
252 self.account_selector.setCurrentIndex(0)
253 self.account_selector.show()
255 self.account_selector.hide()
257 self.new_account.setEnabled(self.wallet.seed_version>4)
259 self.update_lock_icon()
260 self.update_buttons_on_seed()
261 self.update_console()
263 self.run_hook('load_wallet')
266 def select_wallet_file(self):
267 wallet_folder = self.wallet.storage.path
268 re.sub("(\/\w*.dat)$", "", wallet_folder)
269 file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
273 def open_wallet(self):
275 filename = self.select_wallet_file()
279 storage = WalletStorage({'wallet_path': filename})
280 if not storage.file_exists:
281 self.show_message("file not found "+ filename)
284 self.wallet.stop_threads()
287 wallet = Wallet(storage)
288 wallet.start_threads(network)
290 self.load_wallet(wallet)
293 def new_wallet(self):
296 wallet_folder = self.wallet.storage.path
297 re.sub("(\/\w*.dat)$", "", wallet_folder)
298 filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
300 storage = WalletStorage({'wallet_path': filename})
301 assert not storage.file_exists
303 wizard = installwizard.InstallWizard(self.config, self.network, storage)
304 wallet = wizard.run()
306 self.load_wallet(wallet)
310 def init_menubar(self):
313 file_menu = menubar.addMenu(_("&File"))
314 open_wallet_action = file_menu.addAction(_("&Open"))
315 open_wallet_action.triggered.connect(self.open_wallet)
317 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
318 new_wallet_action.triggered.connect(self.new_wallet)
320 wallet_backup = file_menu.addAction(_("&Copy"))
321 wallet_backup.triggered.connect(lambda: backup_wallet(self.wallet.storage.path))
323 quit_item = file_menu.addAction(_("&Close"))
324 quit_item.triggered.connect(self.close)
326 wallet_menu = menubar.addMenu(_("&Wallet"))
328 # Settings / Preferences are all reserved keywords in OSX using this as work around
329 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
330 preferences_menu = wallet_menu.addAction(preferences_name)
331 preferences_menu.triggered.connect(self.settings_dialog)
333 wallet_menu.addSeparator()
335 raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
337 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
338 raw_transaction_file.triggered.connect(self.do_process_from_file)
340 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
341 raw_transaction_text.triggered.connect(self.do_process_from_text)
343 csv_transaction_menu = wallet_menu.addMenu(_("&Load CSV transaction"))
345 csv_transaction_file = csv_transaction_menu.addAction(_("&From file"))
346 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
348 csv_transaction_text = csv_transaction_menu.addAction(_("&From text"))
349 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
351 wallet_menu.addSeparator()
353 show_menu = wallet_menu.addMenu(_("Show"))
355 #if self.wallet.seed:
356 show_seed = show_menu.addAction(_("&Seed"))
357 show_seed.triggered.connect(self.show_seed_dialog)
359 show_mpk = show_menu.addAction(_("&Master Public Key"))
360 show_mpk.triggered.connect(self.show_master_public_key)
362 wallet_menu.addSeparator()
363 new_contact = wallet_menu.addAction(_("&New contact"))
364 new_contact.triggered.connect(self.new_contact_dialog)
366 self.new_account = wallet_menu.addAction(_("&New account"))
367 self.new_account.triggered.connect(self.new_account_dialog)
369 import_menu = menubar.addMenu(_("&Import"))
370 in_labels = import_menu.addAction(_("&Labels"))
371 in_labels.triggered.connect(self.do_import_labels)
373 in_private_keys = import_menu.addAction(_("&Private keys"))
374 in_private_keys.triggered.connect(self.do_import_privkey)
376 export_menu = menubar.addMenu(_("&Export"))
377 ex_private_keys = export_menu.addAction(_("&Private keys"))
378 ex_private_keys.triggered.connect(self.do_export_privkeys)
380 ex_history = export_menu.addAction(_("&History"))
381 ex_history.triggered.connect(self.do_export_history)
383 ex_labels = export_menu.addAction(_("&Labels"))
384 ex_labels.triggered.connect(self.do_export_labels)
386 help_menu = menubar.addMenu(_("&Help"))
387 show_about = help_menu.addAction(_("&About"))
388 show_about.triggered.connect(self.show_about)
389 web_open = help_menu.addAction(_("&Official website"))
390 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
392 help_menu.addSeparator()
393 doc_open = help_menu.addAction(_("&Documentation"))
394 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
395 report_bug = help_menu.addAction(_("&Report Bug"))
396 report_bug.triggered.connect(self.show_report_bug)
398 self.setMenuBar(menubar)
400 def show_about(self):
401 QMessageBox.about(self, "Electrum",
402 _("Version")+" %s" % (self.wallet.electrum_version) + "\n\n" + _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
404 def show_report_bug(self):
405 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
406 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
409 def notify_transactions(self):
410 print_error("Notifying GUI")
411 if len(self.network.interface.pending_transactions_for_notifications) > 0:
412 # Combine the transactions if there are more then three
413 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
416 for tx in self.network.interface.pending_transactions_for_notifications:
417 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
421 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
422 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
424 self.network.interface.pending_transactions_for_notifications = []
426 for tx in self.network.interface.pending_transactions_for_notifications:
428 self.network.interface.pending_transactions_for_notifications.remove(tx)
429 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
431 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
433 def notify(self, message):
434 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
437 def init_plugins(self):
438 import imp, pkgutil, __builtin__
439 if __builtin__.use_local_modules:
440 fp, pathname, description = imp.find_module('plugins')
441 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
442 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
443 imp.load_module('electrum_plugins', fp, pathname, description)
444 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
446 import electrum_plugins
447 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
448 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
451 for name, p in zip(plugin_names, plugins):
453 self.plugins.append( p.Plugin(self, name) )
455 print_msg("Error:cannot initialize plugin",p)
456 traceback.print_exc(file=sys.stdout)
459 def run_hook(self, name, *args):
460 for p in self.plugins:
461 if not p.is_enabled():
470 print_error("Plugin error")
471 traceback.print_exc(file=sys.stdout)
477 def set_label(self, name, text = None):
479 old_text = self.wallet.labels.get(name)
482 self.wallet.labels[name] = text
483 self.wallet.storage.put('labels', self.wallet.labels)
487 self.wallet.labels.pop(name)
489 self.run_hook('set_label', name, text, changed)
493 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
494 def getOpenFileName(self, title, filter = None):
495 directory = self.config.get('io_dir', os.path.expanduser('~'))
496 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
497 if fileName and directory != os.path.dirname(fileName):
498 self.config.set_key('io_dir', os.path.dirname(fileName), True)
501 def getSaveFileName(self, title, filename, filter = None):
502 directory = self.config.get('io_dir', os.path.expanduser('~'))
503 path = os.path.join( directory, filename )
504 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
505 if fileName and directory != os.path.dirname(fileName):
506 self.config.set_key('io_dir', os.path.dirname(fileName), True)
510 QMainWindow.close(self)
511 self.run_hook('close_main_window')
513 def connect_slots(self, sender):
514 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
515 self.previous_payto_e=''
517 def timer_actions(self):
518 if self.need_update.is_set():
520 self.need_update.clear()
521 self.run_hook('timer_actions')
523 def format_amount(self, x, is_diff=False, whitespaces=False):
524 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
526 def read_amount(self, x):
527 if x in['.', '']: return None
528 p = pow(10, self.decimal_point)
529 return int( p * Decimal(x) )
532 assert self.decimal_point in [5,8]
533 return "BTC" if self.decimal_point == 8 else "mBTC"
535 def update_status(self):
536 if self.network.interface and self.network.interface.is_connected:
537 if not self.wallet.up_to_date:
538 text = _("Synchronizing...")
539 icon = QIcon(":icons/status_waiting.png")
541 c, u = self.wallet.get_account_balance(self.current_account)
542 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
543 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
544 text += self.create_quote_text(Decimal(c+u)/100000000)
545 self.tray.setToolTip(text)
546 icon = QIcon(":icons/status_connected.png")
548 text = _("Not connected")
549 icon = QIcon(":icons/status_disconnected.png")
551 self.balance_label.setText(text)
552 self.status_button.setIcon( icon )
554 def update_wallet(self):
556 if self.wallet.up_to_date or not self.network.interface.is_connected:
557 self.update_history_tab()
558 self.update_receive_tab()
559 self.update_contacts_tab()
560 self.update_completions()
563 def create_quote_text(self, btc_balance):
564 quote_currency = self.config.get("currency", "None")
565 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
566 if quote_balance is None:
569 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
572 def create_history_tab(self):
573 self.history_list = l = MyTreeWidget(self)
575 for i,width in enumerate(self.column_widths['history']):
576 l.setColumnWidth(i, width)
577 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
578 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
579 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
581 l.setContextMenuPolicy(Qt.CustomContextMenu)
582 l.customContextMenuRequested.connect(self.create_history_menu)
586 def create_history_menu(self, position):
587 self.history_list.selectedIndexes()
588 item = self.history_list.currentItem()
590 tx_hash = str(item.data(0, Qt.UserRole).toString())
591 if not tx_hash: return
593 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
594 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
595 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
596 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
599 def show_tx_details(self, tx):
600 dialog = QDialog(self)
602 dialog.setWindowTitle(_("Transaction Details"))
604 dialog.setLayout(vbox)
605 dialog.setMinimumSize(600,300)
608 if tx_hash in self.wallet.transactions.keys():
609 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
610 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
612 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
618 vbox.addWidget(QLabel("Transaction ID:"))
619 e = QLineEdit(tx_hash)
623 vbox.addWidget(QLabel("Date: %s"%time_str))
624 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
627 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
628 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
630 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
631 vbox.addWidget(QLabel("Transaction fee: unknown"))
633 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
635 vbox.addWidget( self.generate_transaction_information_widget(tx) )
637 ok_button = QPushButton(_("Close"))
638 ok_button.setDefault(True)
639 ok_button.clicked.connect(dialog.accept)
643 hbox.addWidget(ok_button)
647 def tx_label_clicked(self, item, column):
648 if column==2 and item.isSelected():
650 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
651 self.history_list.editItem( item, column )
652 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
655 def tx_label_changed(self, item, column):
659 tx_hash = str(item.data(0, Qt.UserRole).toString())
660 tx = self.wallet.transactions.get(tx_hash)
661 text = unicode( item.text(2) )
662 self.set_label(tx_hash, text)
664 item.setForeground(2, QBrush(QColor('black')))
666 text = self.wallet.get_default_label(tx_hash)
667 item.setText(2, text)
668 item.setForeground(2, QBrush(QColor('gray')))
672 def edit_label(self, is_recv):
673 l = self.receive_list if is_recv else self.contacts_list
674 item = l.currentItem()
675 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
676 l.editItem( item, 1 )
677 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
681 def address_label_clicked(self, item, column, l, column_addr, column_label):
682 if column == column_label and item.isSelected():
683 is_editable = item.data(0, 32).toBool()
686 addr = unicode( item.text(column_addr) )
687 label = unicode( item.text(column_label) )
688 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
689 l.editItem( item, column )
690 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
693 def address_label_changed(self, item, column, l, column_addr, column_label):
694 if column == column_label:
695 addr = unicode( item.text(column_addr) )
696 text = unicode( item.text(column_label) )
697 is_editable = item.data(0, 32).toBool()
701 changed = self.set_label(addr, text)
703 self.update_history_tab()
704 self.update_completions()
706 self.current_item_changed(item)
708 self.run_hook('item_changed', item, column)
711 def current_item_changed(self, a):
712 self.run_hook('current_item_changed', a)
716 def update_history_tab(self):
718 self.history_list.clear()
719 for item in self.wallet.get_tx_history(self.current_account):
720 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
723 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
728 time_str = 'unverified'
729 icon = QIcon(":icons/unconfirmed.png")
732 icon = QIcon(":icons/unconfirmed.png")
734 icon = QIcon(":icons/clock%d.png"%conf)
736 icon = QIcon(":icons/confirmed.png")
738 if value is not None:
739 v_str = self.format_amount(value, True, whitespaces=True)
743 balance_str = self.format_amount(balance, whitespaces=True)
746 label, is_default_label = self.wallet.get_label(tx_hash)
748 label = _('Pruned transaction outputs')
749 is_default_label = False
751 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
752 item.setFont(2, QFont(MONOSPACE_FONT))
753 item.setFont(3, QFont(MONOSPACE_FONT))
754 item.setFont(4, QFont(MONOSPACE_FONT))
756 item.setForeground(3, QBrush(QColor("#BC1E1E")))
758 item.setData(0, Qt.UserRole, tx_hash)
759 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
761 item.setForeground(2, QBrush(QColor('grey')))
763 item.setIcon(0, icon)
764 self.history_list.insertTopLevelItem(0,item)
767 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
770 def create_send_tab(self):
775 grid.setColumnMinimumWidth(3,300)
776 grid.setColumnStretch(5,1)
779 self.payto_e = QLineEdit()
780 grid.addWidget(QLabel(_('Pay to')), 1, 0)
781 grid.addWidget(self.payto_e, 1, 1, 1, 3)
783 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)
785 completer = QCompleter()
786 completer.setCaseSensitivity(False)
787 self.payto_e.setCompleter(completer)
788 completer.setModel(self.completions)
790 self.message_e = QLineEdit()
791 grid.addWidget(QLabel(_('Description')), 2, 0)
792 grid.addWidget(self.message_e, 2, 1, 1, 3)
793 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)
795 self.amount_e = AmountEdit(self.base_unit)
796 grid.addWidget(QLabel(_('Amount')), 3, 0)
797 grid.addWidget(self.amount_e, 3, 1, 1, 2)
798 grid.addWidget(HelpButton(
799 _('Amount to be sent.') + '\n\n' \
800 + _('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.') \
801 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
803 self.fee_e = AmountEdit(self.base_unit)
804 grid.addWidget(QLabel(_('Fee')), 4, 0)
805 grid.addWidget(self.fee_e, 4, 1, 1, 2)
806 grid.addWidget(HelpButton(
807 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
808 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
809 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
812 self.send_button = EnterButton(_("Send"), self.do_send)
813 grid.addWidget(self.send_button, 6, 1)
815 b = EnterButton(_("Clear"),self.do_clear)
816 grid.addWidget(b, 6, 2)
818 self.payto_sig = QLabel('')
819 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
821 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
822 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
831 def entry_changed( is_fee ):
832 self.funds_error = False
834 if self.amount_e.is_shortcut:
835 self.amount_e.is_shortcut = False
836 c, u = self.wallet.get_account_balance(self.current_account)
837 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
838 fee = self.wallet.estimated_fee(inputs)
840 self.amount_e.setText( self.format_amount(amount) )
841 self.fee_e.setText( self.format_amount( fee ) )
844 amount = self.read_amount(str(self.amount_e.text()))
845 fee = self.read_amount(str(self.fee_e.text()))
847 if not is_fee: fee = None
850 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
852 self.fee_e.setText( self.format_amount( fee ) )
855 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
859 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
860 self.funds_error = True
861 text = _( "Not enough funds" )
862 c, u = self.wallet.get_frozen_balance()
863 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
865 self.statusBar().showMessage(text)
866 self.amount_e.setPalette(palette)
867 self.fee_e.setPalette(palette)
869 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
870 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
872 self.run_hook('create_send_tab', grid)
876 def update_completions(self):
878 for addr,label in self.wallet.labels.items():
879 if addr in self.wallet.addressbook:
880 l.append( label + ' <' + addr + '>')
882 self.run_hook('update_completions', l)
883 self.completions.setStringList(l)
887 return lambda s, *args: s.do_protect(func, args)
892 label = unicode( self.message_e.text() )
893 r = unicode( self.payto_e.text() )
896 # label or alias, with address in brackets
897 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
898 to_address = m.group(2) if m else r
900 if not is_valid(to_address):
901 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
905 amount = self.read_amount(unicode( self.amount_e.text()))
907 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
910 fee = self.read_amount(unicode( self.fee_e.text()))
912 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
915 confirm_amount = self.config.get('confirm_amount', 100000000)
916 if amount >= confirm_amount:
917 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
920 self.send_tx(to_address, amount, fee, label)
924 def send_tx(self, to_address, amount, fee, label, password):
927 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
928 except BaseException, e:
929 traceback.print_exc(file=sys.stdout)
930 self.show_message(str(e))
933 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
934 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
937 self.run_hook('send_tx', tx)
940 self.set_label(tx.hash(), label)
943 h = self.wallet.send_tx(tx)
944 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
945 status, msg = self.wallet.receive_tx( h )
947 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
949 self.update_contacts_tab()
951 QMessageBox.warning(self, _('Error'), msg, _('OK'))
953 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
955 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
956 with open(fileName,'w') as f:
957 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
958 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
960 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
962 # add recipient to addressbook
963 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
964 self.wallet.addressbook.append(to_address)
969 def set_url(self, url):
970 address, amount, label, message, signature, identity, url = util.parse_url(url)
971 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
973 if label and self.wallet.labels.get(address) != label:
974 if self.question('Give label "%s" to address %s ?'%(label,address)):
975 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
976 self.wallet.addressbook.append(address)
977 self.set_label(address, label)
979 self.run_hook('set_url', url, self.show_message, self.question)
981 self.tabs.setCurrentIndex(1)
982 label = self.wallet.labels.get(address)
983 m_addr = label + ' <'+ address +'>' if label else address
984 self.payto_e.setText(m_addr)
986 self.message_e.setText(message)
987 self.amount_e.setText(amount)
989 self.set_frozen(self.payto_e,True)
990 self.set_frozen(self.amount_e,True)
991 self.set_frozen(self.message_e,True)
992 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
994 self.payto_sig.setVisible(False)
997 self.payto_sig.setVisible(False)
998 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1000 self.set_frozen(e,False)
1001 self.update_status()
1003 def set_frozen(self,entry,frozen):
1005 entry.setReadOnly(True)
1006 entry.setFrame(False)
1007 palette = QPalette()
1008 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1009 entry.setPalette(palette)
1011 entry.setReadOnly(False)
1012 entry.setFrame(True)
1013 palette = QPalette()
1014 palette.setColor(entry.backgroundRole(), QColor('white'))
1015 entry.setPalette(palette)
1018 def toggle_freeze(self,addr):
1020 if addr in self.wallet.frozen_addresses:
1021 self.wallet.unfreeze(addr)
1023 self.wallet.freeze(addr)
1024 self.update_receive_tab()
1026 def toggle_priority(self,addr):
1028 if addr in self.wallet.prioritized_addresses:
1029 self.wallet.unprioritize(addr)
1031 self.wallet.prioritize(addr)
1032 self.update_receive_tab()
1035 def create_list_tab(self, headers):
1036 "generic tab creation method"
1037 l = MyTreeWidget(self)
1038 l.setColumnCount( len(headers) )
1039 l.setHeaderLabels( headers )
1042 vbox = QVBoxLayout()
1049 vbox.addWidget(buttons)
1051 hbox = QHBoxLayout()
1054 buttons.setLayout(hbox)
1059 def create_receive_tab(self):
1060 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1061 l.setContextMenuPolicy(Qt.CustomContextMenu)
1062 l.customContextMenuRequested.connect(self.create_receive_menu)
1063 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1064 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1065 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1066 self.receive_list = l
1067 self.receive_buttons_hbox = hbox
1072 def receive_tab_set_mode(self, i):
1073 self.save_column_widths()
1074 self.expert_mode = (i == 1)
1075 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1076 self.update_receive_tab()
1079 def save_column_widths(self):
1080 if not self.expert_mode:
1081 widths = [ self.receive_list.columnWidth(0) ]
1084 for i in range(self.receive_list.columnCount() -1):
1085 widths.append(self.receive_list.columnWidth(i))
1086 self.column_widths["receive"][self.expert_mode] = widths
1088 self.column_widths["history"] = []
1089 for i in range(self.history_list.columnCount() - 1):
1090 self.column_widths["history"].append(self.history_list.columnWidth(i))
1092 self.column_widths["contacts"] = []
1093 for i in range(self.contacts_list.columnCount() - 1):
1094 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1096 self.config.set_key("column_widths", self.column_widths, True)
1099 def create_contacts_tab(self):
1100 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1101 l.setContextMenuPolicy(Qt.CustomContextMenu)
1102 l.customContextMenuRequested.connect(self.create_contact_menu)
1103 for i,width in enumerate(self.column_widths['contacts']):
1104 l.setColumnWidth(i, width)
1106 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1107 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1108 self.contacts_list = l
1109 self.contacts_buttons_hbox = hbox
1114 def delete_imported_key(self, addr):
1115 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1116 self.wallet.delete_imported_key(addr)
1117 self.update_receive_tab()
1118 self.update_history_tab()
1121 def create_receive_menu(self, position):
1122 # fixme: this function apparently has a side effect.
1123 # if it is not called the menu pops up several times
1124 #self.receive_list.selectedIndexes()
1126 item = self.receive_list.itemAt(position)
1128 addr = unicode(item.text(0))
1129 if not is_valid(addr):
1130 item.setExpanded(not item.isExpanded())
1133 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1134 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1135 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1136 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1137 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1138 if addr in self.wallet.imported_keys:
1139 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1141 if self.expert_mode:
1142 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1143 menu.addAction(t, lambda: self.toggle_freeze(addr))
1144 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1145 menu.addAction(t, lambda: self.toggle_priority(addr))
1147 self.run_hook('receive_menu', menu)
1148 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1151 def payto(self, addr):
1153 label = self.wallet.labels.get(addr)
1154 m_addr = label + ' <' + addr + '>' if label else addr
1155 self.tabs.setCurrentIndex(1)
1156 self.payto_e.setText(m_addr)
1157 self.amount_e.setFocus()
1160 def delete_contact(self, x):
1161 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1162 self.wallet.delete_contact(x)
1163 self.set_label(x, None)
1164 self.update_history_tab()
1165 self.update_contacts_tab()
1166 self.update_completions()
1169 def create_contact_menu(self, position):
1170 item = self.contacts_list.itemAt(position)
1172 addr = unicode(item.text(0))
1173 label = unicode(item.text(1))
1174 is_editable = item.data(0,32).toBool()
1175 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 self.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 self.run_hook('update_receive_item', address, item)
1197 c, u = self.wallet.get_addr_balance(address)
1198 balance = self.format_amount(c + u)
1199 item.setData(2,0,balance)
1201 if self.expert_mode:
1202 if address in self.wallet.frozen_addresses:
1203 item.setBackgroundColor(0, QColor('lightblue'))
1204 elif address in self.wallet.prioritized_addresses:
1205 item.setBackgroundColor(0, QColor('lightgreen'))
1208 def update_receive_tab(self):
1209 l = self.receive_list
1212 l.setColumnHidden(2, not self.expert_mode)
1213 l.setColumnHidden(3, not self.expert_mode)
1214 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1215 l.setColumnWidth(i, width)
1217 if self.current_account is None:
1218 account_items = self.wallet.accounts.items()
1219 elif self.current_account != -1:
1220 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1224 for k, account in account_items:
1225 name = self.wallet.get_account_name(k)
1226 c,u = self.wallet.get_account_balance(k)
1227 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1228 l.addTopLevelItem(account_item)
1229 account_item.setExpanded(True)
1231 for is_change in ([0,1] if self.expert_mode else [0]):
1232 if self.expert_mode:
1233 name = "Receiving" if not is_change else "Change"
1234 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1235 account_item.addChild(seq_item)
1236 if not is_change: seq_item.setExpanded(True)
1238 seq_item = account_item
1242 for address in account.get_addresses(is_change):
1243 h = self.wallet.history.get(address,[])
1247 if gap > self.wallet.gap_limit:
1252 num_tx = '*' if h == ['*'] else "%d"%len(h)
1253 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1254 self.update_receive_item(item)
1256 item.setBackgroundColor(1, QColor('red'))
1257 seq_item.addChild(item)
1260 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1261 c,u = self.wallet.get_imported_balance()
1262 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1263 l.addTopLevelItem(account_item)
1264 account_item.setExpanded(True)
1265 for address in self.wallet.imported_keys.keys():
1266 item = QTreeWidgetItem( [ address, '', '', ''] )
1267 self.update_receive_item(item)
1268 account_item.addChild(item)
1271 # we use column 1 because column 0 may be hidden
1272 l.setCurrentItem(l.topLevelItem(0),1)
1275 def update_contacts_tab(self):
1276 l = self.contacts_list
1279 for address in self.wallet.addressbook:
1280 label = self.wallet.labels.get(address,'')
1281 n = self.wallet.get_num_tx(address)
1282 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1283 item.setFont(0, QFont(MONOSPACE_FONT))
1284 # 32 = label can be edited (bool)
1285 item.setData(0,32, True)
1287 item.setData(0,33, address)
1288 l.addTopLevelItem(item)
1290 self.run_hook('update_contacts_tab', l)
1291 l.setCurrentItem(l.topLevelItem(0))
1295 def create_console_tab(self):
1296 from qt_console import Console
1297 self.console = console = Console()
1301 def update_console(self):
1302 console = self.console
1303 console.history = self.config.get("console-history",[])
1304 console.history_index = len(console.history)
1306 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1307 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1309 c = commands.Commands(self.wallet, self.network.interface, lambda: self.console.set_json(True))
1311 def mkfunc(f, method):
1312 return lambda *args: apply( f, (method, args, self.password_dialog ))
1314 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1315 methods[m] = mkfunc(c._run, m)
1317 console.updateNamespace(methods)
1320 def change_account(self,s):
1321 if s == _("All accounts"):
1322 self.current_account = None
1324 accounts = self.wallet.get_account_names()
1325 for k, v in accounts.items():
1327 self.current_account = k
1328 self.update_history_tab()
1329 self.update_status()
1330 self.update_receive_tab()
1332 def create_status_bar(self):
1335 sb.setFixedHeight(35)
1336 qtVersion = qVersion()
1338 self.balance_label = QLabel("")
1339 sb.addWidget(self.balance_label)
1341 from version_getter import UpdateLabel
1342 self.updatelabel = UpdateLabel(self.config, sb)
1344 self.account_selector = QComboBox()
1345 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1346 sb.addPermanentWidget(self.account_selector)
1348 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1349 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1351 self.lock_icon = QIcon()
1352 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1353 sb.addPermanentWidget( self.password_button )
1355 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1356 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1357 sb.addPermanentWidget( self.seed_button )
1358 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1359 sb.addPermanentWidget( self.status_button )
1361 self.run_hook('create_status_bar', (sb,))
1363 self.setStatusBar(sb)
1366 def update_lock_icon(self):
1367 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1368 self.password_button.setIcon( icon )
1371 def update_buttons_on_seed(self):
1372 if self.wallet.seed:
1373 self.seed_button.show()
1374 self.password_button.show()
1375 self.send_button.setText(_("Send"))
1377 self.password_button.hide()
1378 self.seed_button.hide()
1379 self.send_button.setText(_("Create unsigned transaction"))
1382 def change_password_dialog(self):
1383 from password_dialog import PasswordDialog
1384 d = PasswordDialog(self.wallet, self)
1386 self.update_lock_icon()
1389 def new_contact_dialog(self):
1390 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1391 address = unicode(text)
1393 if is_valid(address):
1394 self.wallet.add_contact(address)
1395 self.update_contacts_tab()
1396 self.update_history_tab()
1397 self.update_completions()
1399 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1402 def new_account_dialog(self):
1404 dialog = QDialog(self)
1406 dialog.setWindowTitle(_("New Account"))
1408 addr = self.wallet.new_account_address()
1409 vbox = QVBoxLayout()
1410 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1415 ok_button = QPushButton(_("OK"))
1416 ok_button.setDefault(True)
1417 ok_button.clicked.connect(dialog.accept)
1419 hbox = QHBoxLayout()
1421 hbox.addWidget(ok_button)
1422 vbox.addLayout(hbox)
1424 dialog.setLayout(vbox)
1429 def show_master_public_key(self):
1430 dialog = QDialog(self)
1432 dialog.setWindowTitle(_("Master Public Key"))
1434 main_text = QTextEdit()
1435 main_text.setText(self.wallet.get_master_public_key())
1436 main_text.setReadOnly(True)
1437 main_text.setMaximumHeight(170)
1438 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1440 ok_button = QPushButton(_("OK"))
1441 ok_button.setDefault(True)
1442 ok_button.clicked.connect(dialog.accept)
1444 main_layout = QGridLayout()
1445 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1447 main_layout.addWidget(main_text, 1, 0)
1448 main_layout.addWidget(qrw, 1, 1 )
1450 vbox = QVBoxLayout()
1451 vbox.addLayout(main_layout)
1452 hbox = QHBoxLayout()
1454 hbox.addWidget(ok_button)
1455 vbox.addLayout(hbox)
1457 dialog.setLayout(vbox)
1462 def show_seed_dialog(self, password):
1463 if not self.wallet.seed:
1464 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1467 seed = self.wallet.decode_seed(password)
1469 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1472 from seed_dialog import SeedDialog
1473 d = SeedDialog(self)
1474 d.show_seed(seed, self.wallet.imported_keys)
1478 def show_qrcode(self, data, title = "QR code"):
1482 d.setWindowTitle(title)
1483 d.setMinimumSize(270, 300)
1484 vbox = QVBoxLayout()
1485 qrw = QRCodeWidget(data)
1486 vbox.addWidget(qrw, 1)
1487 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1488 hbox = QHBoxLayout()
1492 filename = "qrcode.bmp"
1493 bmp.save_qrcode(qrw.qr, filename)
1494 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1496 b = QPushButton(_("Save"))
1498 b.clicked.connect(print_qr)
1500 b = QPushButton(_("Close"))
1502 b.clicked.connect(d.accept)
1505 vbox.addLayout(hbox)
1510 def do_protect(self, func, args):
1511 if self.wallet.use_encryption:
1512 password = self.password_dialog()
1518 if args != (False,):
1519 args = (self,) + args + (password,)
1521 args = (self,password)
1526 def show_private_key(self, address, password):
1527 if not address: return
1529 pk_list = self.wallet.get_private_key(address, password)
1530 except BaseException, e:
1531 self.show_message(str(e))
1533 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1537 def do_sign(self, address, message, signature, password):
1539 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1540 signature.setText(sig)
1541 except BaseException, e:
1542 self.show_message(str(e))
1544 def sign_message(self, address):
1545 if not address: return
1548 d.setWindowTitle(_('Sign Message'))
1549 d.setMinimumSize(410, 290)
1551 tab_widget = QTabWidget()
1553 layout = QGridLayout(tab)
1555 sign_address = QLineEdit()
1557 sign_address.setText(address)
1558 layout.addWidget(QLabel(_('Address')), 1, 0)
1559 layout.addWidget(sign_address, 1, 1)
1561 sign_message = QTextEdit()
1562 layout.addWidget(QLabel(_('Message')), 2, 0)
1563 layout.addWidget(sign_message, 2, 1)
1564 layout.setRowStretch(2,3)
1566 sign_signature = QTextEdit()
1567 layout.addWidget(QLabel(_('Signature')), 3, 0)
1568 layout.addWidget(sign_signature, 3, 1)
1569 layout.setRowStretch(3,1)
1572 hbox = QHBoxLayout()
1573 b = QPushButton(_("Sign"))
1575 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1576 b = QPushButton(_("Close"))
1577 b.clicked.connect(d.accept)
1579 layout.addLayout(hbox, 4, 1)
1580 tab_widget.addTab(tab, _("Sign"))
1584 layout = QGridLayout(tab)
1586 verify_address = QLineEdit()
1587 layout.addWidget(QLabel(_('Address')), 1, 0)
1588 layout.addWidget(verify_address, 1, 1)
1590 verify_message = QTextEdit()
1591 layout.addWidget(QLabel(_('Message')), 2, 0)
1592 layout.addWidget(verify_message, 2, 1)
1593 layout.setRowStretch(2,3)
1595 verify_signature = QTextEdit()
1596 layout.addWidget(QLabel(_('Signature')), 3, 0)
1597 layout.addWidget(verify_signature, 3, 1)
1598 layout.setRowStretch(3,1)
1601 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1602 self.show_message(_("Signature verified"))
1604 self.show_message(_("Error: wrong signature"))
1606 hbox = QHBoxLayout()
1607 b = QPushButton(_("Verify"))
1608 b.clicked.connect(do_verify)
1610 b = QPushButton(_("Close"))
1611 b.clicked.connect(d.accept)
1613 layout.addLayout(hbox, 4, 1)
1614 tab_widget.addTab(tab, _("Verify"))
1616 vbox = QVBoxLayout()
1617 vbox.addWidget(tab_widget)
1624 def question(self, msg):
1625 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1627 def show_message(self, msg):
1628 QMessageBox.information(self, _('Message'), msg, _('OK'))
1630 def password_dialog(self ):
1637 vbox = QVBoxLayout()
1638 msg = _('Please enter your password')
1639 vbox.addWidget(QLabel(msg))
1641 grid = QGridLayout()
1643 grid.addWidget(QLabel(_('Password')), 1, 0)
1644 grid.addWidget(pw, 1, 1)
1645 vbox.addLayout(grid)
1647 vbox.addLayout(ok_cancel_buttons(d))
1650 self.run_hook('password_dialog', pw, grid, 1)
1651 if not d.exec_(): return
1652 return unicode(pw.text())
1659 def generate_transaction_information_widget(self, tx):
1660 tabs = QTabWidget(self)
1663 grid_ui = QGridLayout(tab1)
1664 grid_ui.setColumnStretch(0,1)
1665 tabs.addTab(tab1, _('Outputs') )
1667 tree_widget = MyTreeWidget(self)
1668 tree_widget.setColumnCount(2)
1669 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1670 tree_widget.setColumnWidth(0, 300)
1671 tree_widget.setColumnWidth(1, 50)
1673 for address, value in tx.outputs:
1674 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1675 tree_widget.addTopLevelItem(item)
1677 tree_widget.setMaximumHeight(100)
1679 grid_ui.addWidget(tree_widget)
1682 grid_ui = QGridLayout(tab2)
1683 grid_ui.setColumnStretch(0,1)
1684 tabs.addTab(tab2, _('Inputs') )
1686 tree_widget = MyTreeWidget(self)
1687 tree_widget.setColumnCount(2)
1688 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1690 for input_line in tx.inputs:
1691 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1692 tree_widget.addTopLevelItem(item)
1694 tree_widget.setMaximumHeight(100)
1696 grid_ui.addWidget(tree_widget)
1700 def tx_dict_from_text(self, txt):
1702 tx_dict = json.loads(str(txt))
1703 assert "hex" in tx_dict.keys()
1704 assert "complete" in tx_dict.keys()
1705 if not tx_dict["complete"]:
1706 assert "input_info" in tx_dict.keys()
1708 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1713 def read_tx_from_file(self):
1714 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1718 with open(fileName, "r") as f:
1719 file_content = f.read()
1720 except (ValueError, IOError, os.error), reason:
1721 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1723 return self.tx_dict_from_text(file_content)
1727 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1729 self.wallet.signrawtransaction(tx, input_info, [], password)
1731 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1733 with open(fileName, "w+") as f:
1734 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1735 self.show_message(_("Transaction saved successfully"))
1738 except BaseException, e:
1739 self.show_message(str(e))
1742 def send_raw_transaction(self, raw_tx, dialog = ""):
1743 result, result_message = self.wallet.sendtx( raw_tx )
1745 self.show_message("Transaction successfully sent: %s" % (result_message))
1749 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1751 def do_process_from_text(self):
1752 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1755 tx_dict = self.tx_dict_from_text(text)
1757 self.create_process_transaction_window(tx_dict)
1759 def do_process_from_file(self):
1760 tx_dict = self.read_tx_from_file()
1762 self.create_process_transaction_window(tx_dict)
1764 def do_process_from_csvReader(self, csvReader):
1767 for row in csvReader:
1769 amount = float(row[1])
1770 amount = int(100000000*amount)
1771 outputs.append((address, amount))
1772 except (ValueError, IOError, os.error), reason:
1773 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1777 tx = self.wallet.make_unsigned_transaction(outputs, None, None, account=self.current_account)
1778 except BaseException, e:
1779 self.show_message(str(e))
1782 tx_dict = tx.as_dict()
1783 self.create_process_transaction_window(tx_dict)
1785 def do_process_from_csv_file(self):
1786 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1790 with open(fileName, "r") as f:
1791 csvReader = csv.reader(f)
1792 self.do_process_from_csvReader(csvReader)
1793 except (ValueError, IOError, os.error), reason:
1794 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1797 def do_process_from_csv_text(self):
1798 text = text_dialog(self, _('Input CSV'), _("CSV:"), _("Load CSV"))
1801 f = StringIO.StringIO(text)
1802 csvReader = csv.reader(f)
1803 self.do_process_from_csvReader(csvReader)
1805 def create_process_transaction_window(self, tx_dict):
1806 tx = Transaction(tx_dict["hex"])
1808 dialog = QDialog(self)
1809 dialog.setMinimumWidth(500)
1810 dialog.setWindowTitle(_('Process raw transaction'))
1816 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1817 l.addWidget(QLabel(_("Actions")), 4,0)
1819 if tx_dict["complete"] == False:
1820 l.addWidget(QLabel(_("Unsigned")), 3,1)
1821 if self.wallet.seed :
1822 b = QPushButton("Sign transaction")
1823 input_info = json.loads(tx_dict["input_info"])
1824 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1825 l.addWidget(b, 4, 1)
1827 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1829 l.addWidget(QLabel(_("Signed")), 3,1)
1830 b = QPushButton("Broadcast transaction")
1831 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1834 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1835 cancelButton = QPushButton(_("Cancel"))
1836 cancelButton.clicked.connect(lambda: dialog.done(0))
1837 l.addWidget(cancelButton, 4,2)
1843 def do_export_privkeys(self, password):
1844 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.")))
1847 select_export = _('Select file to export your private keys to')
1848 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1850 with open(fileName, "w+") as csvfile:
1851 transaction = csv.writer(csvfile)
1852 transaction.writerow(["address", "private_key"])
1854 addresses = self.wallet.addresses(True)
1856 for addr in addresses:
1857 pk = "".join(self.wallet.get_private_key(addr, password))
1858 transaction.writerow(["%34s"%addr,pk])
1860 self.show_message(_("Private keys exported."))
1862 except (IOError, os.error), reason:
1863 export_error_label = _("Electrum was unable to produce a private key-export.")
1864 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1866 except BaseException, e:
1867 self.show_message(str(e))
1871 def do_import_labels(self):
1872 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1873 if not labelsFile: return
1875 f = open(labelsFile, 'r')
1878 for key, value in json.loads(data).items():
1879 self.wallet.set_label(key, value)
1880 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1881 except (IOError, os.error), reason:
1882 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1885 def do_export_labels(self):
1886 labels = self.wallet.labels
1888 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1890 with open(fileName, 'w+') as f:
1891 json.dump(labels, f)
1892 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1893 except (IOError, os.error), reason:
1894 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1897 def do_export_history(self):
1898 from lite_window import csv_transaction
1899 csv_transaction(self.wallet)
1903 def do_import_privkey(self, password):
1904 if not self.wallet.imported_keys:
1905 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1906 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1907 + _('Are you sure you understand what you are doing?'), 3, 4)
1910 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1913 text = str(text).split()
1918 addr = self.wallet.import_key(key, password)
1919 except BaseException as e:
1925 addrlist.append(addr)
1927 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1929 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1930 self.update_receive_tab()
1931 self.update_history_tab()
1934 def settings_dialog(self):
1936 d.setWindowTitle(_('Electrum Settings'))
1938 vbox = QVBoxLayout()
1940 tabs = QTabWidget(self)
1941 self.settings_tab = tabs
1942 vbox.addWidget(tabs)
1945 grid_ui = QGridLayout(tab1)
1946 grid_ui.setColumnStretch(0,1)
1947 tabs.addTab(tab1, _('Display') )
1949 nz_label = QLabel(_('Display zeros'))
1950 grid_ui.addWidget(nz_label, 0, 0)
1951 nz_e = AmountEdit(None,True)
1952 nz_e.setText("%d"% self.num_zeros)
1953 grid_ui.addWidget(nz_e, 0, 1)
1954 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1955 grid_ui.addWidget(HelpButton(msg), 0, 2)
1956 if not self.config.is_modifiable('num_zeros'):
1957 for w in [nz_e, nz_label]: w.setEnabled(False)
1959 lang_label=QLabel(_('Language') + ':')
1960 grid_ui.addWidget(lang_label, 1, 0)
1961 lang_combo = QComboBox()
1962 from electrum.i18n import languages
1963 lang_combo.addItems(languages.values())
1965 index = languages.keys().index(self.config.get("language",''))
1968 lang_combo.setCurrentIndex(index)
1969 grid_ui.addWidget(lang_combo, 1, 1)
1970 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1971 if not self.config.is_modifiable('language'):
1972 for w in [lang_combo, lang_label]: w.setEnabled(False)
1974 currencies = self.exchanger.get_currencies()
1975 currencies.insert(0, "None")
1977 cur_label=QLabel(_('Currency') + ':')
1978 grid_ui.addWidget(cur_label , 2, 0)
1979 cur_combo = QComboBox()
1980 cur_combo.addItems(currencies)
1982 index = currencies.index(self.config.get('currency', "None"))
1985 cur_combo.setCurrentIndex(index)
1986 grid_ui.addWidget(cur_combo, 2, 1)
1987 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1989 expert_cb = QCheckBox(_('Expert mode'))
1990 expert_cb.setChecked(self.expert_mode)
1991 grid_ui.addWidget(expert_cb, 3, 0)
1992 hh = _('In expert mode, your client will:') + '\n' \
1993 + _(' - Show change addresses in the Receive tab') + '\n' \
1994 + _(' - Display the balance of each address') + '\n' \
1995 + _(' - Add freeze/prioritize actions to addresses.')
1996 grid_ui.addWidget(HelpButton(hh), 3, 2)
1997 grid_ui.setRowStretch(4,1)
2001 grid_wallet = QGridLayout(tab2)
2002 grid_wallet.setColumnStretch(0,1)
2003 tabs.addTab(tab2, _('Wallet') )
2005 fee_label = QLabel(_('Transaction fee'))
2006 grid_wallet.addWidget(fee_label, 0, 0)
2007 fee_e = AmountEdit(self.base_unit)
2008 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2009 grid_wallet.addWidget(fee_e, 0, 2)
2010 msg = _('Fee per kilobyte of transaction.') + ' ' \
2011 + _('Recommended value') + ': ' + self.format_amount(50000)
2012 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2013 if not self.config.is_modifiable('fee_per_kb'):
2014 for w in [fee_e, fee_label]: w.setEnabled(False)
2016 usechange_cb = QCheckBox(_('Use change addresses'))
2017 usechange_cb.setChecked(self.wallet.use_change)
2018 grid_wallet.addWidget(usechange_cb, 1, 0)
2019 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2020 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2022 gap_label = QLabel(_('Gap limit'))
2023 grid_wallet.addWidget(gap_label, 2, 0)
2024 gap_e = AmountEdit(None,True)
2025 gap_e.setText("%d"% self.wallet.gap_limit)
2026 grid_wallet.addWidget(gap_e, 2, 2)
2027 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2028 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2029 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2030 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2031 + _('Warning') + ': ' \
2032 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2033 + _('Do not modify it if you do not understand what you are doing, or if you expect to recover your wallet without knowing it!') + '\n\n'
2034 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2035 if not self.config.is_modifiable('gap_limit'):
2036 for w in [gap_e, gap_label]: w.setEnabled(False)
2038 units = ['BTC', 'mBTC']
2039 unit_label = QLabel(_('Base unit'))
2040 grid_wallet.addWidget(unit_label, 3, 0)
2041 unit_combo = QComboBox()
2042 unit_combo.addItems(units)
2043 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2044 grid_wallet.addWidget(unit_combo, 3, 2)
2045 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2046 + '\n1BTC=1000mBTC.\n' \
2047 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2048 grid_wallet.setRowStretch(4,1)
2052 tab5 = QScrollArea()
2053 tab5.setEnabled(True)
2054 tab5.setWidgetResizable(True)
2056 grid_plugins = QGridLayout()
2057 grid_plugins.setColumnStretch(0,1)
2060 w.setLayout(grid_plugins)
2063 w.setMinimumHeight(len(self.plugins)*35)
2065 tabs.addTab(tab5, _('Plugins') )
2066 def mk_toggle(cb, p):
2067 return lambda: cb.setChecked(p.toggle())
2068 for i, p in enumerate(self.plugins):
2070 cb = QCheckBox(p.fullname())
2071 cb.setDisabled(not p.is_available())
2072 cb.setChecked(p.is_enabled())
2073 cb.clicked.connect(mk_toggle(cb,p))
2074 grid_plugins.addWidget(cb, i, 0)
2075 if p.requires_settings():
2076 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2077 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2079 print_msg("Error: cannot display plugin", p)
2080 traceback.print_exc(file=sys.stdout)
2081 grid_plugins.setRowStretch(i+1,1)
2083 self.run_hook('create_settings_tab', tabs)
2085 vbox.addLayout(ok_cancel_buttons(d))
2089 if not d.exec_(): return
2091 fee = unicode(fee_e.text())
2093 fee = self.read_amount(fee)
2095 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2098 self.wallet.set_fee(fee)
2100 nz = unicode(nz_e.text())
2105 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2108 if self.num_zeros != nz:
2110 self.config.set_key('num_zeros', nz, True)
2111 self.update_history_tab()
2112 self.update_receive_tab()
2114 usechange_result = usechange_cb.isChecked()
2115 if self.wallet.use_change != usechange_result:
2116 self.wallet.use_change = usechange_result
2117 self.config.set_key('use_change', self.wallet.use_change, True)
2119 unit_result = units[unit_combo.currentIndex()]
2120 if self.base_unit() != unit_result:
2121 self.decimal_point = 8 if unit_result == 'BTC' else 5
2122 self.config.set_key('decimal_point', self.decimal_point, True)
2123 self.update_history_tab()
2124 self.update_status()
2127 n = int(gap_e.text())
2129 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2132 if self.wallet.gap_limit != n:
2133 r = self.wallet.change_gap_limit(n)
2135 self.update_receive_tab()
2136 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2138 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2140 need_restart = False
2142 lang_request = languages.keys()[lang_combo.currentIndex()]
2143 if lang_request != self.config.get('language'):
2144 self.config.set_key("language", lang_request, True)
2147 cur_request = str(currencies[cur_combo.currentIndex()])
2148 if cur_request != self.config.get('currency', "None"):
2149 self.config.set_key('currency', cur_request, True)
2150 self.update_wallet()
2152 self.run_hook('close_settings_dialog')
2155 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2157 self.receive_tab_set_mode(expert_cb.isChecked())
2159 def run_network_dialog(self):
2160 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2162 def closeEvent(self, event):
2164 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2165 self.save_column_widths()
2166 self.config.set_key("console-history", self.console.history[-50:], True)