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 i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
31 from PyQt4.QtGui import *
32 from PyQt4.QtCore import *
33 import PyQt4.QtCore as QtCore
35 from electrum.bitcoin import MIN_RELAY_TX_FEE
40 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
42 from electrum.wallet import format_satoshis
43 from electrum.bitcoin import Transaction, is_valid
44 from electrum import mnemonic
45 from electrum import util, bitcoin, commands
47 import bmp, pyqrnative
50 from amountedit import AmountEdit
51 from network_dialog import NetworkDialog
52 from qrcodewidget import QRCodeWidget
54 from decimal import Decimal
62 if platform.system() == 'Windows':
63 MONOSPACE_FONT = 'Lucida Console'
64 elif platform.system() == 'Darwin':
65 MONOSPACE_FONT = 'Monaco'
67 MONOSPACE_FONT = 'monospace'
69 from electrum import ELECTRUM_VERSION
74 class UpdateLabel(QLabel):
75 def __init__(self, config, parent=None):
76 QLabel.__init__(self, parent)
77 self.new_version = False
80 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
81 con.request("GET", "/version")
82 res = con.getresponse()
83 except socket.error as msg:
84 print_error("Could not retrieve version information")
88 self.latest_version = res.read()
89 self.latest_version = self.latest_version.replace("\n","")
90 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
92 self.current_version = ELECTRUM_VERSION
93 if(self.compare_versions(self.latest_version, self.current_version) == 1):
94 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
95 if(self.compare_versions(self.latest_version, latest_seen) == 1):
96 self.new_version = True
97 self.setText(_("New version available") + ": " + self.latest_version)
100 def compare_versions(self, version1, version2):
102 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
103 return cmp(normalize(version1), normalize(version2))
105 def ignore_this_version(self):
107 self.config.set_key("last_seen_version", self.latest_version, True)
108 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
111 def ignore_all_version(self):
113 self.config.set_key("last_seen_version", "9.9.9", True)
114 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
117 def open_website(self):
118 webbrowser.open("http://electrum.org/download.html")
121 def mouseReleaseEvent(self, event):
122 dialog = QDialog(self)
123 dialog.setWindowTitle(_('Electrum update'))
126 main_layout = QGridLayout()
127 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
129 ignore_version = QPushButton(_("Ignore this version"))
130 ignore_version.clicked.connect(self.ignore_this_version)
132 ignore_all_versions = QPushButton(_("Ignore all versions"))
133 ignore_all_versions.clicked.connect(self.ignore_all_version)
135 open_website = QPushButton(_("Goto download page"))
136 open_website.clicked.connect(self.open_website)
138 main_layout.addWidget(ignore_version, 1, 0)
139 main_layout.addWidget(ignore_all_versions, 1, 1)
140 main_layout.addWidget(open_website, 1, 2)
142 dialog.setLayout(main_layout)
146 if not dialog.exec_(): return
150 class Timer(QtCore.QThread):
153 self.emit(QtCore.SIGNAL('timersignal'))
156 class HelpButton(QPushButton):
157 def __init__(self, text):
158 QPushButton.__init__(self, '?')
159 self.setFocusPolicy(Qt.NoFocus)
160 self.setFixedWidth(20)
161 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
164 class EnterButton(QPushButton):
165 def __init__(self, text, func):
166 QPushButton.__init__(self, text)
168 self.clicked.connect(func)
170 def keyPressEvent(self, e):
171 if e.key() == QtCore.Qt.Key_Return:
174 class MyTreeWidget(QTreeWidget):
175 def __init__(self, parent):
176 QTreeWidget.__init__(self, parent)
179 for i in range(0,self.viewport().height()/5):
180 if self.itemAt(QPoint(0,i*5)) == item:
184 for j in range(0,30):
185 if self.itemAt(QPoint(0,i*5 + j)) != item:
187 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
189 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
194 class StatusBarButton(QPushButton):
195 def __init__(self, icon, tooltip, func):
196 QPushButton.__init__(self, icon, '')
197 self.setToolTip(tooltip)
199 self.setMaximumWidth(25)
200 self.clicked.connect(func)
203 def keyPressEvent(self, e):
204 if e.key() == QtCore.Qt.Key_Return:
211 def waiting_dialog(f):
217 w.setWindowTitle('Electrum')
227 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
236 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
238 class ElectrumWindow(QMainWindow):
240 def __init__(self, wallet, config):
241 QMainWindow.__init__(self)
245 self.current_account = self.config.get("current_account", None)
247 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
248 self.notifier = QSystemTrayIcon(self.icon, self)
249 self.notifier.setToolTip('Electrum')
253 self.create_status_bar()
255 self.need_update = threading.Event()
256 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
257 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
258 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
259 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
260 self.wallet.interface.register_callback('new_transaction', self.notify_transactions)
262 self.expert_mode = config.get('classic_expert_mode', False)
263 self.decimal_point = config.get('decimal_point', 8)
265 set_language(config.get('language'))
267 self.funds_error = False
268 self.completions = QStringListModel()
270 self.tabs = tabs = QTabWidget(self)
271 self.column_widths = self.config.get("column_widths", default_column_widths )
272 tabs.addTab(self.create_history_tab(), _('History') )
273 tabs.addTab(self.create_send_tab(), _('Send') )
274 tabs.addTab(self.create_receive_tab(), _('Receive') )
275 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
276 tabs.addTab(self.create_console_tab(), _('Console') )
277 tabs.setMinimumSize(600, 400)
278 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
279 self.setCentralWidget(tabs)
281 g = self.config.get("winpos-qt",[100, 100, 840, 400])
282 self.setGeometry(g[0], g[1], g[2], g[3])
283 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
284 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
285 self.setWindowTitle( title )
289 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
290 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
291 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
292 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
293 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
295 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
296 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
298 self.history_list.setFocus(True)
300 self.exchanger = exchange_rate.Exchanger(self)
301 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
303 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
304 if platform.system() == 'Windows':
305 n = 3 if self.wallet.seed else 2
306 tabs.setCurrentIndex (n)
307 tabs.setCurrentIndex (0)
310 if self.wallet.fee < 50000:
311 self.wallet.set_fee(50000)
312 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
314 # set initial message
315 self.console.showMessage(self.wallet.interface.banner)
317 # plugins that need to change the GUI do it here
318 self.run_hook('init_gui')
321 def select_wallet_file(self):
322 wallet_folder = self.wallet.config.path
323 re.sub("(\/\w*.dat)$", "", wallet_folder)
324 file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
328 self.load_wallet(file_name)
331 def init_menubar(self):
334 electrum_menu = menubar.addMenu(_("&File"))
335 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
336 open_wallet_action.triggered.connect(self.select_wallet_file)
338 preferences_name = _("Preferences")
339 if sys.platform == 'darwin':
340 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
342 preferences_menu = electrum_menu.addAction(preferences_name)
343 preferences_menu.triggered.connect(self.settings_dialog)
344 electrum_menu.addSeparator()
346 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
348 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
349 raw_transaction_file.triggered.connect(self.do_process_from_file)
351 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
352 raw_transaction_text.triggered.connect(self.do_process_from_text)
354 electrum_menu.addSeparator()
355 quit_item = electrum_menu.addAction(_("&Close"))
356 quit_item.triggered.connect(self.close)
358 wallet_menu = menubar.addMenu(_("&Wallet"))
359 wallet_backup = wallet_menu.addAction(_("&Create backup"))
360 wallet_backup.triggered.connect(backup_wallet)
362 show_menu = wallet_menu.addMenu(_("Show"))
365 show_seed = show_menu.addAction(_("&Seed"))
366 show_seed.triggered.connect(self.show_seed_dialog)
368 show_mpk = show_menu.addAction(_("&Master Public Key"))
369 show_mpk.triggered.connect(self.show_master_public_key)
371 wallet_menu.addSeparator()
372 new_contact = wallet_menu.addAction(_("&New contact"))
373 new_contact.triggered.connect(self.new_contact_dialog)
375 import_menu = menubar.addMenu(_("&Import"))
376 in_labels = import_menu.addAction(_("&Labels"))
377 in_labels.triggered.connect(self.do_import_labels)
379 in_private_keys = import_menu.addAction(_("&Private keys"))
380 in_private_keys.triggered.connect(self.do_import_privkey)
382 export_menu = menubar.addMenu(_("&Export"))
383 ex_private_keys = export_menu.addAction(_("&Private keys"))
384 ex_private_keys.triggered.connect(self.do_export_privkeys)
386 ex_history = export_menu.addAction(_("&History"))
387 ex_history.triggered.connect(self.do_export_history)
389 ex_labels = export_menu.addAction(_("&Labels"))
390 ex_labels.triggered.connect(self.do_export_labels)
392 help_menu = menubar.addMenu(_("&Help"))
393 doc_open = help_menu.addAction(_("&Documentation"))
394 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
395 web_open = help_menu.addAction(_("&Official website"))
396 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
398 self.setMenuBar(menubar)
400 def load_wallet(self, filename):
403 config = electrum.SimpleConfig({'wallet_path': filename})
404 if not config.wallet_file_exists:
405 self.show_message("file not found "+ filename)
408 #self.wallet.verifier.stop()
409 interface = self.wallet.interface
410 verifier = self.wallet.verifier
411 self.wallet.synchronizer.stop()
414 self.wallet = electrum.Wallet(self.config)
415 self.wallet.interface = interface
416 self.wallet.verifier = verifier
418 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
423 def notify_transactions(self):
424 for tx in self.wallet.interface.pending_transactions:
426 self.wallet.interface.pending_transactions.remove(tx)
427 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
428 self.notify("New transaction received. %s BTC" % (self.format_amount(v)))
430 def notify(self, message):
431 self.notifier.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
434 def init_plugins(self):
435 import imp, pkgutil, __builtin__
436 if __builtin__.use_local_modules:
437 fp, pathname, description = imp.find_module('plugins')
438 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
439 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
440 imp.load_module('electrum_plugins', fp, pathname, description)
441 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
443 import electrum_plugins
444 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
445 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
450 self.plugins.append( p.Plugin(self) )
452 print_msg("Error:cannot initialize plugin",p)
453 traceback.print_exc(file=sys.stdout)
456 def run_hook(self, name, *args):
457 for p in self.plugins:
458 if not p.is_enabled():
467 print_error("Plugin error")
468 traceback.print_exc(file=sys.stdout)
473 def set_label(self, name, text = None):
475 old_text = self.wallet.labels.get(name)
478 self.wallet.labels[name] = text
479 self.wallet.config.set_key('labels', self.wallet.labels)
483 self.wallet.labels.pop(name)
485 self.run_hook('set_label', name, text, changed)
489 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
490 def getOpenFileName(self, title, filter = None):
491 directory = self.config.get('io_dir', os.path.expanduser('~'))
492 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
493 if fileName and directory != os.path.dirname(fileName):
494 self.config.set_key('io_dir', os.path.dirname(fileName), True)
497 def getSaveFileName(self, title, filename, filter = None):
498 directory = self.config.get('io_dir', os.path.expanduser('~'))
499 path = os.path.join( directory, filename )
500 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
501 if fileName and directory != os.path.dirname(fileName):
502 self.config.set_key('io_dir', os.path.dirname(fileName), True)
508 QMainWindow.close(self)
509 self.run_hook('close_main_window')
511 def connect_slots(self, sender):
512 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
513 self.previous_payto_e=''
515 def timer_actions(self):
516 if self.need_update.is_set():
518 self.need_update.clear()
519 self.run_hook('timer_actions')
521 def format_amount(self, x, is_diff=False, whitespaces=False):
522 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
524 def read_amount(self, x):
525 if x in['.', '']: return None
526 p = pow(10, self.decimal_point)
527 return int( p * Decimal(x) )
530 assert self.decimal_point in [5,8]
531 return "BTC" if self.decimal_point == 8 else "mBTC"
533 def update_status(self):
534 if self.wallet.interface and self.wallet.interface.is_connected:
535 if not self.wallet.up_to_date:
536 text = _("Synchronizing...")
537 icon = QIcon(":icons/status_waiting.png")
539 c, u = self.wallet.get_account_balance(self.current_account)
540 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
541 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
542 text += self.create_quote_text(Decimal(c+u)/100000000)
543 icon = QIcon(":icons/status_connected.png")
545 text = _("Not connected")
546 icon = QIcon(":icons/status_disconnected.png")
548 self.balance_label.setText(text)
549 self.status_button.setIcon( icon )
551 def update_wallet(self):
553 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
554 self.update_history_tab()
555 self.update_receive_tab()
556 self.update_contacts_tab()
557 self.update_completions()
560 def create_quote_text(self, btc_balance):
561 quote_currency = self.config.get("currency", "None")
562 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
563 if quote_balance is None:
566 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
569 def create_history_tab(self):
570 self.history_list = l = MyTreeWidget(self)
572 for i,width in enumerate(self.column_widths['history']):
573 l.setColumnWidth(i, width)
574 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
575 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
576 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
578 l.setContextMenuPolicy(Qt.CustomContextMenu)
579 l.customContextMenuRequested.connect(self.create_history_menu)
583 def create_history_menu(self, position):
584 self.history_list.selectedIndexes()
585 item = self.history_list.currentItem()
587 tx_hash = str(item.data(0, Qt.UserRole).toString())
588 if not tx_hash: return
590 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
591 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
592 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
593 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
596 def show_tx_details(self, tx):
597 dialog = QDialog(self)
599 dialog.setWindowTitle(_("Transaction Details"))
601 dialog.setLayout(vbox)
602 dialog.setMinimumSize(600,300)
605 if tx_hash in self.wallet.transactions.keys():
606 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
607 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
609 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
615 vbox.addWidget(QLabel("Transaction ID:"))
616 e = QLineEdit(tx_hash)
620 vbox.addWidget(QLabel("Date: %s"%time_str))
621 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
624 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
625 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
627 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
628 vbox.addWidget(QLabel("Transaction fee: unknown"))
630 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
632 vbox.addWidget( self.generate_transaction_information_widget(tx) )
634 ok_button = QPushButton(_("Close"))
635 ok_button.setDefault(True)
636 ok_button.clicked.connect(dialog.accept)
640 hbox.addWidget(ok_button)
644 def tx_label_clicked(self, item, column):
645 if column==2 and item.isSelected():
647 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
648 self.history_list.editItem( item, column )
649 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
652 def tx_label_changed(self, item, column):
656 tx_hash = str(item.data(0, Qt.UserRole).toString())
657 tx = self.wallet.transactions.get(tx_hash)
658 text = unicode( item.text(2) )
659 self.set_label(tx_hash, text)
661 item.setForeground(2, QBrush(QColor('black')))
663 text = self.wallet.get_default_label(tx_hash)
664 item.setText(2, text)
665 item.setForeground(2, QBrush(QColor('gray')))
669 def edit_label(self, is_recv):
670 l = self.receive_list if is_recv else self.contacts_list
671 item = l.currentItem()
672 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
673 l.editItem( item, 1 )
674 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
678 def address_label_clicked(self, item, column, l, column_addr, column_label):
679 if column == column_label and item.isSelected():
680 is_editable = item.data(0, 32).toBool()
683 addr = unicode( item.text(column_addr) )
684 label = unicode( item.text(column_label) )
685 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
686 l.editItem( item, column )
687 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
690 def address_label_changed(self, item, column, l, column_addr, column_label):
691 if column == column_label:
692 addr = unicode( item.text(column_addr) )
693 text = unicode( item.text(column_label) )
694 is_editable = item.data(0, 32).toBool()
698 changed = self.set_label(addr, text)
700 self.update_history_tab()
701 self.update_completions()
703 self.current_item_changed(item)
705 self.run_hook('item_changed', item, column)
708 def current_item_changed(self, a):
709 self.run_hook('current_item_changed', a)
713 def update_history_tab(self):
715 self.history_list.clear()
716 for item in self.wallet.get_tx_history(self.current_account):
717 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
720 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
725 time_str = 'unverified'
726 icon = QIcon(":icons/unconfirmed.png")
729 icon = QIcon(":icons/unconfirmed.png")
731 icon = QIcon(":icons/clock%d.png"%conf)
733 icon = QIcon(":icons/confirmed.png")
735 if value is not None:
736 v_str = self.format_amount(value, True, whitespaces=True)
740 balance_str = self.format_amount(balance, whitespaces=True)
743 label, is_default_label = self.wallet.get_label(tx_hash)
745 label = _('Pruned transaction outputs')
746 is_default_label = False
748 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
749 item.setFont(2, QFont(MONOSPACE_FONT))
750 item.setFont(3, QFont(MONOSPACE_FONT))
751 item.setFont(4, QFont(MONOSPACE_FONT))
753 item.setForeground(3, QBrush(QColor("#BC1E1E")))
755 item.setData(0, Qt.UserRole, tx_hash)
756 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
758 item.setForeground(2, QBrush(QColor('grey')))
760 item.setIcon(0, icon)
761 self.history_list.insertTopLevelItem(0,item)
764 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
767 def create_send_tab(self):
772 grid.setColumnMinimumWidth(3,300)
773 grid.setColumnStretch(5,1)
776 self.payto_e = QLineEdit()
777 grid.addWidget(QLabel(_('Pay to')), 1, 0)
778 grid.addWidget(self.payto_e, 1, 1, 1, 3)
780 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)
782 completer = QCompleter()
783 completer.setCaseSensitivity(False)
784 self.payto_e.setCompleter(completer)
785 completer.setModel(self.completions)
787 self.message_e = QLineEdit()
788 grid.addWidget(QLabel(_('Description')), 2, 0)
789 grid.addWidget(self.message_e, 2, 1, 1, 3)
790 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)
792 self.amount_e = AmountEdit(self.base_unit)
793 grid.addWidget(QLabel(_('Amount')), 3, 0)
794 grid.addWidget(self.amount_e, 3, 1, 1, 2)
795 grid.addWidget(HelpButton(
796 _('Amount to be sent.') + '\n\n' \
797 + _('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.') \
798 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
800 self.fee_e = AmountEdit(self.base_unit)
801 grid.addWidget(QLabel(_('Fee')), 4, 0)
802 grid.addWidget(self.fee_e, 4, 1, 1, 2)
803 grid.addWidget(HelpButton(
804 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
805 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
806 + _('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)
809 b = EnterButton(_("Send"), self.do_send)
811 b = EnterButton(_("Create unsigned transaction"), self.do_send)
812 grid.addWidget(b, 6, 1)
814 b = EnterButton(_("Clear"),self.do_clear)
815 grid.addWidget(b, 6, 2)
817 self.payto_sig = QLabel('')
818 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
820 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
821 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
830 def entry_changed( is_fee ):
831 self.funds_error = False
833 if self.amount_e.is_shortcut:
834 self.amount_e.is_shortcut = False
835 c, u = self.wallet.get_account_balance(self.current_account)
836 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
837 fee = self.wallet.estimated_fee(inputs)
839 self.amount_e.setText( self.format_amount(amount) )
840 self.fee_e.setText( self.format_amount( fee ) )
843 amount = self.read_amount(str(self.amount_e.text()))
844 fee = self.read_amount(str(self.fee_e.text()))
846 if not is_fee: fee = None
849 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
851 self.fee_e.setText( self.format_amount( fee ) )
854 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
858 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
859 self.funds_error = True
860 text = _( "Not enough funds" )
861 c, u = self.wallet.get_frozen_balance()
862 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
864 self.statusBar().showMessage(text)
865 self.amount_e.setPalette(palette)
866 self.fee_e.setPalette(palette)
868 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
869 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
871 self.run_hook('create_send_tab', grid)
875 def update_completions(self):
877 for addr,label in self.wallet.labels.items():
878 if addr in self.wallet.addressbook:
879 l.append( label + ' <' + addr + '>')
881 self.run_hook('update_completions', l)
882 self.completions.setStringList(l)
886 return lambda s, *args: s.do_protect(func, args)
891 label = unicode( self.message_e.text() )
892 r = unicode( self.payto_e.text() )
895 # label or alias, with address in brackets
896 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
897 to_address = m.group(2) if m else r
899 if not is_valid(to_address):
900 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
904 amount = self.read_amount(unicode( self.amount_e.text()))
906 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
909 fee = self.read_amount(unicode( self.fee_e.text()))
911 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
914 confirm_amount = self.config.get('confirm_amount', 100000000)
915 if amount >= confirm_amount:
916 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
919 self.send_tx(to_address, amount, fee, label)
923 def send_tx(self, to_address, amount, fee, label, password):
926 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
927 except BaseException, e:
928 self.show_message(str(e))
931 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
932 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
935 self.run_hook('send_tx', tx)
938 self.set_label(tx.hash(), label)
941 h = self.wallet.send_tx(tx)
942 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
943 status, msg = self.wallet.receive_tx( h )
945 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
947 self.update_contacts_tab()
949 QMessageBox.warning(self, _('Error'), msg, _('OK'))
951 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
953 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
954 with open(fileName,'w') as f:
955 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
956 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
958 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
963 def set_url(self, url):
964 address, amount, label, message, signature, identity, url = util.parse_url(url)
965 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
967 if label and self.wallet.labels.get(address) != label:
968 if self.question('Give label "%s" to address %s ?'%(label,address)):
969 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
970 self.wallet.addressbook.append(address)
971 self.set_label(address, label)
973 self.run_hook('set_url', url, self.show_message, self.question)
975 self.tabs.setCurrentIndex(1)
976 label = self.wallet.labels.get(address)
977 m_addr = label + ' <'+ address +'>' if label else address
978 self.payto_e.setText(m_addr)
980 self.message_e.setText(message)
981 self.amount_e.setText(amount)
983 self.set_frozen(self.payto_e,True)
984 self.set_frozen(self.amount_e,True)
985 self.set_frozen(self.message_e,True)
986 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
988 self.payto_sig.setVisible(False)
991 self.payto_sig.setVisible(False)
992 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
994 self.set_frozen(e,False)
997 def set_frozen(self,entry,frozen):
999 entry.setReadOnly(True)
1000 entry.setFrame(False)
1001 palette = QPalette()
1002 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1003 entry.setPalette(palette)
1005 entry.setReadOnly(False)
1006 entry.setFrame(True)
1007 palette = QPalette()
1008 palette.setColor(entry.backgroundRole(), QColor('white'))
1009 entry.setPalette(palette)
1012 def toggle_freeze(self,addr):
1014 if addr in self.wallet.frozen_addresses:
1015 self.wallet.unfreeze(addr)
1017 self.wallet.freeze(addr)
1018 self.update_receive_tab()
1020 def toggle_priority(self,addr):
1022 if addr in self.wallet.prioritized_addresses:
1023 self.wallet.unprioritize(addr)
1025 self.wallet.prioritize(addr)
1026 self.update_receive_tab()
1029 def create_list_tab(self, headers):
1030 "generic tab creation method"
1031 l = MyTreeWidget(self)
1032 l.setColumnCount( len(headers) )
1033 l.setHeaderLabels( headers )
1036 vbox = QVBoxLayout()
1043 vbox.addWidget(buttons)
1045 hbox = QHBoxLayout()
1048 buttons.setLayout(hbox)
1053 def create_receive_tab(self):
1054 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1055 l.setContextMenuPolicy(Qt.CustomContextMenu)
1056 l.customContextMenuRequested.connect(self.create_receive_menu)
1057 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1058 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1059 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1060 self.receive_list = l
1061 self.receive_buttons_hbox = hbox
1066 def receive_tab_set_mode(self, i):
1067 self.save_column_widths()
1068 self.expert_mode = (i == 1)
1069 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1070 self.update_receive_tab()
1073 def save_column_widths(self):
1074 if not self.expert_mode:
1075 widths = [ self.receive_list.columnWidth(0) ]
1078 for i in range(self.receive_list.columnCount() -1):
1079 widths.append(self.receive_list.columnWidth(i))
1080 self.column_widths["receive"][self.expert_mode] = widths
1082 self.column_widths["history"] = []
1083 for i in range(self.history_list.columnCount() - 1):
1084 self.column_widths["history"].append(self.history_list.columnWidth(i))
1086 self.column_widths["contacts"] = []
1087 for i in range(self.contacts_list.columnCount() - 1):
1088 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1090 self.config.set_key("column_widths", self.column_widths, True)
1093 def create_contacts_tab(self):
1094 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1095 l.setContextMenuPolicy(Qt.CustomContextMenu)
1096 l.customContextMenuRequested.connect(self.create_contact_menu)
1097 for i,width in enumerate(self.column_widths['contacts']):
1098 l.setColumnWidth(i, width)
1100 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1101 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1102 self.contacts_list = l
1103 self.contacts_buttons_hbox = hbox
1108 def delete_imported_key(self, addr):
1109 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1110 self.wallet.delete_imported_key(addr)
1111 self.update_receive_tab()
1112 self.update_history_tab()
1115 def create_receive_menu(self, position):
1116 # fixme: this function apparently has a side effect.
1117 # if it is not called the menu pops up several times
1118 #self.receive_list.selectedIndexes()
1120 item = self.receive_list.itemAt(position)
1122 addr = unicode(item.text(0))
1123 if not is_valid(addr):
1124 item.setExpanded(not item.isExpanded())
1127 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1128 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1129 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1130 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1131 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1132 if addr in self.wallet.imported_keys:
1133 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1135 if self.expert_mode:
1136 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1137 menu.addAction(t, lambda: self.toggle_freeze(addr))
1138 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1139 menu.addAction(t, lambda: self.toggle_priority(addr))
1141 self.run_hook('receive_menu', menu)
1142 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1145 def payto(self, addr):
1147 label = self.wallet.labels.get(addr)
1148 m_addr = label + ' <' + addr + '>' if label else addr
1149 self.tabs.setCurrentIndex(1)
1150 self.payto_e.setText(m_addr)
1151 self.amount_e.setFocus()
1154 def delete_contact(self, x):
1155 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1156 self.wallet.delete_contact(x)
1157 self.set_label(x, None)
1158 self.update_history_tab()
1159 self.update_contacts_tab()
1160 self.update_completions()
1163 def create_contact_menu(self, position):
1164 item = self.contacts_list.itemAt(position)
1166 addr = unicode(item.text(0))
1167 label = unicode(item.text(1))
1168 is_editable = item.data(0,32).toBool()
1169 payto_addr = item.data(0,33).toString()
1171 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1172 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1173 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1175 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1176 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1178 self.run_hook('create_contact_menu', menu, item)
1179 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1182 def update_receive_item(self, item):
1183 item.setFont(0, QFont(MONOSPACE_FONT))
1184 address = str(item.data(0,0).toString())
1185 label = self.wallet.labels.get(address,'')
1186 item.setData(1,0,label)
1187 item.setData(0,32, True) # is editable
1189 self.run_hook('update_receive_item', address, item)
1191 c, u = self.wallet.get_addr_balance(address)
1192 balance = self.format_amount(c + u)
1193 item.setData(2,0,balance)
1195 if self.expert_mode:
1196 if address in self.wallet.frozen_addresses:
1197 item.setBackgroundColor(0, QColor('lightblue'))
1198 elif address in self.wallet.prioritized_addresses:
1199 item.setBackgroundColor(0, QColor('lightgreen'))
1202 def update_receive_tab(self):
1203 l = self.receive_list
1206 l.setColumnHidden(2, not self.expert_mode)
1207 l.setColumnHidden(3, not self.expert_mode)
1208 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1209 l.setColumnWidth(i, width)
1211 if self.current_account is None:
1212 account_items = self.wallet.accounts.items()
1213 elif self.current_account != -1:
1214 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1218 for k, account in account_items:
1219 name = account.get('name',str(k))
1220 c,u = self.wallet.get_account_balance(k)
1221 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1222 l.addTopLevelItem(account_item)
1223 account_item.setExpanded(True)
1225 for is_change in ([0,1] if self.expert_mode else [0]):
1226 if self.expert_mode:
1227 name = "Receiving" if not is_change else "Change"
1228 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1229 account_item.addChild(seq_item)
1230 if not is_change: seq_item.setExpanded(True)
1232 seq_item = account_item
1236 for address in account[is_change]:
1237 h = self.wallet.history.get(address,[])
1241 if gap > self.wallet.gap_limit:
1246 num_tx = '*' if h == ['*'] else "%d"%len(h)
1247 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1248 self.update_receive_item(item)
1250 item.setBackgroundColor(1, QColor('red'))
1251 seq_item.addChild(item)
1254 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1255 c,u = self.wallet.get_imported_balance()
1256 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1257 l.addTopLevelItem(account_item)
1258 account_item.setExpanded(True)
1259 for address in self.wallet.imported_keys.keys():
1260 item = QTreeWidgetItem( [ address, '', '', ''] )
1261 self.update_receive_item(item)
1262 account_item.addChild(item)
1265 # we use column 1 because column 0 may be hidden
1266 l.setCurrentItem(l.topLevelItem(0),1)
1269 def update_contacts_tab(self):
1270 l = self.contacts_list
1273 for address in self.wallet.addressbook:
1274 label = self.wallet.labels.get(address,'')
1275 n = self.wallet.get_num_tx(address)
1276 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1277 item.setFont(0, QFont(MONOSPACE_FONT))
1278 # 32 = label can be edited (bool)
1279 item.setData(0,32, True)
1281 item.setData(0,33, address)
1282 l.addTopLevelItem(item)
1284 self.run_hook('update_contacts_tab', l)
1285 l.setCurrentItem(l.topLevelItem(0))
1289 def create_console_tab(self):
1290 from qt_console import Console
1291 self.console = console = Console()
1292 self.console.history = self.config.get("console-history",[])
1293 self.console.history_index = len(self.console.history)
1295 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1296 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1298 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1300 def mkfunc(f, method):
1301 return lambda *args: apply( f, (method, args, self.password_dialog ))
1303 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1304 methods[m] = mkfunc(c._run, m)
1306 console.updateNamespace(methods)
1309 def change_account(self,s):
1310 if s == _("All accounts"):
1311 self.current_account = None
1313 accounts = self.wallet.get_accounts()
1314 for k, v in accounts.items():
1316 self.current_account = k
1317 self.update_history_tab()
1318 self.update_status()
1319 self.update_receive_tab()
1321 def create_status_bar(self):
1324 sb.setFixedHeight(35)
1325 qtVersion = qVersion()
1327 self.balance_label = QLabel("")
1328 sb.addWidget(self.balance_label)
1330 update_notification = UpdateLabel(self.config)
1331 if(update_notification.new_version):
1332 sb.addPermanentWidget(update_notification)
1334 accounts = self.wallet.get_accounts()
1335 if len(accounts) > 1:
1336 from_combo = QComboBox()
1337 from_combo.addItems([_("All accounts")] + accounts.values())
1338 from_combo.setCurrentIndex(0)
1339 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1340 sb.addPermanentWidget(from_combo)
1342 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1343 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1344 if self.wallet.seed:
1345 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1346 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1347 sb.addPermanentWidget( self.password_button )
1348 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1349 if self.wallet.seed:
1350 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1351 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1352 sb.addPermanentWidget( self.status_button )
1354 self.run_hook('create_status_bar', (sb,))
1356 self.setStatusBar(sb)
1360 self.config.set_key('gui', 'lite', True)
1363 self.lite.mini.show()
1365 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1366 self.lite.main(None)
1368 def new_contact_dialog(self):
1369 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1370 address = unicode(text)
1372 if is_valid(address):
1373 self.wallet.add_contact(address)
1374 self.update_contacts_tab()
1375 self.update_history_tab()
1376 self.update_completions()
1378 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1380 def show_master_public_key(self):
1381 dialog = QDialog(self)
1383 dialog.setWindowTitle(_("Master Public Key"))
1385 main_text = QTextEdit()
1386 main_text.setText(self.wallet.get_master_public_key())
1387 main_text.setReadOnly(True)
1388 main_text.setMaximumHeight(170)
1389 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1391 ok_button = QPushButton(_("OK"))
1392 ok_button.setDefault(True)
1393 ok_button.clicked.connect(dialog.accept)
1395 main_layout = QGridLayout()
1396 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1398 main_layout.addWidget(main_text, 1, 0)
1399 main_layout.addWidget(qrw, 1, 1 )
1401 vbox = QVBoxLayout()
1402 vbox.addLayout(main_layout)
1403 hbox = QHBoxLayout()
1405 hbox.addWidget(ok_button)
1406 vbox.addLayout(hbox)
1408 dialog.setLayout(vbox)
1413 def show_seed_dialog(self, password):
1414 if not self.wallet.seed:
1415 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1418 seed = self.wallet.decode_seed(password)
1420 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1422 self.show_seed(seed, self.wallet.imported_keys, self)
1426 def show_seed(self, seed, imported_keys, parent=None):
1427 dialog = QDialog(parent)
1429 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1431 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1433 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1435 seed_text = QTextEdit(brainwallet)
1436 seed_text.setReadOnly(True)
1437 seed_text.setMaximumHeight(130)
1439 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1440 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1441 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1442 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1444 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1445 label2 = QLabel(msg2)
1446 label2.setWordWrap(True)
1449 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1450 logo.setMaximumWidth(60)
1452 qrw = QRCodeWidget(seed)
1454 ok_button = QPushButton(_("OK"))
1455 ok_button.setDefault(True)
1456 ok_button.clicked.connect(dialog.accept)
1458 grid = QGridLayout()
1459 #main_layout.addWidget(logo, 0, 0)
1461 grid.addWidget(logo, 0, 0)
1462 grid.addWidget(label1, 0, 1)
1464 grid.addWidget(seed_text, 1, 0, 1, 2)
1466 grid.addWidget(qrw, 0, 2, 2, 1)
1468 vbox = QVBoxLayout()
1469 vbox.addLayout(grid)
1470 vbox.addWidget(label2)
1472 hbox = QHBoxLayout()
1474 hbox.addWidget(ok_button)
1475 vbox.addLayout(hbox)
1477 dialog.setLayout(vbox)
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 = 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') + ': ' + pk, _('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 change_password_dialog( wallet, parent=None ):
1664 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1672 new_pw = QLineEdit()
1673 new_pw.setEchoMode(2)
1674 conf_pw = QLineEdit()
1675 conf_pw.setEchoMode(2)
1677 vbox = QVBoxLayout()
1679 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1680 +_('To disable wallet encryption, enter an empty new password.')) \
1681 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1683 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1684 +_("Leave these fields empty if you want to disable encryption.")
1685 vbox.addWidget(QLabel(msg))
1687 grid = QGridLayout()
1690 if wallet.use_encryption:
1691 grid.addWidget(QLabel(_('Password')), 1, 0)
1692 grid.addWidget(pw, 1, 1)
1694 grid.addWidget(QLabel(_('New Password')), 2, 0)
1695 grid.addWidget(new_pw, 2, 1)
1697 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1698 grid.addWidget(conf_pw, 3, 1)
1699 vbox.addLayout(grid)
1701 vbox.addLayout(ok_cancel_buttons(d))
1704 if not d.exec_(): return
1706 password = unicode(pw.text()) if wallet.use_encryption else None
1707 new_password = unicode(new_pw.text())
1708 new_password2 = unicode(conf_pw.text())
1711 seed = wallet.decode_seed(password)
1713 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1716 if new_password != new_password2:
1717 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1718 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1721 wallet.update_password(seed, password, new_password)
1723 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1726 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1729 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1730 parent.password_button.setIcon( icon )
1734 def generate_transaction_information_widget(self, tx):
1735 tabs = QTabWidget(self)
1738 grid_ui = QGridLayout(tab1)
1739 grid_ui.setColumnStretch(0,1)
1740 tabs.addTab(tab1, _('Outputs') )
1742 tree_widget = MyTreeWidget(self)
1743 tree_widget.setColumnCount(2)
1744 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1745 tree_widget.setColumnWidth(0, 300)
1746 tree_widget.setColumnWidth(1, 50)
1748 for address, value in tx.outputs:
1749 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1750 tree_widget.addTopLevelItem(item)
1752 tree_widget.setMaximumHeight(100)
1754 grid_ui.addWidget(tree_widget)
1757 grid_ui = QGridLayout(tab2)
1758 grid_ui.setColumnStretch(0,1)
1759 tabs.addTab(tab2, _('Inputs') )
1761 tree_widget = MyTreeWidget(self)
1762 tree_widget.setColumnCount(2)
1763 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1765 for input_line in tx.inputs:
1766 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1767 tree_widget.addTopLevelItem(item)
1769 tree_widget.setMaximumHeight(100)
1771 grid_ui.addWidget(tree_widget)
1775 def tx_dict_from_text(self, txt):
1777 tx_dict = json.loads(str(txt))
1778 assert "hex" in tx_dict.keys()
1779 assert "complete" in tx_dict.keys()
1780 if not tx_dict["complete"]:
1781 assert "input_info" in tx_dict.keys()
1783 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1788 def read_tx_from_file(self):
1789 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1793 with open(fileName, "r") as f:
1794 file_content = f.read()
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))
1798 return self.tx_dict_from_text(file_content)
1802 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1804 self.wallet.signrawtransaction(tx, input_info, [], password)
1806 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1808 with open(fileName, "w+") as f:
1809 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1810 self.show_message(_("Transaction saved successfully"))
1813 except BaseException, e:
1814 self.show_message(str(e))
1817 def send_raw_transaction(self, raw_tx, dialog = ""):
1818 result, result_message = self.wallet.sendtx( raw_tx )
1820 self.show_message("Transaction successfully sent: %s" % (result_message))
1824 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1826 def do_process_from_text(self):
1827 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1830 tx_dict = self.tx_dict_from_text(text)
1832 self.create_process_transaction_window(tx_dict)
1834 def do_process_from_file(self):
1835 tx_dict = self.read_tx_from_file()
1837 self.create_process_transaction_window(tx_dict)
1839 def create_process_transaction_window(self, tx_dict):
1840 tx = Transaction(tx_dict["hex"])
1842 dialog = QDialog(self)
1843 dialog.setMinimumWidth(500)
1844 dialog.setWindowTitle(_('Process raw transaction'))
1850 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1851 l.addWidget(QLabel(_("Actions")), 4,0)
1853 if tx_dict["complete"] == False:
1854 l.addWidget(QLabel(_("Unsigned")), 3,1)
1855 if self.wallet.seed :
1856 b = QPushButton("Sign transaction")
1857 input_info = json.loads(tx_dict["input_info"])
1858 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1859 l.addWidget(b, 4, 1)
1861 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1863 l.addWidget(QLabel(_("Signed")), 3,1)
1864 b = QPushButton("Broadcast transaction")
1865 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1868 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1869 cancelButton = QPushButton(_("Cancel"))
1870 cancelButton.clicked.connect(lambda: dialog.done(0))
1871 l.addWidget(cancelButton, 4,2)
1877 def do_export_privkeys(self, password):
1878 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.")))
1881 select_export = _('Select file to export your private keys to')
1882 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1884 with open(fileName, "w+") as csvfile:
1885 transaction = csv.writer(csvfile)
1886 transaction.writerow(["address", "private_key"])
1889 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1890 transaction.writerow(["%34s"%addr,pk])
1892 self.show_message(_("Private keys exported."))
1894 except (IOError, os.error), reason:
1895 export_error_label = _("Electrum was unable to produce a private key-export.")
1896 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1898 except BaseException, e:
1899 self.show_message(str(e))
1903 def do_import_labels(self):
1904 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1905 if not labelsFile: return
1907 f = open(labelsFile, 'r')
1910 for key, value in json.loads(data).items():
1911 self.wallet.labels[key] = value
1913 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1914 except (IOError, os.error), reason:
1915 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1918 def do_export_labels(self):
1919 labels = self.wallet.labels
1921 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1923 with open(fileName, 'w+') as f:
1924 json.dump(labels, f)
1925 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1926 except (IOError, os.error), reason:
1927 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1930 def do_export_history(self):
1931 from gui_lite import csv_transaction
1932 csv_transaction(self.wallet)
1936 def do_import_privkey(self, password):
1937 if not self.wallet.imported_keys:
1938 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1939 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1940 + _('Are you sure you understand what you are doing?'), 3, 4)
1943 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1946 text = str(text).split()
1951 addr = self.wallet.import_key(key, password)
1952 except BaseException as e:
1958 addrlist.append(addr)
1960 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1962 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1963 self.update_receive_tab()
1964 self.update_history_tab()
1967 def settings_dialog(self):
1969 d.setWindowTitle(_('Electrum Settings'))
1971 vbox = QVBoxLayout()
1973 tabs = QTabWidget(self)
1974 self.settings_tab = tabs
1975 vbox.addWidget(tabs)
1978 grid_ui = QGridLayout(tab1)
1979 grid_ui.setColumnStretch(0,1)
1980 tabs.addTab(tab1, _('Display') )
1982 nz_label = QLabel(_('Display zeros'))
1983 grid_ui.addWidget(nz_label, 0, 0)
1984 nz_e = AmountEdit(None,True)
1985 nz_e.setText("%d"% self.wallet.num_zeros)
1986 grid_ui.addWidget(nz_e, 0, 1)
1987 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1988 grid_ui.addWidget(HelpButton(msg), 0, 2)
1989 if not self.config.is_modifiable('num_zeros'):
1990 for w in [nz_e, nz_label]: w.setEnabled(False)
1992 lang_label=QLabel(_('Language') + ':')
1993 grid_ui.addWidget(lang_label, 1, 0)
1994 lang_combo = QComboBox()
1995 from i18n import languages
1996 lang_combo.addItems(languages.values())
1998 index = languages.keys().index(self.config.get("language",''))
2001 lang_combo.setCurrentIndex(index)
2002 grid_ui.addWidget(lang_combo, 1, 1)
2003 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2004 if not self.config.is_modifiable('language'):
2005 for w in [lang_combo, lang_label]: w.setEnabled(False)
2007 currencies = self.exchanger.get_currencies()
2008 currencies.insert(0, "None")
2010 cur_label=QLabel(_('Currency') + ':')
2011 grid_ui.addWidget(cur_label , 2, 0)
2012 cur_combo = QComboBox()
2013 cur_combo.addItems(currencies)
2015 index = currencies.index(self.config.get('currency', "None"))
2018 cur_combo.setCurrentIndex(index)
2019 grid_ui.addWidget(cur_combo, 2, 1)
2020 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2022 expert_cb = QCheckBox(_('Expert mode'))
2023 expert_cb.setChecked(self.expert_mode)
2024 grid_ui.addWidget(expert_cb, 3, 0)
2025 hh = _('In expert mode, your client will:') + '\n' \
2026 + _(' - Show change addresses in the Receive tab') + '\n' \
2027 + _(' - Display the balance of each address') + '\n' \
2028 + _(' - Add freeze/prioritize actions to addresses.')
2029 grid_ui.addWidget(HelpButton(hh), 3, 2)
2030 grid_ui.setRowStretch(4,1)
2034 grid_wallet = QGridLayout(tab2)
2035 grid_wallet.setColumnStretch(0,1)
2036 tabs.addTab(tab2, _('Wallet') )
2038 fee_label = QLabel(_('Transaction fee'))
2039 grid_wallet.addWidget(fee_label, 0, 0)
2040 fee_e = AmountEdit(self.base_unit)
2041 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2042 grid_wallet.addWidget(fee_e, 0, 2)
2043 msg = _('Fee per kilobyte of transaction.') + ' ' \
2044 + _('Recommended value') + ': ' + self.format_amount(50000)
2045 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2046 if not self.config.is_modifiable('fee_per_kb'):
2047 for w in [fee_e, fee_label]: w.setEnabled(False)
2049 usechange_cb = QCheckBox(_('Use change addresses'))
2050 usechange_cb.setChecked(self.wallet.use_change)
2051 grid_wallet.addWidget(usechange_cb, 1, 0)
2052 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2053 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2055 gap_label = QLabel(_('Gap limit'))
2056 grid_wallet.addWidget(gap_label, 2, 0)
2057 gap_e = AmountEdit(None,True)
2058 gap_e.setText("%d"% self.wallet.gap_limit)
2059 grid_wallet.addWidget(gap_e, 2, 2)
2060 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2061 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2062 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2063 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2064 + _('Warning') + ': ' \
2065 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2066 + _('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'
2067 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2068 if not self.config.is_modifiable('gap_limit'):
2069 for w in [gap_e, gap_label]: w.setEnabled(False)
2071 units = ['BTC', 'mBTC']
2072 unit_label = QLabel(_('Base unit'))
2073 grid_wallet.addWidget(unit_label, 3, 0)
2074 unit_combo = QComboBox()
2075 unit_combo.addItems(units)
2076 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2077 grid_wallet.addWidget(unit_combo, 3, 2)
2078 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2079 + '\n1BTC=1000mBTC.\n' \
2080 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2081 grid_wallet.setRowStretch(4,1)
2085 tab5 = QScrollArea()
2086 tab5.setEnabled(True)
2087 tab5.setWidgetResizable(True)
2089 grid_plugins = QGridLayout()
2090 grid_plugins.setColumnStretch(0,1)
2093 w.setLayout(grid_plugins)
2096 w.setMinimumHeight(len(self.plugins)*35)
2098 tabs.addTab(tab5, _('Plugins') )
2099 def mk_toggle(cb, p):
2100 return lambda: cb.setChecked(p.toggle())
2101 for i, p in enumerate(self.plugins):
2103 name, description = p.get_info()
2104 cb = QCheckBox(name)
2105 cb.setDisabled(not p.is_available())
2106 cb.setChecked(p.is_enabled())
2107 cb.clicked.connect(mk_toggle(cb,p))
2108 grid_plugins.addWidget(cb, i, 0)
2109 if p.requires_settings():
2110 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2111 grid_plugins.addWidget(HelpButton(description), i, 2)
2113 print_msg("Error: cannot display plugin", p)
2114 traceback.print_exc(file=sys.stdout)
2115 grid_plugins.setRowStretch(i+1,1)
2117 self.run_hook('create_settings_tab', tabs)
2119 vbox.addLayout(ok_cancel_buttons(d))
2123 if not d.exec_(): return
2125 fee = unicode(fee_e.text())
2127 fee = self.read_amount(fee)
2129 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2132 self.wallet.set_fee(fee)
2134 nz = unicode(nz_e.text())
2139 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2142 if self.wallet.num_zeros != nz:
2143 self.wallet.num_zeros = nz
2144 self.config.set_key('num_zeros', nz, True)
2145 self.update_history_tab()
2146 self.update_receive_tab()
2148 usechange_result = usechange_cb.isChecked()
2149 if self.wallet.use_change != usechange_result:
2150 self.wallet.use_change = usechange_result
2151 self.config.set_key('use_change', self.wallet.use_change, True)
2153 unit_result = units[unit_combo.currentIndex()]
2154 if self.base_unit() != unit_result:
2155 self.decimal_point = 8 if unit_result == 'BTC' else 5
2156 self.config.set_key('decimal_point', self.decimal_point, True)
2157 self.update_history_tab()
2158 self.update_status()
2161 n = int(gap_e.text())
2163 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2166 if self.wallet.gap_limit != n:
2167 r = self.wallet.change_gap_limit(n)
2169 self.update_receive_tab()
2170 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2172 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2174 need_restart = False
2176 lang_request = languages.keys()[lang_combo.currentIndex()]
2177 if lang_request != self.config.get('language'):
2178 self.config.set_key("language", lang_request, True)
2181 cur_request = str(currencies[cur_combo.currentIndex()])
2182 if cur_request != self.config.get('currency', "None"):
2183 self.config.set_key('currency', cur_request, True)
2184 self.update_wallet()
2186 self.run_hook('close_settings_dialog')
2189 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2191 self.receive_tab_set_mode(expert_cb.isChecked())
2193 def run_network_dialog(self):
2194 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2196 def closeEvent(self, event):
2198 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2199 self.save_column_widths()
2200 self.config.set_key("console-history",self.console.history[-50:])
2209 def __init__(self, wallet, config, app=None):
2210 self.wallet = wallet
2211 self.config = config
2213 self.app = QApplication(sys.argv)
2216 def restore_or_create(self):
2217 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2218 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2219 if r==2: return None
2220 return 'restore' if r==1 else 'create'
2223 def verify_seed(self):
2224 r = self.seed_dialog(False)
2225 if r != self.wallet.seed:
2226 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2233 def seed_dialog(self, is_restore=True):
2237 vbox = QVBoxLayout()
2239 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2241 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2243 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2246 label.setWordWrap(True)
2247 vbox.addWidget(label)
2249 seed_e = QTextEdit()
2250 seed_e.setMaximumHeight(100)
2251 vbox.addWidget(seed_e)
2254 grid = QGridLayout()
2256 gap_e = AmountEdit(None, True)
2258 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2259 grid.addWidget(gap_e, 2, 1)
2260 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2261 vbox.addLayout(grid)
2263 vbox.addLayout(ok_cancel_buttons(d))
2266 if not d.exec_(): return
2269 seed = str(seed_e.toPlainText())
2273 seed = mnemonic.mn_decode( seed.split() )
2275 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2279 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2286 gap = int(unicode(gap_e.text()))
2288 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2293 def network_dialog(self):
2294 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2297 def show_seed(self):
2298 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2300 def password_dialog(self):
2301 if self.wallet.seed:
2302 ElectrumWindow.change_password_dialog(self.wallet)
2305 def restore_wallet(self):
2306 wallet = self.wallet
2307 # wait until we are connected, because the user might have selected another server
2308 if not wallet.interface.is_connected:
2309 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2310 waiting_dialog(waiting)
2312 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2313 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2315 wallet.set_up_to_date(False)
2316 wallet.interface.poke('synchronizer')
2317 waiting_dialog(waiting)
2318 if wallet.is_found():
2319 print_error( "Recovery successful" )
2321 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2328 w = ElectrumWindow(self.wallet, self.config)
2329 if url: w.set_url(url)