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 interface = self.wallet.interface
285 blockchain = self.wallet.verifier.blockchain
286 self.wallet.stop_threads()
289 wallet = Wallet(storage)
290 wallet.start_threads(interface, blockchain)
292 self.load_wallet(wallet)
295 def new_wallet(self):
298 wallet_folder = self.wallet.storage.path
299 re.sub("(\/\w*.dat)$", "", wallet_folder)
300 filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
302 storage = WalletStorage({'wallet_path': filename})
303 assert not storage.file_exists
305 wizard = installwizard.InstallWizard(self.config, self.wallet.interface, self.wallet.verifier.blockchain, storage)
306 wallet = wizard.run()
308 self.load_wallet(wallet)
312 def init_menubar(self):
315 file_menu = menubar.addMenu(_("&File"))
316 open_wallet_action = file_menu.addAction(_("&Open"))
317 open_wallet_action.triggered.connect(self.open_wallet)
319 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
320 new_wallet_action.triggered.connect(self.new_wallet)
322 wallet_backup = file_menu.addAction(_("&Copy"))
323 wallet_backup.triggered.connect(lambda: backup_wallet(self.wallet.storage.path))
325 quit_item = file_menu.addAction(_("&Close"))
326 quit_item.triggered.connect(self.close)
328 wallet_menu = menubar.addMenu(_("&Wallet"))
330 # Settings / Preferences are all reserved keywords in OSX using this as work around
331 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
332 preferences_menu = wallet_menu.addAction(preferences_name)
333 preferences_menu.triggered.connect(self.settings_dialog)
335 wallet_menu.addSeparator()
337 raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
339 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
340 raw_transaction_file.triggered.connect(self.do_process_from_file)
342 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
343 raw_transaction_text.triggered.connect(self.do_process_from_text)
345 csv_transaction_menu = wallet_menu.addMenu(_("&Load CSV transaction"))
347 csv_transaction_file = csv_transaction_menu.addAction(_("&From file"))
348 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
350 csv_transaction_text = csv_transaction_menu.addAction(_("&From text"))
351 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
353 wallet_menu.addSeparator()
355 show_menu = wallet_menu.addMenu(_("Show"))
357 #if self.wallet.seed:
358 show_seed = show_menu.addAction(_("&Seed"))
359 show_seed.triggered.connect(self.show_seed_dialog)
361 show_mpk = show_menu.addAction(_("&Master Public Key"))
362 show_mpk.triggered.connect(self.show_master_public_key)
364 wallet_menu.addSeparator()
365 new_contact = wallet_menu.addAction(_("&New contact"))
366 new_contact.triggered.connect(self.new_contact_dialog)
368 self.new_account = wallet_menu.addAction(_("&New account"))
369 self.new_account.triggered.connect(self.new_account_dialog)
371 import_menu = menubar.addMenu(_("&Import"))
372 in_labels = import_menu.addAction(_("&Labels"))
373 in_labels.triggered.connect(self.do_import_labels)
375 in_private_keys = import_menu.addAction(_("&Private keys"))
376 in_private_keys.triggered.connect(self.do_import_privkey)
378 export_menu = menubar.addMenu(_("&Export"))
379 ex_private_keys = export_menu.addAction(_("&Private keys"))
380 ex_private_keys.triggered.connect(self.do_export_privkeys)
382 ex_history = export_menu.addAction(_("&History"))
383 ex_history.triggered.connect(self.do_export_history)
385 ex_labels = export_menu.addAction(_("&Labels"))
386 ex_labels.triggered.connect(self.do_export_labels)
388 help_menu = menubar.addMenu(_("&Help"))
389 show_about = help_menu.addAction(_("&About"))
390 show_about.triggered.connect(self.show_about)
391 web_open = help_menu.addAction(_("&Official website"))
392 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
394 help_menu.addSeparator()
395 doc_open = help_menu.addAction(_("&Documentation"))
396 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
397 report_bug = help_menu.addAction(_("&Report Bug"))
398 report_bug.triggered.connect(self.show_report_bug)
400 self.setMenuBar(menubar)
402 def show_about(self):
403 QMessageBox.about(self, "Electrum",
404 _("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."))
406 def show_report_bug(self):
407 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
408 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
411 def notify_transactions(self):
412 print_error("Notifying GUI")
413 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
414 # Combine the transactions if there are more then three
415 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
418 for tx in self.wallet.interface.pending_transactions_for_notifications:
419 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
423 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
424 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
426 self.wallet.interface.pending_transactions_for_notifications = []
428 for tx in self.wallet.interface.pending_transactions_for_notifications:
430 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
431 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
433 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
435 def notify(self, message):
436 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
439 def init_plugins(self):
440 import imp, pkgutil, __builtin__
441 if __builtin__.use_local_modules:
442 fp, pathname, description = imp.find_module('plugins')
443 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
444 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
445 imp.load_module('electrum_plugins', fp, pathname, description)
446 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
448 import electrum_plugins
449 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
450 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
453 for name, p in zip(plugin_names, plugins):
455 self.plugins.append( p.Plugin(self, name) )
457 print_msg("Error:cannot initialize plugin",p)
458 traceback.print_exc(file=sys.stdout)
461 def run_hook(self, name, *args):
462 for p in self.plugins:
463 if not p.is_enabled():
472 print_error("Plugin error")
473 traceback.print_exc(file=sys.stdout)
479 def set_label(self, name, text = None):
481 old_text = self.wallet.labels.get(name)
484 self.wallet.labels[name] = text
485 self.wallet.storage.put('labels', self.wallet.labels)
489 self.wallet.labels.pop(name)
491 self.run_hook('set_label', name, text, changed)
495 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
496 def getOpenFileName(self, title, filter = None):
497 directory = self.config.get('io_dir', os.path.expanduser('~'))
498 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
499 if fileName and directory != os.path.dirname(fileName):
500 self.config.set_key('io_dir', os.path.dirname(fileName), True)
503 def getSaveFileName(self, title, filename, filter = None):
504 directory = self.config.get('io_dir', os.path.expanduser('~'))
505 path = os.path.join( directory, filename )
506 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
507 if fileName and directory != os.path.dirname(fileName):
508 self.config.set_key('io_dir', os.path.dirname(fileName), True)
512 QMainWindow.close(self)
513 self.run_hook('close_main_window')
515 def connect_slots(self, sender):
516 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
517 self.previous_payto_e=''
519 def timer_actions(self):
520 if self.need_update.is_set():
522 self.need_update.clear()
523 self.run_hook('timer_actions')
525 def format_amount(self, x, is_diff=False, whitespaces=False):
526 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
528 def read_amount(self, x):
529 if x in['.', '']: return None
530 p = pow(10, self.decimal_point)
531 return int( p * Decimal(x) )
534 assert self.decimal_point in [5,8]
535 return "BTC" if self.decimal_point == 8 else "mBTC"
537 def update_status(self):
538 if self.wallet.interface and self.wallet.interface.is_connected:
539 if not self.wallet.up_to_date:
540 text = _("Synchronizing...")
541 icon = QIcon(":icons/status_waiting.png")
543 c, u = self.wallet.get_account_balance(self.current_account)
544 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
545 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
546 text += self.create_quote_text(Decimal(c+u)/100000000)
547 self.tray.setToolTip(text)
548 icon = QIcon(":icons/status_connected.png")
550 text = _("Not connected")
551 icon = QIcon(":icons/status_disconnected.png")
553 self.balance_label.setText(text)
554 self.status_button.setIcon( icon )
556 def update_wallet(self):
558 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
559 self.update_history_tab()
560 self.update_receive_tab()
561 self.update_contacts_tab()
562 self.update_completions()
565 def create_quote_text(self, btc_balance):
566 quote_currency = self.config.get("currency", "None")
567 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
568 if quote_balance is None:
571 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
574 def create_history_tab(self):
575 self.history_list = l = MyTreeWidget(self)
577 for i,width in enumerate(self.column_widths['history']):
578 l.setColumnWidth(i, width)
579 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
580 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
581 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
583 l.setContextMenuPolicy(Qt.CustomContextMenu)
584 l.customContextMenuRequested.connect(self.create_history_menu)
588 def create_history_menu(self, position):
589 self.history_list.selectedIndexes()
590 item = self.history_list.currentItem()
592 tx_hash = str(item.data(0, Qt.UserRole).toString())
593 if not tx_hash: return
595 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
596 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
597 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
598 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
601 def show_tx_details(self, tx):
602 dialog = QDialog(self)
604 dialog.setWindowTitle(_("Transaction Details"))
606 dialog.setLayout(vbox)
607 dialog.setMinimumSize(600,300)
610 if tx_hash in self.wallet.transactions.keys():
611 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
612 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
614 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
620 vbox.addWidget(QLabel("Transaction ID:"))
621 e = QLineEdit(tx_hash)
625 vbox.addWidget(QLabel("Date: %s"%time_str))
626 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
629 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
630 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
632 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
633 vbox.addWidget(QLabel("Transaction fee: unknown"))
635 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
637 vbox.addWidget( self.generate_transaction_information_widget(tx) )
639 ok_button = QPushButton(_("Close"))
640 ok_button.setDefault(True)
641 ok_button.clicked.connect(dialog.accept)
645 hbox.addWidget(ok_button)
649 def tx_label_clicked(self, item, column):
650 if column==2 and item.isSelected():
652 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
653 self.history_list.editItem( item, column )
654 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
657 def tx_label_changed(self, item, column):
661 tx_hash = str(item.data(0, Qt.UserRole).toString())
662 tx = self.wallet.transactions.get(tx_hash)
663 text = unicode( item.text(2) )
664 self.set_label(tx_hash, text)
666 item.setForeground(2, QBrush(QColor('black')))
668 text = self.wallet.get_default_label(tx_hash)
669 item.setText(2, text)
670 item.setForeground(2, QBrush(QColor('gray')))
674 def edit_label(self, is_recv):
675 l = self.receive_list if is_recv else self.contacts_list
676 item = l.currentItem()
677 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
678 l.editItem( item, 1 )
679 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
683 def address_label_clicked(self, item, column, l, column_addr, column_label):
684 if column == column_label and item.isSelected():
685 is_editable = item.data(0, 32).toBool()
688 addr = unicode( item.text(column_addr) )
689 label = unicode( item.text(column_label) )
690 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
691 l.editItem( item, column )
692 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
695 def address_label_changed(self, item, column, l, column_addr, column_label):
696 if column == column_label:
697 addr = unicode( item.text(column_addr) )
698 text = unicode( item.text(column_label) )
699 is_editable = item.data(0, 32).toBool()
703 changed = self.set_label(addr, text)
705 self.update_history_tab()
706 self.update_completions()
708 self.current_item_changed(item)
710 self.run_hook('item_changed', item, column)
713 def current_item_changed(self, a):
714 self.run_hook('current_item_changed', a)
718 def update_history_tab(self):
720 self.history_list.clear()
721 for item in self.wallet.get_tx_history(self.current_account):
722 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
725 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
730 time_str = 'unverified'
731 icon = QIcon(":icons/unconfirmed.png")
734 icon = QIcon(":icons/unconfirmed.png")
736 icon = QIcon(":icons/clock%d.png"%conf)
738 icon = QIcon(":icons/confirmed.png")
740 if value is not None:
741 v_str = self.format_amount(value, True, whitespaces=True)
745 balance_str = self.format_amount(balance, whitespaces=True)
748 label, is_default_label = self.wallet.get_label(tx_hash)
750 label = _('Pruned transaction outputs')
751 is_default_label = False
753 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
754 item.setFont(2, QFont(MONOSPACE_FONT))
755 item.setFont(3, QFont(MONOSPACE_FONT))
756 item.setFont(4, QFont(MONOSPACE_FONT))
758 item.setForeground(3, QBrush(QColor("#BC1E1E")))
760 item.setData(0, Qt.UserRole, tx_hash)
761 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
763 item.setForeground(2, QBrush(QColor('grey')))
765 item.setIcon(0, icon)
766 self.history_list.insertTopLevelItem(0,item)
769 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
772 def create_send_tab(self):
777 grid.setColumnMinimumWidth(3,300)
778 grid.setColumnStretch(5,1)
781 self.payto_e = QLineEdit()
782 grid.addWidget(QLabel(_('Pay to')), 1, 0)
783 grid.addWidget(self.payto_e, 1, 1, 1, 3)
785 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)
787 completer = QCompleter()
788 completer.setCaseSensitivity(False)
789 self.payto_e.setCompleter(completer)
790 completer.setModel(self.completions)
792 self.message_e = QLineEdit()
793 grid.addWidget(QLabel(_('Description')), 2, 0)
794 grid.addWidget(self.message_e, 2, 1, 1, 3)
795 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)
797 self.amount_e = AmountEdit(self.base_unit)
798 grid.addWidget(QLabel(_('Amount')), 3, 0)
799 grid.addWidget(self.amount_e, 3, 1, 1, 2)
800 grid.addWidget(HelpButton(
801 _('Amount to be sent.') + '\n\n' \
802 + _('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.') \
803 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
805 self.fee_e = AmountEdit(self.base_unit)
806 grid.addWidget(QLabel(_('Fee')), 4, 0)
807 grid.addWidget(self.fee_e, 4, 1, 1, 2)
808 grid.addWidget(HelpButton(
809 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
810 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
811 + _('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)
814 self.send_button = EnterButton(_("Send"), self.do_send)
815 grid.addWidget(self.send_button, 6, 1)
817 b = EnterButton(_("Clear"),self.do_clear)
818 grid.addWidget(b, 6, 2)
820 self.payto_sig = QLabel('')
821 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
823 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
824 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
833 def entry_changed( is_fee ):
834 self.funds_error = False
836 if self.amount_e.is_shortcut:
837 self.amount_e.is_shortcut = False
838 c, u = self.wallet.get_account_balance(self.current_account)
839 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
840 fee = self.wallet.estimated_fee(inputs)
842 self.amount_e.setText( self.format_amount(amount) )
843 self.fee_e.setText( self.format_amount( fee ) )
846 amount = self.read_amount(str(self.amount_e.text()))
847 fee = self.read_amount(str(self.fee_e.text()))
849 if not is_fee: fee = None
852 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
854 self.fee_e.setText( self.format_amount( fee ) )
857 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
861 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
862 self.funds_error = True
863 text = _( "Not enough funds" )
864 c, u = self.wallet.get_frozen_balance()
865 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
867 self.statusBar().showMessage(text)
868 self.amount_e.setPalette(palette)
869 self.fee_e.setPalette(palette)
871 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
872 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
874 self.run_hook('create_send_tab', grid)
878 def update_completions(self):
880 for addr,label in self.wallet.labels.items():
881 if addr in self.wallet.addressbook:
882 l.append( label + ' <' + addr + '>')
884 self.run_hook('update_completions', l)
885 self.completions.setStringList(l)
889 return lambda s, *args: s.do_protect(func, args)
894 label = unicode( self.message_e.text() )
895 r = unicode( self.payto_e.text() )
898 # label or alias, with address in brackets
899 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
900 to_address = m.group(2) if m else r
902 if not is_valid(to_address):
903 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
907 amount = self.read_amount(unicode( self.amount_e.text()))
909 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
912 fee = self.read_amount(unicode( self.fee_e.text()))
914 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
917 confirm_amount = self.config.get('confirm_amount', 100000000)
918 if amount >= confirm_amount:
919 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
922 self.send_tx(to_address, amount, fee, label)
926 def send_tx(self, to_address, amount, fee, label, password):
929 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
930 except BaseException, e:
931 traceback.print_exc(file=sys.stdout)
932 self.show_message(str(e))
935 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
936 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
939 self.run_hook('send_tx', tx)
942 self.set_label(tx.hash(), label)
945 h = self.wallet.send_tx(tx)
946 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
947 status, msg = self.wallet.receive_tx( h )
949 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
951 self.update_contacts_tab()
953 QMessageBox.warning(self, _('Error'), msg, _('OK'))
955 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
957 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
958 with open(fileName,'w') as f:
959 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
960 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
962 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
964 # add recipient to addressbook
965 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
966 self.wallet.addressbook.append(to_address)
971 def set_url(self, url):
972 address, amount, label, message, signature, identity, url = util.parse_url(url)
973 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
975 if label and self.wallet.labels.get(address) != label:
976 if self.question('Give label "%s" to address %s ?'%(label,address)):
977 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
978 self.wallet.addressbook.append(address)
979 self.set_label(address, label)
981 self.run_hook('set_url', url, self.show_message, self.question)
983 self.tabs.setCurrentIndex(1)
984 label = self.wallet.labels.get(address)
985 m_addr = label + ' <'+ address +'>' if label else address
986 self.payto_e.setText(m_addr)
988 self.message_e.setText(message)
989 self.amount_e.setText(amount)
991 self.set_frozen(self.payto_e,True)
992 self.set_frozen(self.amount_e,True)
993 self.set_frozen(self.message_e,True)
994 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
996 self.payto_sig.setVisible(False)
999 self.payto_sig.setVisible(False)
1000 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1002 self.set_frozen(e,False)
1003 self.update_status()
1005 def set_frozen(self,entry,frozen):
1007 entry.setReadOnly(True)
1008 entry.setFrame(False)
1009 palette = QPalette()
1010 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1011 entry.setPalette(palette)
1013 entry.setReadOnly(False)
1014 entry.setFrame(True)
1015 palette = QPalette()
1016 palette.setColor(entry.backgroundRole(), QColor('white'))
1017 entry.setPalette(palette)
1020 def toggle_freeze(self,addr):
1022 if addr in self.wallet.frozen_addresses:
1023 self.wallet.unfreeze(addr)
1025 self.wallet.freeze(addr)
1026 self.update_receive_tab()
1028 def toggle_priority(self,addr):
1030 if addr in self.wallet.prioritized_addresses:
1031 self.wallet.unprioritize(addr)
1033 self.wallet.prioritize(addr)
1034 self.update_receive_tab()
1037 def create_list_tab(self, headers):
1038 "generic tab creation method"
1039 l = MyTreeWidget(self)
1040 l.setColumnCount( len(headers) )
1041 l.setHeaderLabels( headers )
1044 vbox = QVBoxLayout()
1051 vbox.addWidget(buttons)
1053 hbox = QHBoxLayout()
1056 buttons.setLayout(hbox)
1061 def create_receive_tab(self):
1062 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1063 l.setContextMenuPolicy(Qt.CustomContextMenu)
1064 l.customContextMenuRequested.connect(self.create_receive_menu)
1065 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1066 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1067 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1068 self.receive_list = l
1069 self.receive_buttons_hbox = hbox
1074 def receive_tab_set_mode(self, i):
1075 self.save_column_widths()
1076 self.expert_mode = (i == 1)
1077 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1078 self.update_receive_tab()
1081 def save_column_widths(self):
1082 if not self.expert_mode:
1083 widths = [ self.receive_list.columnWidth(0) ]
1086 for i in range(self.receive_list.columnCount() -1):
1087 widths.append(self.receive_list.columnWidth(i))
1088 self.column_widths["receive"][self.expert_mode] = widths
1090 self.column_widths["history"] = []
1091 for i in range(self.history_list.columnCount() - 1):
1092 self.column_widths["history"].append(self.history_list.columnWidth(i))
1094 self.column_widths["contacts"] = []
1095 for i in range(self.contacts_list.columnCount() - 1):
1096 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1098 self.config.set_key("column_widths", self.column_widths, True)
1101 def create_contacts_tab(self):
1102 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1103 l.setContextMenuPolicy(Qt.CustomContextMenu)
1104 l.customContextMenuRequested.connect(self.create_contact_menu)
1105 for i,width in enumerate(self.column_widths['contacts']):
1106 l.setColumnWidth(i, width)
1108 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1109 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1110 self.contacts_list = l
1111 self.contacts_buttons_hbox = hbox
1116 def delete_imported_key(self, addr):
1117 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1118 self.wallet.delete_imported_key(addr)
1119 self.update_receive_tab()
1120 self.update_history_tab()
1123 def create_receive_menu(self, position):
1124 # fixme: this function apparently has a side effect.
1125 # if it is not called the menu pops up several times
1126 #self.receive_list.selectedIndexes()
1128 item = self.receive_list.itemAt(position)
1130 addr = unicode(item.text(0))
1131 if not is_valid(addr):
1132 item.setExpanded(not item.isExpanded())
1135 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1136 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1137 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1138 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1139 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1140 if addr in self.wallet.imported_keys:
1141 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1143 if self.expert_mode:
1144 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1145 menu.addAction(t, lambda: self.toggle_freeze(addr))
1146 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1147 menu.addAction(t, lambda: self.toggle_priority(addr))
1149 self.run_hook('receive_menu', menu)
1150 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1153 def payto(self, addr):
1155 label = self.wallet.labels.get(addr)
1156 m_addr = label + ' <' + addr + '>' if label else addr
1157 self.tabs.setCurrentIndex(1)
1158 self.payto_e.setText(m_addr)
1159 self.amount_e.setFocus()
1162 def delete_contact(self, x):
1163 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1164 self.wallet.delete_contact(x)
1165 self.set_label(x, None)
1166 self.update_history_tab()
1167 self.update_contacts_tab()
1168 self.update_completions()
1171 def create_contact_menu(self, position):
1172 item = self.contacts_list.itemAt(position)
1174 addr = unicode(item.text(0))
1175 label = unicode(item.text(1))
1176 is_editable = item.data(0,32).toBool()
1177 payto_addr = item.data(0,33).toString()
1179 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1180 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1181 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1183 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1184 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1186 self.run_hook('create_contact_menu', menu, item)
1187 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1190 def update_receive_item(self, item):
1191 item.setFont(0, QFont(MONOSPACE_FONT))
1192 address = str(item.data(0,0).toString())
1193 label = self.wallet.labels.get(address,'')
1194 item.setData(1,0,label)
1195 item.setData(0,32, True) # is editable
1197 self.run_hook('update_receive_item', address, item)
1199 c, u = self.wallet.get_addr_balance(address)
1200 balance = self.format_amount(c + u)
1201 item.setData(2,0,balance)
1203 if self.expert_mode:
1204 if address in self.wallet.frozen_addresses:
1205 item.setBackgroundColor(0, QColor('lightblue'))
1206 elif address in self.wallet.prioritized_addresses:
1207 item.setBackgroundColor(0, QColor('lightgreen'))
1210 def update_receive_tab(self):
1211 l = self.receive_list
1214 l.setColumnHidden(2, not self.expert_mode)
1215 l.setColumnHidden(3, not self.expert_mode)
1216 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1217 l.setColumnWidth(i, width)
1219 if self.current_account is None:
1220 account_items = self.wallet.accounts.items()
1221 elif self.current_account != -1:
1222 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1226 for k, account in account_items:
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(True)
1233 for is_change in ([0,1] if self.expert_mode else [0]):
1234 if self.expert_mode:
1235 name = "Receiving" if not is_change else "Change"
1236 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1237 account_item.addChild(seq_item)
1238 if not is_change: seq_item.setExpanded(True)
1240 seq_item = account_item
1244 for address in account.get_addresses(is_change):
1245 h = self.wallet.history.get(address,[])
1249 if gap > self.wallet.gap_limit:
1254 num_tx = '*' if h == ['*'] else "%d"%len(h)
1255 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1256 self.update_receive_item(item)
1258 item.setBackgroundColor(1, QColor('red'))
1259 seq_item.addChild(item)
1262 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1263 c,u = self.wallet.get_imported_balance()
1264 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1265 l.addTopLevelItem(account_item)
1266 account_item.setExpanded(True)
1267 for address in self.wallet.imported_keys.keys():
1268 item = QTreeWidgetItem( [ address, '', '', ''] )
1269 self.update_receive_item(item)
1270 account_item.addChild(item)
1273 # we use column 1 because column 0 may be hidden
1274 l.setCurrentItem(l.topLevelItem(0),1)
1277 def update_contacts_tab(self):
1278 l = self.contacts_list
1281 for address in self.wallet.addressbook:
1282 label = self.wallet.labels.get(address,'')
1283 n = self.wallet.get_num_tx(address)
1284 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1285 item.setFont(0, QFont(MONOSPACE_FONT))
1286 # 32 = label can be edited (bool)
1287 item.setData(0,32, True)
1289 item.setData(0,33, address)
1290 l.addTopLevelItem(item)
1292 self.run_hook('update_contacts_tab', l)
1293 l.setCurrentItem(l.topLevelItem(0))
1297 def create_console_tab(self):
1298 from qt_console import Console
1299 self.console = console = Console()
1303 def update_console(self):
1304 console = self.console
1305 console.history = self.config.get("console-history",[])
1306 console.history_index = len(console.history)
1308 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1309 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1311 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1313 def mkfunc(f, method):
1314 return lambda *args: apply( f, (method, args, self.password_dialog ))
1316 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1317 methods[m] = mkfunc(c._run, m)
1319 console.updateNamespace(methods)
1322 def change_account(self,s):
1323 if s == _("All accounts"):
1324 self.current_account = None
1326 accounts = self.wallet.get_account_names()
1327 for k, v in accounts.items():
1329 self.current_account = k
1330 self.update_history_tab()
1331 self.update_status()
1332 self.update_receive_tab()
1334 def create_status_bar(self):
1337 sb.setFixedHeight(35)
1338 qtVersion = qVersion()
1340 self.balance_label = QLabel("")
1341 sb.addWidget(self.balance_label)
1343 from version_getter import UpdateLabel
1344 self.updatelabel = UpdateLabel(self.config, sb)
1346 self.account_selector = QComboBox()
1347 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1348 sb.addPermanentWidget(self.account_selector)
1350 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1351 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1353 self.lock_icon = QIcon()
1354 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1355 sb.addPermanentWidget( self.password_button )
1357 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1358 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1359 sb.addPermanentWidget( self.seed_button )
1360 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1361 sb.addPermanentWidget( self.status_button )
1363 self.run_hook('create_status_bar', (sb,))
1365 self.setStatusBar(sb)
1368 def update_lock_icon(self):
1369 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1370 self.password_button.setIcon( icon )
1373 def update_buttons_on_seed(self):
1374 if self.wallet.seed:
1375 self.seed_button.show()
1376 self.password_button.show()
1377 self.send_button.setText(_("Send"))
1379 self.password_button.hide()
1380 self.seed_button.hide()
1381 self.send_button.setText(_("Create unsigned transaction"))
1384 def change_password_dialog(self):
1385 from password_dialog import PasswordDialog
1386 d = PasswordDialog(self.wallet, self)
1388 self.update_lock_icon()
1391 def new_contact_dialog(self):
1392 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1393 address = unicode(text)
1395 if is_valid(address):
1396 self.wallet.add_contact(address)
1397 self.update_contacts_tab()
1398 self.update_history_tab()
1399 self.update_completions()
1401 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1404 def new_account_dialog(self):
1406 dialog = QDialog(self)
1408 dialog.setWindowTitle(_("New Account"))
1410 addr = self.wallet.new_account_address()
1411 vbox = QVBoxLayout()
1412 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1417 ok_button = QPushButton(_("OK"))
1418 ok_button.setDefault(True)
1419 ok_button.clicked.connect(dialog.accept)
1421 hbox = QHBoxLayout()
1423 hbox.addWidget(ok_button)
1424 vbox.addLayout(hbox)
1426 dialog.setLayout(vbox)
1431 def show_master_public_key(self):
1432 dialog = QDialog(self)
1434 dialog.setWindowTitle(_("Master Public Key"))
1436 main_text = QTextEdit()
1437 main_text.setText(self.wallet.get_master_public_key())
1438 main_text.setReadOnly(True)
1439 main_text.setMaximumHeight(170)
1440 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1442 ok_button = QPushButton(_("OK"))
1443 ok_button.setDefault(True)
1444 ok_button.clicked.connect(dialog.accept)
1446 main_layout = QGridLayout()
1447 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1449 main_layout.addWidget(main_text, 1, 0)
1450 main_layout.addWidget(qrw, 1, 1 )
1452 vbox = QVBoxLayout()
1453 vbox.addLayout(main_layout)
1454 hbox = QHBoxLayout()
1456 hbox.addWidget(ok_button)
1457 vbox.addLayout(hbox)
1459 dialog.setLayout(vbox)
1464 def show_seed_dialog(self, password):
1465 if not self.wallet.seed:
1466 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1469 seed = self.wallet.decode_seed(password)
1471 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1474 from seed_dialog import SeedDialog
1475 d = SeedDialog(self)
1476 d.show_seed(seed, self.wallet.imported_keys)
1480 def show_qrcode(self, data, title = "QR code"):
1484 d.setWindowTitle(title)
1485 d.setMinimumSize(270, 300)
1486 vbox = QVBoxLayout()
1487 qrw = QRCodeWidget(data)
1488 vbox.addWidget(qrw, 1)
1489 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1490 hbox = QHBoxLayout()
1494 filename = "qrcode.bmp"
1495 bmp.save_qrcode(qrw.qr, filename)
1496 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1498 b = QPushButton(_("Save"))
1500 b.clicked.connect(print_qr)
1502 b = QPushButton(_("Close"))
1504 b.clicked.connect(d.accept)
1507 vbox.addLayout(hbox)
1512 def do_protect(self, func, args):
1513 if self.wallet.use_encryption:
1514 password = self.password_dialog()
1520 if args != (False,):
1521 args = (self,) + args + (password,)
1523 args = (self,password)
1528 def show_private_key(self, address, password):
1529 if not address: return
1531 pk_list = self.wallet.get_private_key(address, password)
1532 except BaseException, e:
1533 self.show_message(str(e))
1535 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1539 def do_sign(self, address, message, signature, password):
1541 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1542 signature.setText(sig)
1543 except BaseException, e:
1544 self.show_message(str(e))
1546 def sign_message(self, address):
1547 if not address: return
1550 d.setWindowTitle(_('Sign Message'))
1551 d.setMinimumSize(410, 290)
1553 tab_widget = QTabWidget()
1555 layout = QGridLayout(tab)
1557 sign_address = QLineEdit()
1559 sign_address.setText(address)
1560 layout.addWidget(QLabel(_('Address')), 1, 0)
1561 layout.addWidget(sign_address, 1, 1)
1563 sign_message = QTextEdit()
1564 layout.addWidget(QLabel(_('Message')), 2, 0)
1565 layout.addWidget(sign_message, 2, 1)
1566 layout.setRowStretch(2,3)
1568 sign_signature = QTextEdit()
1569 layout.addWidget(QLabel(_('Signature')), 3, 0)
1570 layout.addWidget(sign_signature, 3, 1)
1571 layout.setRowStretch(3,1)
1574 hbox = QHBoxLayout()
1575 b = QPushButton(_("Sign"))
1577 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1578 b = QPushButton(_("Close"))
1579 b.clicked.connect(d.accept)
1581 layout.addLayout(hbox, 4, 1)
1582 tab_widget.addTab(tab, _("Sign"))
1586 layout = QGridLayout(tab)
1588 verify_address = QLineEdit()
1589 layout.addWidget(QLabel(_('Address')), 1, 0)
1590 layout.addWidget(verify_address, 1, 1)
1592 verify_message = QTextEdit()
1593 layout.addWidget(QLabel(_('Message')), 2, 0)
1594 layout.addWidget(verify_message, 2, 1)
1595 layout.setRowStretch(2,3)
1597 verify_signature = QTextEdit()
1598 layout.addWidget(QLabel(_('Signature')), 3, 0)
1599 layout.addWidget(verify_signature, 3, 1)
1600 layout.setRowStretch(3,1)
1603 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1604 self.show_message(_("Signature verified"))
1606 self.show_message(_("Error: wrong signature"))
1608 hbox = QHBoxLayout()
1609 b = QPushButton(_("Verify"))
1610 b.clicked.connect(do_verify)
1612 b = QPushButton(_("Close"))
1613 b.clicked.connect(d.accept)
1615 layout.addLayout(hbox, 4, 1)
1616 tab_widget.addTab(tab, _("Verify"))
1618 vbox = QVBoxLayout()
1619 vbox.addWidget(tab_widget)
1626 def question(self, msg):
1627 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1629 def show_message(self, msg):
1630 QMessageBox.information(self, _('Message'), msg, _('OK'))
1632 def password_dialog(self ):
1639 vbox = QVBoxLayout()
1640 msg = _('Please enter your password')
1641 vbox.addWidget(QLabel(msg))
1643 grid = QGridLayout()
1645 grid.addWidget(QLabel(_('Password')), 1, 0)
1646 grid.addWidget(pw, 1, 1)
1647 vbox.addLayout(grid)
1649 vbox.addLayout(ok_cancel_buttons(d))
1652 self.run_hook('password_dialog', pw, grid, 1)
1653 if not d.exec_(): return
1654 return unicode(pw.text())
1661 def generate_transaction_information_widget(self, tx):
1662 tabs = QTabWidget(self)
1665 grid_ui = QGridLayout(tab1)
1666 grid_ui.setColumnStretch(0,1)
1667 tabs.addTab(tab1, _('Outputs') )
1669 tree_widget = MyTreeWidget(self)
1670 tree_widget.setColumnCount(2)
1671 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1672 tree_widget.setColumnWidth(0, 300)
1673 tree_widget.setColumnWidth(1, 50)
1675 for address, value in tx.outputs:
1676 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1677 tree_widget.addTopLevelItem(item)
1679 tree_widget.setMaximumHeight(100)
1681 grid_ui.addWidget(tree_widget)
1684 grid_ui = QGridLayout(tab2)
1685 grid_ui.setColumnStretch(0,1)
1686 tabs.addTab(tab2, _('Inputs') )
1688 tree_widget = MyTreeWidget(self)
1689 tree_widget.setColumnCount(2)
1690 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1692 for input_line in tx.inputs:
1693 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1694 tree_widget.addTopLevelItem(item)
1696 tree_widget.setMaximumHeight(100)
1698 grid_ui.addWidget(tree_widget)
1702 def tx_dict_from_text(self, txt):
1704 tx_dict = json.loads(str(txt))
1705 assert "hex" in tx_dict.keys()
1706 assert "complete" in tx_dict.keys()
1707 if not tx_dict["complete"]:
1708 assert "input_info" in tx_dict.keys()
1710 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1715 def read_tx_from_file(self):
1716 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1720 with open(fileName, "r") as f:
1721 file_content = f.read()
1722 except (ValueError, IOError, os.error), reason:
1723 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1725 return self.tx_dict_from_text(file_content)
1729 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1731 self.wallet.signrawtransaction(tx, input_info, [], password)
1733 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1735 with open(fileName, "w+") as f:
1736 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1737 self.show_message(_("Transaction saved successfully"))
1740 except BaseException, e:
1741 self.show_message(str(e))
1744 def send_raw_transaction(self, raw_tx, dialog = ""):
1745 result, result_message = self.wallet.sendtx( raw_tx )
1747 self.show_message("Transaction successfully sent: %s" % (result_message))
1751 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1753 def do_process_from_text(self):
1754 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1757 tx_dict = self.tx_dict_from_text(text)
1759 self.create_process_transaction_window(tx_dict)
1761 def do_process_from_file(self):
1762 tx_dict = self.read_tx_from_file()
1764 self.create_process_transaction_window(tx_dict)
1766 def do_process_from_csvReader(self, csvReader):
1769 for row in csvReader:
1771 amount = float(row[1])
1772 amount = int(100000000*amount)
1773 outputs.append((address, amount))
1774 except (ValueError, IOError, os.error), reason:
1775 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1779 tx = self.wallet.make_unsigned_transaction(outputs, None, None, account=self.current_account)
1780 except BaseException, e:
1781 self.show_message(str(e))
1784 tx_dict = tx.as_dict()
1785 self.create_process_transaction_window(tx_dict)
1787 def do_process_from_csv_file(self):
1788 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1792 with open(fileName, "r") as f:
1793 csvReader = csv.reader(f)
1794 self.do_process_from_csvReader(csvReader)
1795 except (ValueError, IOError, os.error), reason:
1796 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1799 def do_process_from_csv_text(self):
1800 text = text_dialog(self, _('Input CSV'), _("CSV:"), _("Load CSV"))
1803 f = StringIO.StringIO(text)
1804 csvReader = csv.reader(f)
1805 self.do_process_from_csvReader(csvReader)
1807 def create_process_transaction_window(self, tx_dict):
1808 tx = Transaction(tx_dict["hex"])
1810 dialog = QDialog(self)
1811 dialog.setMinimumWidth(500)
1812 dialog.setWindowTitle(_('Process raw transaction'))
1818 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1819 l.addWidget(QLabel(_("Actions")), 4,0)
1821 if tx_dict["complete"] == False:
1822 l.addWidget(QLabel(_("Unsigned")), 3,1)
1823 if self.wallet.seed :
1824 b = QPushButton("Sign transaction")
1825 input_info = json.loads(tx_dict["input_info"])
1826 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1827 l.addWidget(b, 4, 1)
1829 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1831 l.addWidget(QLabel(_("Signed")), 3,1)
1832 b = QPushButton("Broadcast transaction")
1833 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1836 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1837 cancelButton = QPushButton(_("Cancel"))
1838 cancelButton.clicked.connect(lambda: dialog.done(0))
1839 l.addWidget(cancelButton, 4,2)
1845 def do_export_privkeys(self, password):
1846 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.")))
1849 select_export = _('Select file to export your private keys to')
1850 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1852 with open(fileName, "w+") as csvfile:
1853 transaction = csv.writer(csvfile)
1854 transaction.writerow(["address", "private_key"])
1856 addresses = self.wallet.addresses(True)
1858 for addr in addresses:
1859 pk = "".join(self.wallet.get_private_key(addr, password))
1860 transaction.writerow(["%34s"%addr,pk])
1862 self.show_message(_("Private keys exported."))
1864 except (IOError, os.error), reason:
1865 export_error_label = _("Electrum was unable to produce a private key-export.")
1866 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1868 except BaseException, e:
1869 self.show_message(str(e))
1873 def do_import_labels(self):
1874 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1875 if not labelsFile: return
1877 f = open(labelsFile, 'r')
1880 for key, value in json.loads(data).items():
1881 self.wallet.set_label(key, value)
1882 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1883 except (IOError, os.error), reason:
1884 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1887 def do_export_labels(self):
1888 labels = self.wallet.labels
1890 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1892 with open(fileName, 'w+') as f:
1893 json.dump(labels, f)
1894 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1895 except (IOError, os.error), reason:
1896 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1899 def do_export_history(self):
1900 from lite_window import csv_transaction
1901 csv_transaction(self.wallet)
1905 def do_import_privkey(self, password):
1906 if not self.wallet.imported_keys:
1907 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1908 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1909 + _('Are you sure you understand what you are doing?'), 3, 4)
1912 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1915 text = str(text).split()
1920 addr = self.wallet.import_key(key, password)
1921 except BaseException as e:
1927 addrlist.append(addr)
1929 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1931 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1932 self.update_receive_tab()
1933 self.update_history_tab()
1936 def settings_dialog(self):
1938 d.setWindowTitle(_('Electrum Settings'))
1940 vbox = QVBoxLayout()
1942 tabs = QTabWidget(self)
1943 self.settings_tab = tabs
1944 vbox.addWidget(tabs)
1947 grid_ui = QGridLayout(tab1)
1948 grid_ui.setColumnStretch(0,1)
1949 tabs.addTab(tab1, _('Display') )
1951 nz_label = QLabel(_('Display zeros'))
1952 grid_ui.addWidget(nz_label, 0, 0)
1953 nz_e = AmountEdit(None,True)
1954 nz_e.setText("%d"% self.num_zeros)
1955 grid_ui.addWidget(nz_e, 0, 1)
1956 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1957 grid_ui.addWidget(HelpButton(msg), 0, 2)
1958 if not self.config.is_modifiable('num_zeros'):
1959 for w in [nz_e, nz_label]: w.setEnabled(False)
1961 lang_label=QLabel(_('Language') + ':')
1962 grid_ui.addWidget(lang_label, 1, 0)
1963 lang_combo = QComboBox()
1964 from electrum.i18n import languages
1965 lang_combo.addItems(languages.values())
1967 index = languages.keys().index(self.config.get("language",''))
1970 lang_combo.setCurrentIndex(index)
1971 grid_ui.addWidget(lang_combo, 1, 1)
1972 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1973 if not self.config.is_modifiable('language'):
1974 for w in [lang_combo, lang_label]: w.setEnabled(False)
1976 currencies = self.exchanger.get_currencies()
1977 currencies.insert(0, "None")
1979 cur_label=QLabel(_('Currency') + ':')
1980 grid_ui.addWidget(cur_label , 2, 0)
1981 cur_combo = QComboBox()
1982 cur_combo.addItems(currencies)
1984 index = currencies.index(self.config.get('currency', "None"))
1987 cur_combo.setCurrentIndex(index)
1988 grid_ui.addWidget(cur_combo, 2, 1)
1989 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1991 expert_cb = QCheckBox(_('Expert mode'))
1992 expert_cb.setChecked(self.expert_mode)
1993 grid_ui.addWidget(expert_cb, 3, 0)
1994 hh = _('In expert mode, your client will:') + '\n' \
1995 + _(' - Show change addresses in the Receive tab') + '\n' \
1996 + _(' - Display the balance of each address') + '\n' \
1997 + _(' - Add freeze/prioritize actions to addresses.')
1998 grid_ui.addWidget(HelpButton(hh), 3, 2)
1999 grid_ui.setRowStretch(4,1)
2003 grid_wallet = QGridLayout(tab2)
2004 grid_wallet.setColumnStretch(0,1)
2005 tabs.addTab(tab2, _('Wallet') )
2007 fee_label = QLabel(_('Transaction fee'))
2008 grid_wallet.addWidget(fee_label, 0, 0)
2009 fee_e = AmountEdit(self.base_unit)
2010 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2011 grid_wallet.addWidget(fee_e, 0, 2)
2012 msg = _('Fee per kilobyte of transaction.') + ' ' \
2013 + _('Recommended value') + ': ' + self.format_amount(50000)
2014 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2015 if not self.config.is_modifiable('fee_per_kb'):
2016 for w in [fee_e, fee_label]: w.setEnabled(False)
2018 usechange_cb = QCheckBox(_('Use change addresses'))
2019 usechange_cb.setChecked(self.wallet.use_change)
2020 grid_wallet.addWidget(usechange_cb, 1, 0)
2021 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2022 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2024 gap_label = QLabel(_('Gap limit'))
2025 grid_wallet.addWidget(gap_label, 2, 0)
2026 gap_e = AmountEdit(None,True)
2027 gap_e.setText("%d"% self.wallet.gap_limit)
2028 grid_wallet.addWidget(gap_e, 2, 2)
2029 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2030 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2031 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2032 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2033 + _('Warning') + ': ' \
2034 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2035 + _('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'
2036 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2037 if not self.config.is_modifiable('gap_limit'):
2038 for w in [gap_e, gap_label]: w.setEnabled(False)
2040 units = ['BTC', 'mBTC']
2041 unit_label = QLabel(_('Base unit'))
2042 grid_wallet.addWidget(unit_label, 3, 0)
2043 unit_combo = QComboBox()
2044 unit_combo.addItems(units)
2045 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2046 grid_wallet.addWidget(unit_combo, 3, 2)
2047 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2048 + '\n1BTC=1000mBTC.\n' \
2049 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2050 grid_wallet.setRowStretch(4,1)
2054 tab5 = QScrollArea()
2055 tab5.setEnabled(True)
2056 tab5.setWidgetResizable(True)
2058 grid_plugins = QGridLayout()
2059 grid_plugins.setColumnStretch(0,1)
2062 w.setLayout(grid_plugins)
2065 w.setMinimumHeight(len(self.plugins)*35)
2067 tabs.addTab(tab5, _('Plugins') )
2068 def mk_toggle(cb, p):
2069 return lambda: cb.setChecked(p.toggle())
2070 for i, p in enumerate(self.plugins):
2072 cb = QCheckBox(p.fullname())
2073 cb.setDisabled(not p.is_available())
2074 cb.setChecked(p.is_enabled())
2075 cb.clicked.connect(mk_toggle(cb,p))
2076 grid_plugins.addWidget(cb, i, 0)
2077 if p.requires_settings():
2078 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2079 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2081 print_msg("Error: cannot display plugin", p)
2082 traceback.print_exc(file=sys.stdout)
2083 grid_plugins.setRowStretch(i+1,1)
2085 self.run_hook('create_settings_tab', tabs)
2087 vbox.addLayout(ok_cancel_buttons(d))
2091 if not d.exec_(): return
2093 fee = unicode(fee_e.text())
2095 fee = self.read_amount(fee)
2097 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2100 self.wallet.set_fee(fee)
2102 nz = unicode(nz_e.text())
2107 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2110 if self.num_zeros != nz:
2112 self.config.set_key('num_zeros', nz, True)
2113 self.update_history_tab()
2114 self.update_receive_tab()
2116 usechange_result = usechange_cb.isChecked()
2117 if self.wallet.use_change != usechange_result:
2118 self.wallet.use_change = usechange_result
2119 self.config.set_key('use_change', self.wallet.use_change, True)
2121 unit_result = units[unit_combo.currentIndex()]
2122 if self.base_unit() != unit_result:
2123 self.decimal_point = 8 if unit_result == 'BTC' else 5
2124 self.config.set_key('decimal_point', self.decimal_point, True)
2125 self.update_history_tab()
2126 self.update_status()
2129 n = int(gap_e.text())
2131 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2134 if self.wallet.gap_limit != n:
2135 r = self.wallet.change_gap_limit(n)
2137 self.update_receive_tab()
2138 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2140 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2142 need_restart = False
2144 lang_request = languages.keys()[lang_combo.currentIndex()]
2145 if lang_request != self.config.get('language'):
2146 self.config.set_key("language", lang_request, True)
2149 cur_request = str(currencies[cur_combo.currentIndex()])
2150 if cur_request != self.config.get('currency', "None"):
2151 self.config.set_key('currency', cur_request, True)
2152 self.update_wallet()
2154 self.run_hook('close_settings_dialog')
2157 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2159 self.receive_tab_set_mode(expert_cb.isChecked())
2161 def run_network_dialog(self):
2162 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2164 def closeEvent(self, event):
2166 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2167 self.save_column_widths()
2168 self.config.set_key("console-history", self.console.history[-50:], True)