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)
248 self.create_status_bar()
250 self.need_update = threading.Event()
251 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
252 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
253 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
254 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
256 self.expert_mode = config.get('classic_expert_mode', False)
257 self.decimal_point = config.get('decimal_point', 8)
259 set_language(config.get('language'))
261 self.funds_error = False
262 self.completions = QStringListModel()
264 self.tabs = tabs = QTabWidget(self)
265 self.column_widths = self.config.get("column_widths", default_column_widths )
266 tabs.addTab(self.create_history_tab(), _('History') )
267 tabs.addTab(self.create_send_tab(), _('Send') )
268 tabs.addTab(self.create_receive_tab(), _('Receive') )
269 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
270 tabs.addTab(self.create_console_tab(), _('Console') )
271 tabs.setMinimumSize(600, 400)
272 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
273 self.setCentralWidget(tabs)
275 g = self.config.get("winpos-qt",[100, 100, 840, 400])
276 self.setGeometry(g[0], g[1], g[2], g[3])
277 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
278 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
279 self.setWindowTitle( title )
283 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
284 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
285 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
286 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
287 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
289 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
290 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
291 self.history_list.setFocus(True)
293 self.exchanger = exchange_rate.Exchanger(self)
294 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
296 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
297 if platform.system() == 'Windows':
298 n = 3 if self.wallet.seed else 2
299 tabs.setCurrentIndex (n)
300 tabs.setCurrentIndex (0)
303 if self.wallet.fee < 50000:
304 self.wallet.set_fee(50000)
305 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
307 # set initial message
308 self.console.showMessage(self.wallet.interface.banner)
310 # plugins that need to change the GUI do it here
311 self.run_hook('init_gui')
314 def select_wallet_file(self):
315 wallet_folder = self.wallet.config.path
316 re.sub("(\/\w*.dat)$", "", wallet_folder)
317 file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
321 self.load_wallet(file_name)
324 def init_menubar(self):
327 electrum_menu = menubar.addMenu(_("&File"))
328 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
329 open_wallet_action.triggered.connect(self.select_wallet_file)
331 preferences_name = _("Preferences")
332 if sys.platform == 'darwin':
333 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
335 preferences_menu = electrum_menu.addAction(preferences_name)
336 preferences_menu.triggered.connect(self.settings_dialog)
337 electrum_menu.addSeparator()
339 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
341 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
342 raw_transaction_file.triggered.connect(self.do_process_from_file)
344 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
345 raw_transaction_text.triggered.connect(self.do_process_from_text)
347 electrum_menu.addSeparator()
348 quit_item = electrum_menu.addAction(_("&Close"))
349 quit_item.triggered.connect(self.close)
351 wallet_menu = menubar.addMenu(_("&Wallet"))
352 wallet_backup = wallet_menu.addAction(_("&Create backup"))
353 wallet_backup.triggered.connect(backup_wallet)
355 show_menu = wallet_menu.addMenu(_("Show"))
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 import_menu = menubar.addMenu(_("&Import"))
369 in_labels = import_menu.addAction(_("&Labels"))
370 in_labels.triggered.connect(self.do_import_labels)
372 in_private_keys = import_menu.addAction(_("&Private keys"))
373 in_private_keys.triggered.connect(self.do_import_privkey)
375 export_menu = menubar.addMenu(_("&Export"))
376 ex_private_keys = export_menu.addAction(_("&Private keys"))
377 ex_private_keys.triggered.connect(self.do_export_privkeys)
379 ex_history = export_menu.addAction(_("&History"))
380 ex_history.triggered.connect(self.do_export_history)
382 ex_labels = export_menu.addAction(_("&Labels"))
383 ex_labels.triggered.connect(self.do_export_labels)
385 help_menu = menubar.addMenu(_("&Help"))
386 doc_open = help_menu.addAction(_("&Documentation"))
387 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
388 web_open = help_menu.addAction(_("&Official website"))
389 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
391 self.setMenuBar(menubar)
395 def load_wallet(self, filename):
398 config = electrum.SimpleConfig({'wallet_path': filename})
399 if not config.wallet_file_exists:
400 self.show_message("file not found "+ filename)
403 #self.wallet.verifier.stop()
404 interface = self.wallet.interface
405 verifier = self.wallet.verifier
406 self.wallet.synchronizer.stop()
409 self.wallet = electrum.Wallet(self.config)
410 self.wallet.interface = interface
411 self.wallet.verifier = verifier
413 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
421 def init_plugins(self):
422 import imp, pkgutil, __builtin__
423 if __builtin__.use_local_modules:
424 fp, pathname, description = imp.find_module('plugins')
425 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
426 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
427 imp.load_module('electrum_plugins', fp, pathname, description)
428 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
430 import electrum_plugins
431 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
432 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
437 self.plugins.append( p.Plugin(self) )
439 print_msg("Error:cannot initialize plugin",p)
440 traceback.print_exc(file=sys.stdout)
443 def run_hook(self, name, *args):
444 for p in self.plugins:
445 if not p.is_enabled():
454 print_error("Plugin error")
455 traceback.print_exc(file=sys.stdout)
460 def set_label(self, name, text = None):
462 old_text = self.wallet.labels.get(name)
465 self.wallet.labels[name] = text
466 self.wallet.config.set_key('labels', self.wallet.labels)
470 self.wallet.labels.pop(name)
472 self.run_hook('set_label', name, text, changed)
476 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
477 def getOpenFileName(self, title, filter = None):
478 directory = self.config.get('io_dir', os.path.expanduser('~'))
479 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
480 if fileName and directory != os.path.dirname(fileName):
481 self.config.set_key('io_dir', os.path.dirname(fileName), True)
484 def getSaveFileName(self, title, filename, filter = None):
485 directory = self.config.get('io_dir', os.path.expanduser('~'))
486 path = os.path.join( directory, filename )
487 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
488 if fileName and directory != os.path.dirname(fileName):
489 self.config.set_key('io_dir', os.path.dirname(fileName), True)
495 QMainWindow.close(self)
496 self.run_hook('close_main_window')
498 def connect_slots(self, sender):
499 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
500 self.previous_payto_e=''
502 def timer_actions(self):
503 if self.need_update.is_set():
505 self.need_update.clear()
506 self.run_hook('timer_actions')
508 def format_amount(self, x, is_diff=False, whitespaces=False):
509 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
511 def read_amount(self, x):
512 if x in['.', '']: return None
513 p = pow(10, self.decimal_point)
514 return int( p * Decimal(x) )
517 assert self.decimal_point in [5,8]
518 return "BTC" if self.decimal_point == 8 else "mBTC"
520 def update_status(self):
521 if self.wallet.interface and self.wallet.interface.is_connected:
522 if not self.wallet.up_to_date:
523 text = _("Synchronizing...")
524 icon = QIcon(":icons/status_waiting.png")
526 c, u = self.wallet.get_account_balance(self.current_account)
527 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
528 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
529 text += self.create_quote_text(Decimal(c+u)/100000000)
530 icon = QIcon(":icons/status_connected.png")
532 text = _("Not connected")
533 icon = QIcon(":icons/status_disconnected.png")
535 self.balance_label.setText(text)
536 self.status_button.setIcon( icon )
538 def update_wallet(self):
540 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
541 self.update_history_tab()
542 self.update_receive_tab()
543 self.update_contacts_tab()
544 self.update_completions()
547 def create_quote_text(self, btc_balance):
548 quote_currency = self.config.get("currency", "None")
549 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
550 if quote_balance is None:
553 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
556 def create_history_tab(self):
557 self.history_list = l = MyTreeWidget(self)
559 for i,width in enumerate(self.column_widths['history']):
560 l.setColumnWidth(i, width)
561 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
562 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
563 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
565 l.setContextMenuPolicy(Qt.CustomContextMenu)
566 l.customContextMenuRequested.connect(self.create_history_menu)
570 def create_history_menu(self, position):
571 self.history_list.selectedIndexes()
572 item = self.history_list.currentItem()
574 tx_hash = str(item.data(0, Qt.UserRole).toString())
575 if not tx_hash: return
577 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
578 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
579 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
580 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
583 def show_tx_details(self, tx):
584 dialog = QDialog(self)
586 dialog.setWindowTitle(_("Transaction Details"))
588 dialog.setLayout(vbox)
589 dialog.setMinimumSize(600,300)
592 if tx_hash in self.wallet.transactions.keys():
593 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
594 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
596 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
602 vbox.addWidget(QLabel("Transaction ID:"))
603 e = QLineEdit(tx_hash)
607 vbox.addWidget(QLabel("Date: %s"%time_str))
608 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
611 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
612 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
614 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
615 vbox.addWidget(QLabel("Transaction fee: unknown"))
617 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
619 vbox.addWidget( self.generate_transaction_information_widget(tx) )
621 ok_button = QPushButton(_("Close"))
622 ok_button.setDefault(True)
623 ok_button.clicked.connect(dialog.accept)
627 hbox.addWidget(ok_button)
631 def tx_label_clicked(self, item, column):
632 if column==2 and item.isSelected():
634 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
635 self.history_list.editItem( item, column )
636 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
639 def tx_label_changed(self, item, column):
643 tx_hash = str(item.data(0, Qt.UserRole).toString())
644 tx = self.wallet.transactions.get(tx_hash)
645 text = unicode( item.text(2) )
646 self.set_label(tx_hash, text)
648 item.setForeground(2, QBrush(QColor('black')))
650 text = self.wallet.get_default_label(tx_hash)
651 item.setText(2, text)
652 item.setForeground(2, QBrush(QColor('gray')))
656 def edit_label(self, is_recv):
657 l = self.receive_list if is_recv else self.contacts_list
658 item = l.currentItem()
659 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
660 l.editItem( item, 1 )
661 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
665 def address_label_clicked(self, item, column, l, column_addr, column_label):
666 if column == column_label and item.isSelected():
667 is_editable = item.data(0, 32).toBool()
670 addr = unicode( item.text(column_addr) )
671 label = unicode( item.text(column_label) )
672 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
673 l.editItem( item, column )
674 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
677 def address_label_changed(self, item, column, l, column_addr, column_label):
678 if column == column_label:
679 addr = unicode( item.text(column_addr) )
680 text = unicode( item.text(column_label) )
681 is_editable = item.data(0, 32).toBool()
685 changed = self.set_label(addr, text)
687 self.update_history_tab()
688 self.update_completions()
690 self.current_item_changed(item)
692 self.run_hook('item_changed', item, column)
695 def current_item_changed(self, a):
696 self.run_hook('current_item_changed', a)
700 def update_history_tab(self):
702 self.history_list.clear()
703 for item in self.wallet.get_tx_history(self.current_account):
704 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
707 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
712 time_str = 'unverified'
713 icon = QIcon(":icons/unconfirmed.png")
716 icon = QIcon(":icons/unconfirmed.png")
718 icon = QIcon(":icons/clock%d.png"%conf)
720 icon = QIcon(":icons/confirmed.png")
722 if value is not None:
723 v_str = self.format_amount(value, True, whitespaces=True)
727 balance_str = self.format_amount(balance, whitespaces=True)
730 label, is_default_label = self.wallet.get_label(tx_hash)
732 label = _('Pruned transaction outputs')
733 is_default_label = False
735 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
736 item.setFont(2, QFont(MONOSPACE_FONT))
737 item.setFont(3, QFont(MONOSPACE_FONT))
738 item.setFont(4, QFont(MONOSPACE_FONT))
740 item.setForeground(3, QBrush(QColor("#BC1E1E")))
742 item.setData(0, Qt.UserRole, tx_hash)
743 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
745 item.setForeground(2, QBrush(QColor('grey')))
747 item.setIcon(0, icon)
748 self.history_list.insertTopLevelItem(0,item)
751 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
754 def create_send_tab(self):
759 grid.setColumnMinimumWidth(3,300)
760 grid.setColumnStretch(5,1)
763 self.payto_e = QLineEdit()
764 grid.addWidget(QLabel(_('Pay to')), 1, 0)
765 grid.addWidget(self.payto_e, 1, 1, 1, 3)
767 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)
769 completer = QCompleter()
770 completer.setCaseSensitivity(False)
771 self.payto_e.setCompleter(completer)
772 completer.setModel(self.completions)
774 self.message_e = QLineEdit()
775 grid.addWidget(QLabel(_('Description')), 2, 0)
776 grid.addWidget(self.message_e, 2, 1, 1, 3)
777 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)
779 self.amount_e = AmountEdit(self.base_unit)
780 grid.addWidget(QLabel(_('Amount')), 3, 0)
781 grid.addWidget(self.amount_e, 3, 1, 1, 2)
782 grid.addWidget(HelpButton(
783 _('Amount to be sent.') + '\n\n' \
784 + _('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.') \
785 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
787 self.fee_e = AmountEdit(self.base_unit)
788 grid.addWidget(QLabel(_('Fee')), 4, 0)
789 grid.addWidget(self.fee_e, 4, 1, 1, 2)
790 grid.addWidget(HelpButton(
791 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
792 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
793 + _('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)
796 b = EnterButton(_("Send"), self.do_send)
798 b = EnterButton(_("Create unsigned transaction"), self.do_send)
799 grid.addWidget(b, 6, 1)
801 b = EnterButton(_("Clear"),self.do_clear)
802 grid.addWidget(b, 6, 2)
804 self.payto_sig = QLabel('')
805 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
807 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
808 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
817 def entry_changed( is_fee ):
818 self.funds_error = False
820 if self.amount_e.is_shortcut:
821 self.amount_e.is_shortcut = False
822 c, u = self.wallet.get_account_balance(self.current_account)
823 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
824 fee = self.wallet.estimated_fee(inputs)
826 self.amount_e.setText( self.format_amount(amount) )
827 self.fee_e.setText( self.format_amount( fee ) )
830 amount = self.read_amount(str(self.amount_e.text()))
831 fee = self.read_amount(str(self.fee_e.text()))
833 if not is_fee: fee = None
836 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
838 self.fee_e.setText( self.format_amount( fee ) )
841 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
845 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
846 self.funds_error = True
847 text = _( "Not enough funds" )
848 c, u = self.wallet.get_frozen_balance()
849 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
851 self.statusBar().showMessage(text)
852 self.amount_e.setPalette(palette)
853 self.fee_e.setPalette(palette)
855 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
856 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
858 self.run_hook('create_send_tab', grid)
862 def update_completions(self):
864 for addr,label in self.wallet.labels.items():
865 if addr in self.wallet.addressbook:
866 l.append( label + ' <' + addr + '>')
868 self.run_hook('update_completions', l)
869 self.completions.setStringList(l)
873 return lambda s, *args: s.do_protect(func, args)
877 def do_send(self, password):
879 label = unicode( self.message_e.text() )
880 r = unicode( self.payto_e.text() )
883 # label or alias, with address in brackets
884 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
885 to_address = m.group(2) if m else r
887 if not is_valid(to_address):
888 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
892 amount = self.read_amount(unicode( self.amount_e.text()))
894 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
897 fee = self.read_amount(unicode( self.fee_e.text()))
899 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
903 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
904 except BaseException, e:
905 self.show_message(str(e))
908 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
909 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
912 confirm_amount = self.config.get('confirm_amount', 100000000)
913 if amount >= confirm_amount:
914 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
917 self.run_hook('send_tx', tx)
920 self.set_label(tx.hash(), label)
923 h = self.wallet.send_tx(tx)
924 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
925 status, msg = self.wallet.receive_tx( h )
927 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
929 self.update_contacts_tab()
931 QMessageBox.warning(self, _('Error'), msg, _('OK'))
933 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
935 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
936 with open(fileName,'w') as f:
937 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
938 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
940 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
945 def set_url(self, url):
946 address, amount, label, message, signature, identity, url = util.parse_url(url)
947 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
949 if label and self.wallet.labels.get(address) != label:
950 if self.question('Give label "%s" to address %s ?'%(label,address)):
951 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
952 self.wallet.addressbook.append(address)
953 self.set_label(address, label)
955 self.run_hook('set_url', url, self.show_message, self.question)
957 self.tabs.setCurrentIndex(1)
958 label = self.wallet.labels.get(address)
959 m_addr = label + ' <'+ address +'>' if label else address
960 self.payto_e.setText(m_addr)
962 self.message_e.setText(message)
963 self.amount_e.setText(amount)
965 self.set_frozen(self.payto_e,True)
966 self.set_frozen(self.amount_e,True)
967 self.set_frozen(self.message_e,True)
968 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
970 self.payto_sig.setVisible(False)
973 self.payto_sig.setVisible(False)
974 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
976 self.set_frozen(e,False)
979 def set_frozen(self,entry,frozen):
981 entry.setReadOnly(True)
982 entry.setFrame(False)
984 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
985 entry.setPalette(palette)
987 entry.setReadOnly(False)
990 palette.setColor(entry.backgroundRole(), QColor('white'))
991 entry.setPalette(palette)
994 def toggle_freeze(self,addr):
996 if addr in self.wallet.frozen_addresses:
997 self.wallet.unfreeze(addr)
999 self.wallet.freeze(addr)
1000 self.update_receive_tab()
1002 def toggle_priority(self,addr):
1004 if addr in self.wallet.prioritized_addresses:
1005 self.wallet.unprioritize(addr)
1007 self.wallet.prioritize(addr)
1008 self.update_receive_tab()
1011 def create_list_tab(self, headers):
1012 "generic tab creation method"
1013 l = MyTreeWidget(self)
1014 l.setColumnCount( len(headers) )
1015 l.setHeaderLabels( headers )
1018 vbox = QVBoxLayout()
1025 vbox.addWidget(buttons)
1027 hbox = QHBoxLayout()
1030 buttons.setLayout(hbox)
1035 def create_receive_tab(self):
1036 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1037 l.setContextMenuPolicy(Qt.CustomContextMenu)
1038 l.customContextMenuRequested.connect(self.create_receive_menu)
1039 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1040 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1041 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1042 self.receive_list = l
1043 self.receive_buttons_hbox = hbox
1048 def receive_tab_set_mode(self, i):
1049 self.save_column_widths()
1050 self.expert_mode = (i == 1)
1051 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1052 self.update_receive_tab()
1055 def save_column_widths(self):
1056 if not self.expert_mode:
1057 widths = [ self.receive_list.columnWidth(0) ]
1060 for i in range(self.receive_list.columnCount() -1):
1061 widths.append(self.receive_list.columnWidth(i))
1062 self.column_widths["receive"][self.expert_mode] = widths
1064 self.column_widths["history"] = []
1065 for i in range(self.history_list.columnCount() - 1):
1066 self.column_widths["history"].append(self.history_list.columnWidth(i))
1068 self.column_widths["contacts"] = []
1069 for i in range(self.contacts_list.columnCount() - 1):
1070 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1072 self.config.set_key("column_widths", self.column_widths, True)
1075 def create_contacts_tab(self):
1076 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1077 l.setContextMenuPolicy(Qt.CustomContextMenu)
1078 l.customContextMenuRequested.connect(self.create_contact_menu)
1079 for i,width in enumerate(self.column_widths['contacts']):
1080 l.setColumnWidth(i, width)
1082 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1083 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1084 self.contacts_list = l
1085 self.contacts_buttons_hbox = hbox
1090 def delete_imported_key(self, addr):
1091 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1092 self.wallet.delete_imported_key(addr)
1093 self.update_receive_tab()
1094 self.update_history_tab()
1097 def create_receive_menu(self, position):
1098 # fixme: this function apparently has a side effect.
1099 # if it is not called the menu pops up several times
1100 #self.receive_list.selectedIndexes()
1102 item = self.receive_list.itemAt(position)
1104 addr = unicode(item.text(0))
1105 if not is_valid(addr):
1106 item.setExpanded(not item.isExpanded())
1109 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1110 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1111 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1112 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1113 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1114 if addr in self.wallet.imported_keys:
1115 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1117 if self.expert_mode:
1118 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1119 menu.addAction(t, lambda: self.toggle_freeze(addr))
1120 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1121 menu.addAction(t, lambda: self.toggle_priority(addr))
1123 self.run_hook('receive_menu', menu)
1124 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1127 def payto(self, addr):
1129 label = self.wallet.labels.get(addr)
1130 m_addr = label + ' <' + addr + '>' if label else addr
1131 self.tabs.setCurrentIndex(1)
1132 self.payto_e.setText(m_addr)
1133 self.amount_e.setFocus()
1136 def delete_contact(self, x):
1137 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1138 self.wallet.delete_contact(x)
1139 self.set_label(x, None)
1140 self.update_history_tab()
1141 self.update_contacts_tab()
1142 self.update_completions()
1145 def create_contact_menu(self, position):
1146 item = self.contacts_list.itemAt(position)
1148 addr = unicode(item.text(0))
1149 label = unicode(item.text(1))
1150 is_editable = item.data(0,32).toBool()
1151 payto_addr = item.data(0,33).toString()
1153 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1154 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1155 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1157 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1158 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1160 self.run_hook('create_contact_menu', menu, item)
1161 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1164 def update_receive_item(self, item):
1165 item.setFont(0, QFont(MONOSPACE_FONT))
1166 address = str(item.data(0,0).toString())
1167 label = self.wallet.labels.get(address,'')
1168 item.setData(1,0,label)
1169 item.setData(0,32, True) # is editable
1171 self.run_hook('update_receive_item', address, item)
1173 c, u = self.wallet.get_addr_balance(address)
1174 balance = self.format_amount(c + u)
1175 item.setData(2,0,balance)
1177 if self.expert_mode:
1178 if address in self.wallet.frozen_addresses:
1179 item.setBackgroundColor(0, QColor('lightblue'))
1180 elif address in self.wallet.prioritized_addresses:
1181 item.setBackgroundColor(0, QColor('lightgreen'))
1184 def update_receive_tab(self):
1185 l = self.receive_list
1188 l.setColumnHidden(2, not self.expert_mode)
1189 l.setColumnHidden(3, not self.expert_mode)
1190 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1191 l.setColumnWidth(i, width)
1193 if self.current_account is None:
1194 account_items = self.wallet.accounts.items()
1195 elif self.current_account != -1:
1196 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1200 for k, account in account_items:
1201 name = account.get('name',str(k))
1202 c,u = self.wallet.get_account_balance(k)
1203 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1204 l.addTopLevelItem(account_item)
1205 account_item.setExpanded(True)
1207 for is_change in ([0,1] if self.expert_mode else [0]):
1208 if self.expert_mode:
1209 name = "Receiving" if not is_change else "Change"
1210 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1211 account_item.addChild(seq_item)
1212 if not is_change: seq_item.setExpanded(True)
1214 seq_item = account_item
1218 for address in account[is_change]:
1219 h = self.wallet.history.get(address,[])
1223 if gap > self.wallet.gap_limit:
1228 num_tx = '*' if h == ['*'] else "%d"%len(h)
1229 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1230 self.update_receive_item(item)
1232 item.setBackgroundColor(1, QColor('red'))
1233 seq_item.addChild(item)
1236 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1237 c,u = self.wallet.get_imported_balance()
1238 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1239 l.addTopLevelItem(account_item)
1240 account_item.setExpanded(True)
1241 for address in self.wallet.imported_keys.keys():
1242 item = QTreeWidgetItem( [ address, '', '', ''] )
1243 self.update_receive_item(item)
1244 account_item.addChild(item)
1247 # we use column 1 because column 0 may be hidden
1248 l.setCurrentItem(l.topLevelItem(0),1)
1251 def update_contacts_tab(self):
1252 l = self.contacts_list
1255 for address in self.wallet.addressbook:
1256 label = self.wallet.labels.get(address,'')
1257 n = self.wallet.get_num_tx(address)
1258 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1259 item.setFont(0, QFont(MONOSPACE_FONT))
1260 # 32 = label can be edited (bool)
1261 item.setData(0,32, True)
1263 item.setData(0,33, address)
1264 l.addTopLevelItem(item)
1266 self.run_hook('update_contacts_tab', l)
1267 l.setCurrentItem(l.topLevelItem(0))
1271 def create_console_tab(self):
1272 from qt_console import Console
1273 self.console = console = Console()
1274 self.console.history = self.config.get("console-history",[])
1275 self.console.history_index = len(self.console.history)
1277 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1278 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1280 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1282 def mkfunc(f, method):
1283 return lambda *args: apply( f, (method, args, self.password_dialog ))
1285 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1286 methods[m] = mkfunc(c._run, m)
1288 console.updateNamespace(methods)
1291 def change_account(self,s):
1292 if s == _("All accounts"):
1293 self.current_account = None
1295 accounts = self.wallet.get_accounts()
1296 for k, v in accounts.items():
1298 self.current_account = k
1299 self.update_history_tab()
1300 self.update_status()
1301 self.update_receive_tab()
1303 def create_status_bar(self):
1306 sb.setFixedHeight(35)
1307 qtVersion = qVersion()
1309 self.balance_label = QLabel("")
1310 sb.addWidget(self.balance_label)
1312 update_notification = UpdateLabel(self.config)
1313 if(update_notification.new_version):
1314 sb.addPermanentWidget(update_notification)
1316 accounts = self.wallet.get_accounts()
1317 if len(accounts) > 1:
1318 from_combo = QComboBox()
1319 from_combo.addItems([_("All accounts")] + accounts.values())
1320 from_combo.setCurrentIndex(0)
1321 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1322 sb.addPermanentWidget(from_combo)
1324 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1325 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1326 if self.wallet.seed:
1327 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1328 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1329 sb.addPermanentWidget( self.password_button )
1330 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1331 if self.wallet.seed:
1332 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1333 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1334 sb.addPermanentWidget( self.status_button )
1336 self.run_hook('create_status_bar', (sb,))
1338 self.setStatusBar(sb)
1342 self.config.set_key('gui', 'lite', True)
1345 self.lite.mini.show()
1347 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1348 self.lite.main(None)
1350 def new_contact_dialog(self):
1351 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1352 address = unicode(text)
1354 if is_valid(address):
1355 self.wallet.add_contact(address)
1356 self.update_contacts_tab()
1357 self.update_history_tab()
1358 self.update_completions()
1360 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1362 def show_master_public_key(self):
1363 dialog = QDialog(self)
1365 dialog.setWindowTitle(_("Master Public Key"))
1367 main_text = QTextEdit()
1368 main_text.setText(self.wallet.get_master_public_key())
1369 main_text.setReadOnly(True)
1370 main_text.setMaximumHeight(170)
1371 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1373 ok_button = QPushButton(_("OK"))
1374 ok_button.setDefault(True)
1375 ok_button.clicked.connect(dialog.accept)
1377 main_layout = QGridLayout()
1378 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1380 main_layout.addWidget(main_text, 1, 0)
1381 main_layout.addWidget(qrw, 1, 1 )
1383 vbox = QVBoxLayout()
1384 vbox.addLayout(main_layout)
1385 hbox = QHBoxLayout()
1387 hbox.addWidget(ok_button)
1388 vbox.addLayout(hbox)
1390 dialog.setLayout(vbox)
1395 def show_seed_dialog(self, password):
1396 if not self.wallet.seed:
1397 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1400 seed = self.wallet.decode_seed(password)
1402 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1404 self.show_seed(seed, self.wallet.imported_keys, self)
1408 def show_seed(self, seed, imported_keys, parent=None):
1409 dialog = QDialog(parent)
1411 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1413 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1415 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1417 seed_text = QTextEdit(brainwallet)
1418 seed_text.setReadOnly(True)
1419 seed_text.setMaximumHeight(130)
1421 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1422 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1423 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1424 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1426 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1427 label2 = QLabel(msg2)
1428 label2.setWordWrap(True)
1431 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1432 logo.setMaximumWidth(60)
1434 qrw = QRCodeWidget(seed)
1436 ok_button = QPushButton(_("OK"))
1437 ok_button.setDefault(True)
1438 ok_button.clicked.connect(dialog.accept)
1440 grid = QGridLayout()
1441 #main_layout.addWidget(logo, 0, 0)
1443 grid.addWidget(logo, 0, 0)
1444 grid.addWidget(label1, 0, 1)
1446 grid.addWidget(seed_text, 1, 0, 1, 2)
1448 grid.addWidget(qrw, 0, 2, 2, 1)
1450 vbox = QVBoxLayout()
1451 vbox.addLayout(grid)
1452 vbox.addWidget(label2)
1454 hbox = QHBoxLayout()
1456 hbox.addWidget(ok_button)
1457 vbox.addLayout(hbox)
1459 dialog.setLayout(vbox)
1462 def show_qrcode(self, data, title = "QR code"):
1466 d.setWindowTitle(title)
1467 d.setMinimumSize(270, 300)
1468 vbox = QVBoxLayout()
1469 qrw = QRCodeWidget(data)
1470 vbox.addWidget(qrw, 1)
1471 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1472 hbox = QHBoxLayout()
1476 filename = "qrcode.bmp"
1477 bmp.save_qrcode(qrw.qr, filename)
1478 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1480 b = QPushButton(_("Save"))
1482 b.clicked.connect(print_qr)
1484 b = QPushButton(_("Close"))
1486 b.clicked.connect(d.accept)
1489 vbox.addLayout(hbox)
1494 def do_protect(self, func, args):
1495 if self.wallet.use_encryption:
1496 password = self.password_dialog()
1502 if args != (False,):
1503 args = (self,) + args + (password,)
1505 args = (self,password)
1510 def show_private_key(self, address, password):
1511 if not address: return
1513 pk = self.wallet.get_private_key(address, password)
1514 except BaseException, e:
1515 self.show_message(str(e))
1517 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1521 def do_sign(self, address, message, signature, password):
1523 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1524 signature.setText(sig)
1525 except BaseException, e:
1526 self.show_message(str(e))
1528 def sign_message(self, address):
1529 if not address: return
1532 d.setWindowTitle(_('Sign Message'))
1533 d.setMinimumSize(410, 290)
1535 tab_widget = QTabWidget()
1537 layout = QGridLayout(tab)
1539 sign_address = QLineEdit()
1541 sign_address.setText(address)
1542 layout.addWidget(QLabel(_('Address')), 1, 0)
1543 layout.addWidget(sign_address, 1, 1)
1545 sign_message = QTextEdit()
1546 layout.addWidget(QLabel(_('Message')), 2, 0)
1547 layout.addWidget(sign_message, 2, 1)
1548 layout.setRowStretch(2,3)
1550 sign_signature = QTextEdit()
1551 layout.addWidget(QLabel(_('Signature')), 3, 0)
1552 layout.addWidget(sign_signature, 3, 1)
1553 layout.setRowStretch(3,1)
1556 hbox = QHBoxLayout()
1557 b = QPushButton(_("Sign"))
1559 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1560 b = QPushButton(_("Close"))
1561 b.clicked.connect(d.accept)
1563 layout.addLayout(hbox, 4, 1)
1564 tab_widget.addTab(tab, _("Sign"))
1568 layout = QGridLayout(tab)
1570 verify_address = QLineEdit()
1571 layout.addWidget(QLabel(_('Address')), 1, 0)
1572 layout.addWidget(verify_address, 1, 1)
1574 verify_message = QTextEdit()
1575 layout.addWidget(QLabel(_('Message')), 2, 0)
1576 layout.addWidget(verify_message, 2, 1)
1577 layout.setRowStretch(2,3)
1579 verify_signature = QTextEdit()
1580 layout.addWidget(QLabel(_('Signature')), 3, 0)
1581 layout.addWidget(verify_signature, 3, 1)
1582 layout.setRowStretch(3,1)
1585 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1586 self.show_message(_("Signature verified"))
1588 self.show_message(_("Error: wrong signature"))
1590 hbox = QHBoxLayout()
1591 b = QPushButton(_("Verify"))
1592 b.clicked.connect(do_verify)
1594 b = QPushButton(_("Close"))
1595 b.clicked.connect(d.accept)
1597 layout.addLayout(hbox, 4, 1)
1598 tab_widget.addTab(tab, _("Verify"))
1600 vbox = QVBoxLayout()
1601 vbox.addWidget(tab_widget)
1608 def question(self, msg):
1609 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1611 def show_message(self, msg):
1612 QMessageBox.information(self, _('Message'), msg, _('OK'))
1614 def password_dialog(self ):
1621 vbox = QVBoxLayout()
1622 msg = _('Please enter your password')
1623 vbox.addWidget(QLabel(msg))
1625 grid = QGridLayout()
1627 grid.addWidget(QLabel(_('Password')), 1, 0)
1628 grid.addWidget(pw, 1, 1)
1629 vbox.addLayout(grid)
1631 vbox.addLayout(ok_cancel_buttons(d))
1634 self.run_hook('password_dialog', pw, grid, 1)
1635 if not d.exec_(): return
1636 return unicode(pw.text())
1643 def change_password_dialog( wallet, parent=None ):
1646 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1654 new_pw = QLineEdit()
1655 new_pw.setEchoMode(2)
1656 conf_pw = QLineEdit()
1657 conf_pw.setEchoMode(2)
1659 vbox = QVBoxLayout()
1661 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1662 +_('To disable wallet encryption, enter an empty new password.')) \
1663 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1665 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1666 +_("Leave these fields empty if you want to disable encryption.")
1667 vbox.addWidget(QLabel(msg))
1669 grid = QGridLayout()
1672 if wallet.use_encryption:
1673 grid.addWidget(QLabel(_('Password')), 1, 0)
1674 grid.addWidget(pw, 1, 1)
1676 grid.addWidget(QLabel(_('New Password')), 2, 0)
1677 grid.addWidget(new_pw, 2, 1)
1679 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1680 grid.addWidget(conf_pw, 3, 1)
1681 vbox.addLayout(grid)
1683 vbox.addLayout(ok_cancel_buttons(d))
1686 if not d.exec_(): return
1688 password = unicode(pw.text()) if wallet.use_encryption else None
1689 new_password = unicode(new_pw.text())
1690 new_password2 = unicode(conf_pw.text())
1693 seed = wallet.decode_seed(password)
1695 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1698 if new_password != new_password2:
1699 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1700 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1703 wallet.update_password(seed, password, new_password)
1705 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1708 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1711 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1712 parent.password_button.setIcon( icon )
1716 def generate_transaction_information_widget(self, tx):
1717 tabs = QTabWidget(self)
1720 grid_ui = QGridLayout(tab1)
1721 grid_ui.setColumnStretch(0,1)
1722 tabs.addTab(tab1, _('Outputs') )
1724 tree_widget = MyTreeWidget(self)
1725 tree_widget.setColumnCount(2)
1726 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1727 tree_widget.setColumnWidth(0, 300)
1728 tree_widget.setColumnWidth(1, 50)
1730 for address, value in tx.outputs:
1731 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1732 tree_widget.addTopLevelItem(item)
1734 tree_widget.setMaximumHeight(100)
1736 grid_ui.addWidget(tree_widget)
1739 grid_ui = QGridLayout(tab2)
1740 grid_ui.setColumnStretch(0,1)
1741 tabs.addTab(tab2, _('Inputs') )
1743 tree_widget = MyTreeWidget(self)
1744 tree_widget.setColumnCount(2)
1745 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1747 for input_line in tx.inputs:
1748 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1749 tree_widget.addTopLevelItem(item)
1751 tree_widget.setMaximumHeight(100)
1753 grid_ui.addWidget(tree_widget)
1757 def tx_dict_from_text(self, txt):
1759 tx_dict = json.loads(str(txt))
1760 assert "hex" in tx_dict.keys()
1761 assert "complete" in tx_dict.keys()
1762 if not tx_dict["complete"]:
1763 assert "input_info" in tx_dict.keys()
1765 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1770 def read_tx_from_file(self):
1771 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1775 with open(fileName, "r") as f:
1776 file_content = f.read()
1777 except (ValueError, IOError, os.error), reason:
1778 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1780 return self.tx_dict_from_text(file_content)
1784 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1786 self.wallet.signrawtransaction(tx, input_info, [], password)
1788 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1790 with open(fileName, "w+") as f:
1791 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1792 self.show_message(_("Transaction saved successfully"))
1795 except BaseException, e:
1796 self.show_message(str(e))
1799 def send_raw_transaction(self, raw_tx, dialog = ""):
1800 result, result_message = self.wallet.sendtx( raw_tx )
1802 self.show_message("Transaction successfully sent: %s" % (result_message))
1806 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1808 def do_process_from_text(self):
1809 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1812 tx_dict = self.tx_dict_from_text(text)
1814 self.create_process_transaction_window(tx_dict)
1816 def do_process_from_file(self):
1817 tx_dict = self.read_tx_from_file()
1819 self.create_process_transaction_window(tx_dict)
1821 def create_process_transaction_window(self, tx_dict):
1822 tx = Transaction(tx_dict["hex"])
1824 dialog = QDialog(self)
1825 dialog.setMinimumWidth(500)
1826 dialog.setWindowTitle(_('Process raw transaction'))
1832 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1833 l.addWidget(QLabel(_("Actions")), 4,0)
1835 if tx_dict["complete"] == False:
1836 l.addWidget(QLabel(_("Unsigned")), 3,1)
1837 if self.wallet.seed :
1838 b = QPushButton("Sign transaction")
1839 input_info = json.loads(tx_dict["input_info"])
1840 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1841 l.addWidget(b, 4, 1)
1843 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1845 l.addWidget(QLabel(_("Signed")), 3,1)
1846 b = QPushButton("Broadcast transaction")
1847 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1850 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1851 cancelButton = QPushButton(_("Cancel"))
1852 cancelButton.clicked.connect(lambda: dialog.done(0))
1853 l.addWidget(cancelButton, 4,2)
1859 def do_export_privkeys(self, password):
1860 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.")))
1863 select_export = _('Select file to export your private keys to')
1864 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1866 with open(fileName, "w+") as csvfile:
1867 transaction = csv.writer(csvfile)
1868 transaction.writerow(["address", "private_key"])
1871 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1872 transaction.writerow(["%34s"%addr,pk])
1874 self.show_message(_("Private keys exported."))
1876 except (IOError, os.error), reason:
1877 export_error_label = _("Electrum was unable to produce a private key-export.")
1878 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1880 except BaseException, e:
1881 self.show_message(str(e))
1885 def do_import_labels(self):
1886 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1887 if not labelsFile: return
1889 f = open(labelsFile, 'r')
1892 for key, value in json.loads(data).items():
1893 self.wallet.labels[key] = value
1895 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1896 except (IOError, os.error), reason:
1897 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1900 def do_export_labels(self):
1901 labels = self.wallet.labels
1903 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1905 with open(fileName, 'w+') as f:
1906 json.dump(labels, f)
1907 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1908 except (IOError, os.error), reason:
1909 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1912 def do_export_history(self):
1913 from gui_lite import csv_transaction
1914 csv_transaction(self.wallet)
1918 def do_import_privkey(self, password):
1919 if not self.wallet.imported_keys:
1920 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1921 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1922 + _('Are you sure you understand what you are doing?'), 3, 4)
1925 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1928 text = str(text).split()
1933 addr = self.wallet.import_key(key, password)
1934 except BaseException as e:
1940 addrlist.append(addr)
1942 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1944 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1945 self.update_receive_tab()
1946 self.update_history_tab()
1949 def settings_dialog(self):
1951 d.setWindowTitle(_('Electrum Settings'))
1953 vbox = QVBoxLayout()
1955 tabs = QTabWidget(self)
1956 self.settings_tab = tabs
1957 vbox.addWidget(tabs)
1960 grid_ui = QGridLayout(tab1)
1961 grid_ui.setColumnStretch(0,1)
1962 tabs.addTab(tab1, _('Display') )
1964 nz_label = QLabel(_('Display zeros'))
1965 grid_ui.addWidget(nz_label, 0, 0)
1966 nz_e = AmountEdit(None,True)
1967 nz_e.setText("%d"% self.wallet.num_zeros)
1968 grid_ui.addWidget(nz_e, 0, 1)
1969 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1970 grid_ui.addWidget(HelpButton(msg), 0, 2)
1971 if not self.config.is_modifiable('num_zeros'):
1972 for w in [nz_e, nz_label]: w.setEnabled(False)
1974 lang_label=QLabel(_('Language') + ':')
1975 grid_ui.addWidget(lang_label, 1, 0)
1976 lang_combo = QComboBox()
1977 from i18n import languages
1978 lang_combo.addItems(languages.values())
1980 index = languages.keys().index(self.config.get("language",''))
1983 lang_combo.setCurrentIndex(index)
1984 grid_ui.addWidget(lang_combo, 1, 1)
1985 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1986 if not self.config.is_modifiable('language'):
1987 for w in [lang_combo, lang_label]: w.setEnabled(False)
1989 currencies = self.exchanger.get_currencies()
1990 currencies.insert(0, "None")
1992 cur_label=QLabel(_('Currency') + ':')
1993 grid_ui.addWidget(cur_label , 2, 0)
1994 cur_combo = QComboBox()
1995 cur_combo.addItems(currencies)
1997 index = currencies.index(self.config.get('currency', "None"))
2000 cur_combo.setCurrentIndex(index)
2001 grid_ui.addWidget(cur_combo, 2, 1)
2002 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2004 expert_cb = QCheckBox(_('Expert mode'))
2005 expert_cb.setChecked(self.expert_mode)
2006 grid_ui.addWidget(expert_cb, 3, 0)
2007 hh = _('In expert mode, your client will:') + '\n' \
2008 + _(' - Show change addresses in the Receive tab') + '\n' \
2009 + _(' - Display the balance of each address') + '\n' \
2010 + _(' - Add freeze/prioritize actions to addresses.')
2011 grid_ui.addWidget(HelpButton(hh), 3, 2)
2012 grid_ui.setRowStretch(4,1)
2016 grid_wallet = QGridLayout(tab2)
2017 grid_wallet.setColumnStretch(0,1)
2018 tabs.addTab(tab2, _('Wallet') )
2020 fee_label = QLabel(_('Transaction fee'))
2021 grid_wallet.addWidget(fee_label, 0, 0)
2022 fee_e = AmountEdit(self.base_unit)
2023 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2024 grid_wallet.addWidget(fee_e, 0, 2)
2025 msg = _('Fee per kilobyte of transaction.') + ' ' \
2026 + _('Recommended value') + ': ' + self.format_amount(50000)
2027 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2028 if not self.config.is_modifiable('fee_per_kb'):
2029 for w in [fee_e, fee_label]: w.setEnabled(False)
2031 usechange_cb = QCheckBox(_('Use change addresses'))
2032 usechange_cb.setChecked(self.wallet.use_change)
2033 grid_wallet.addWidget(usechange_cb, 1, 0)
2034 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2035 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2037 gap_label = QLabel(_('Gap limit'))
2038 grid_wallet.addWidget(gap_label, 2, 0)
2039 gap_e = AmountEdit(None,True)
2040 gap_e.setText("%d"% self.wallet.gap_limit)
2041 grid_wallet.addWidget(gap_e, 2, 2)
2042 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2043 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2044 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2045 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2046 + _('Warning') + ': ' \
2047 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2048 + _('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'
2049 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2050 if not self.config.is_modifiable('gap_limit'):
2051 for w in [gap_e, gap_label]: w.setEnabled(False)
2053 units = ['BTC', 'mBTC']
2054 unit_label = QLabel(_('Base unit'))
2055 grid_wallet.addWidget(unit_label, 3, 0)
2056 unit_combo = QComboBox()
2057 unit_combo.addItems(units)
2058 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2059 grid_wallet.addWidget(unit_combo, 3, 2)
2060 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2061 + '\n1BTC=1000mBTC.\n' \
2062 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2063 grid_wallet.setRowStretch(4,1)
2067 tab5 = QScrollArea()
2068 tab5.setEnabled(True)
2069 tab5.setWidgetResizable(True)
2071 grid_plugins = QGridLayout()
2072 grid_plugins.setColumnStretch(0,1)
2075 w.setLayout(grid_plugins)
2078 w.setMinimumHeight(len(self.plugins)*35)
2080 tabs.addTab(tab5, _('Plugins') )
2081 def mk_toggle(cb, p):
2082 return lambda: cb.setChecked(p.toggle())
2083 for i, p in enumerate(self.plugins):
2085 name, description = p.get_info()
2086 cb = QCheckBox(name)
2087 cb.setDisabled(not p.is_available())
2088 cb.setChecked(p.is_enabled())
2089 cb.clicked.connect(mk_toggle(cb,p))
2090 grid_plugins.addWidget(cb, i, 0)
2091 if p.requires_settings():
2092 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2093 grid_plugins.addWidget(HelpButton(description), i, 2)
2095 print_msg("Error: cannot display plugin", p)
2096 traceback.print_exc(file=sys.stdout)
2097 grid_plugins.setRowStretch(i+1,1)
2099 self.run_hook('create_settings_tab', tabs)
2101 vbox.addLayout(ok_cancel_buttons(d))
2105 if not d.exec_(): return
2107 fee = unicode(fee_e.text())
2109 fee = self.read_amount(fee)
2111 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2114 self.wallet.set_fee(fee)
2116 nz = unicode(nz_e.text())
2121 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2124 if self.wallet.num_zeros != nz:
2125 self.wallet.num_zeros = nz
2126 self.config.set_key('num_zeros', nz, True)
2127 self.update_history_tab()
2128 self.update_receive_tab()
2130 usechange_result = usechange_cb.isChecked()
2131 if self.wallet.use_change != usechange_result:
2132 self.wallet.use_change = usechange_result
2133 self.config.set_key('use_change', self.wallet.use_change, True)
2135 unit_result = units[unit_combo.currentIndex()]
2136 if self.base_unit() != unit_result:
2137 self.decimal_point = 8 if unit_result == 'BTC' else 5
2138 self.config.set_key('decimal_point', self.decimal_point, True)
2139 self.update_history_tab()
2140 self.update_status()
2143 n = int(gap_e.text())
2145 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2148 if self.wallet.gap_limit != n:
2149 r = self.wallet.change_gap_limit(n)
2151 self.update_receive_tab()
2152 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2154 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2156 need_restart = False
2158 lang_request = languages.keys()[lang_combo.currentIndex()]
2159 if lang_request != self.config.get('language'):
2160 self.config.set_key("language", lang_request, True)
2163 cur_request = str(currencies[cur_combo.currentIndex()])
2164 if cur_request != self.config.get('currency', "None"):
2165 self.config.set_key('currency', cur_request, True)
2166 self.update_wallet()
2168 self.run_hook('close_settings_dialog')
2171 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2173 self.receive_tab_set_mode(expert_cb.isChecked())
2175 def run_network_dialog(self):
2176 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2178 def closeEvent(self, event):
2180 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2181 self.save_column_widths()
2182 self.config.set_key("console-history",self.console.history[-50:])
2191 def __init__(self, wallet, config, app=None):
2192 self.wallet = wallet
2193 self.config = config
2195 self.app = QApplication(sys.argv)
2198 def restore_or_create(self):
2199 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2200 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2201 if r==2: return None
2202 return 'restore' if r==1 else 'create'
2205 def verify_seed(self):
2206 r = self.seed_dialog(False)
2207 if r != self.wallet.seed:
2208 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2215 def seed_dialog(self, is_restore=True):
2219 vbox = QVBoxLayout()
2221 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2223 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2225 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2228 label.setWordWrap(True)
2229 vbox.addWidget(label)
2231 seed_e = QTextEdit()
2232 seed_e.setMaximumHeight(100)
2233 vbox.addWidget(seed_e)
2236 grid = QGridLayout()
2238 gap_e = AmountEdit(None, True)
2240 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2241 grid.addWidget(gap_e, 2, 1)
2242 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2243 vbox.addLayout(grid)
2245 vbox.addLayout(ok_cancel_buttons(d))
2248 if not d.exec_(): return
2251 seed = str(seed_e.toPlainText())
2255 seed = mnemonic.mn_decode( seed.split() )
2257 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2261 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2268 gap = int(unicode(gap_e.text()))
2270 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2275 def network_dialog(self):
2276 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2279 def show_seed(self):
2280 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2282 def password_dialog(self):
2283 if self.wallet.seed:
2284 ElectrumWindow.change_password_dialog(self.wallet)
2287 def restore_wallet(self):
2288 wallet = self.wallet
2289 # wait until we are connected, because the user might have selected another server
2290 if not wallet.interface.is_connected:
2291 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2292 waiting_dialog(waiting)
2294 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2295 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2297 wallet.set_up_to_date(False)
2298 wallet.interface.poke('synchronizer')
2299 waiting_dialog(waiting)
2300 if wallet.is_found():
2301 print_error( "Recovery successful" )
2303 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2310 w = ElectrumWindow(self.wallet, self.config)
2311 if url: w.set_url(url)