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
469 self.wallet.labels.pop(name)
471 self.run_hook('set_label', name, text, changed)
475 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
476 def getOpenFileName(self, title, filter = None):
477 directory = self.config.get('io_dir', os.path.expanduser('~'))
478 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
479 if fileName and directory != os.path.dirname(fileName):
480 self.config.set_key('io_dir', os.path.dirname(fileName), True)
483 def getSaveFileName(self, title, filename, filter = None):
484 directory = self.config.get('io_dir', os.path.expanduser('~'))
485 path = os.path.join( directory, filename )
486 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
487 if fileName and directory != os.path.dirname(fileName):
488 self.config.set_key('io_dir', os.path.dirname(fileName), True)
494 QMainWindow.close(self)
495 self.run_hook('close_main_window')
497 def connect_slots(self, sender):
498 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
499 self.previous_payto_e=''
501 def timer_actions(self):
502 if self.need_update.is_set():
504 self.need_update.clear()
505 self.run_hook('timer_actions')
507 def format_amount(self, x, is_diff=False):
508 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
510 def read_amount(self, x):
511 if x in['.', '']: return None
512 p = pow(10, self.decimal_point)
513 return int( p * Decimal(x) )
516 assert self.decimal_point in [5,8]
517 return "BTC" if self.decimal_point == 8 else "mBTC"
519 def update_status(self):
520 if self.wallet.interface and self.wallet.interface.is_connected:
521 if not self.wallet.up_to_date:
522 text = _("Synchronizing...")
523 icon = QIcon(":icons/status_waiting.png")
525 c, u = self.wallet.get_account_balance(self.current_account)
526 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
527 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
528 text += self.create_quote_text(Decimal(c+u)/100000000)
529 icon = QIcon(":icons/status_connected.png")
531 text = _("Not connected")
532 icon = QIcon(":icons/status_disconnected.png")
534 self.balance_label.setText(text)
535 self.status_button.setIcon( icon )
537 def update_wallet(self):
539 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
540 self.update_history_tab()
541 self.update_receive_tab()
542 self.update_contacts_tab()
543 self.update_completions()
546 def create_quote_text(self, btc_balance):
547 quote_currency = self.config.get("currency", "None")
548 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
549 if quote_balance is None:
552 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
555 def create_history_tab(self):
556 self.history_list = l = MyTreeWidget(self)
558 for i,width in enumerate(self.column_widths['history']):
559 l.setColumnWidth(i, width)
560 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
561 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
562 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
564 l.setContextMenuPolicy(Qt.CustomContextMenu)
565 l.customContextMenuRequested.connect(self.create_history_menu)
569 def create_history_menu(self, position):
570 self.history_list.selectedIndexes()
571 item = self.history_list.currentItem()
573 tx_hash = str(item.data(0, Qt.UserRole).toString())
574 if not tx_hash: return
576 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
577 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
578 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
579 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
582 def show_tx_details(self, tx):
583 dialog = QDialog(self)
585 dialog.setWindowTitle(_("Transaction Details"))
587 dialog.setLayout(vbox)
588 dialog.setMinimumSize(600,300)
591 if tx_hash in self.wallet.transactions.keys():
592 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
593 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
595 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
601 vbox.addWidget(QLabel("Transaction ID:"))
602 e = QLineEdit(tx_hash)
606 vbox.addWidget(QLabel("Date: %s"%time_str))
607 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
610 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
611 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
613 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
614 vbox.addWidget(QLabel("Transaction fee: unknown"))
616 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
618 vbox.addWidget( self.generate_transaction_information_widget(tx) )
620 ok_button = QPushButton(_("Close"))
621 ok_button.setDefault(True)
622 ok_button.clicked.connect(dialog.accept)
626 hbox.addWidget(ok_button)
630 def tx_label_clicked(self, item, column):
631 if column==2 and item.isSelected():
633 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
634 self.history_list.editItem( item, column )
635 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
638 def tx_label_changed(self, item, column):
642 tx_hash = str(item.data(0, Qt.UserRole).toString())
643 tx = self.wallet.transactions.get(tx_hash)
644 text = unicode( item.text(2) )
645 self.set_label(tx_hash, text)
647 item.setForeground(2, QBrush(QColor('black')))
649 text = self.wallet.get_default_label(tx_hash)
650 item.setText(2, text)
651 item.setForeground(2, QBrush(QColor('gray')))
655 def edit_label(self, is_recv):
656 l = self.receive_list if is_recv else self.contacts_list
657 item = l.currentItem()
658 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
659 l.editItem( item, 1 )
660 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
664 def address_label_clicked(self, item, column, l, column_addr, column_label):
665 if column == column_label and item.isSelected():
666 is_editable = item.data(0, 32).toBool()
669 addr = unicode( item.text(column_addr) )
670 label = unicode( item.text(column_label) )
671 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
672 l.editItem( item, column )
673 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
676 def address_label_changed(self, item, column, l, column_addr, column_label):
677 if column == column_label:
678 addr = unicode( item.text(column_addr) )
679 text = unicode( item.text(column_label) )
680 is_editable = item.data(0, 32).toBool()
684 changed = self.set_label(addr, text)
686 self.update_history_tab()
687 self.update_completions()
689 self.current_item_changed(item)
691 self.run_hook('item_changed', item, column)
694 def current_item_changed(self, a):
695 self.run_hook('current_item_changed', a)
699 def update_history_tab(self):
701 self.history_list.clear()
702 for item in self.wallet.get_tx_history(self.current_account):
703 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
706 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
711 time_str = 'unverified'
712 icon = QIcon(":icons/unconfirmed.png")
715 icon = QIcon(":icons/unconfirmed.png")
717 icon = QIcon(":icons/clock%d.png"%conf)
719 icon = QIcon(":icons/confirmed.png")
721 if value is not None:
722 v_str = self.format_amount(value, True)
726 balance_str = self.format_amount(balance)
729 label, is_default_label = self.wallet.get_label(tx_hash)
731 label = _('Pruned transaction outputs')
732 is_default_label = False
734 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
735 item.setFont(2, QFont(MONOSPACE_FONT))
736 item.setFont(3, QFont(MONOSPACE_FONT))
737 item.setFont(4, QFont(MONOSPACE_FONT))
739 item.setForeground(3, QBrush(QColor("#BC1E1E")))
741 item.setData(0, Qt.UserRole, tx_hash)
742 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
744 item.setForeground(2, QBrush(QColor('grey')))
746 item.setIcon(0, icon)
747 self.history_list.insertTopLevelItem(0,item)
750 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
753 def create_send_tab(self):
758 grid.setColumnMinimumWidth(3,300)
759 grid.setColumnStretch(5,1)
762 self.payto_e = QLineEdit()
763 grid.addWidget(QLabel(_('Pay to')), 1, 0)
764 grid.addWidget(self.payto_e, 1, 1, 1, 3)
766 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)
768 completer = QCompleter()
769 completer.setCaseSensitivity(False)
770 self.payto_e.setCompleter(completer)
771 completer.setModel(self.completions)
773 self.message_e = QLineEdit()
774 grid.addWidget(QLabel(_('Description')), 2, 0)
775 grid.addWidget(self.message_e, 2, 1, 1, 3)
776 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)
778 self.amount_e = AmountEdit(self.base_unit)
779 grid.addWidget(QLabel(_('Amount')), 3, 0)
780 grid.addWidget(self.amount_e, 3, 1, 1, 2)
781 grid.addWidget(HelpButton(
782 _('Amount to be sent.') + '\n\n' \
783 + _('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.') \
784 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
786 self.fee_e = AmountEdit(self.base_unit)
787 grid.addWidget(QLabel(_('Fee')), 4, 0)
788 grid.addWidget(self.fee_e, 4, 1, 1, 2)
789 grid.addWidget(HelpButton(
790 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
791 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
792 + _('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)
795 b = EnterButton(_("Send"), self.do_send)
797 b = EnterButton(_("Create unsigned transaction"), self.do_send)
798 grid.addWidget(b, 6, 1)
800 b = EnterButton(_("Clear"),self.do_clear)
801 grid.addWidget(b, 6, 2)
803 self.payto_sig = QLabel('')
804 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
806 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
807 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
816 def entry_changed( is_fee ):
817 self.funds_error = False
819 if self.amount_e.is_shortcut:
820 self.amount_e.is_shortcut = False
821 c, u = self.wallet.get_account_balance(self.current_account)
822 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
823 fee = self.wallet.estimated_fee(inputs)
825 self.amount_e.setText( self.format_amount(amount) )
826 self.fee_e.setText( self.format_amount( fee ) )
829 amount = self.read_amount(str(self.amount_e.text()))
830 fee = self.read_amount(str(self.fee_e.text()))
832 if not is_fee: fee = None
835 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
837 self.fee_e.setText( self.format_amount( fee ) )
840 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
844 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
845 self.funds_error = True
846 text = _( "Not enough funds" )
847 c, u = self.wallet.get_frozen_balance()
848 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
850 self.statusBar().showMessage(text)
851 self.amount_e.setPalette(palette)
852 self.fee_e.setPalette(palette)
854 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
855 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
857 self.run_hook('create_send_tab', grid)
861 def update_completions(self):
863 for addr,label in self.wallet.labels.items():
864 if addr in self.wallet.addressbook:
865 l.append( label + ' <' + addr + '>')
867 self.run_hook('update_completions', l)
868 self.completions.setStringList(l)
872 return lambda s, *args: s.do_protect(func, args)
876 def do_send(self, password):
878 label = unicode( self.message_e.text() )
879 r = unicode( self.payto_e.text() )
882 # label or alias, with address in brackets
883 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
884 to_address = m.group(2) if m else r
886 if not is_valid(to_address):
887 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
891 amount = self.read_amount(unicode( self.amount_e.text()))
893 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
896 fee = self.read_amount(unicode( self.fee_e.text()))
898 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
902 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
903 except BaseException, e:
904 self.show_message(str(e))
907 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
908 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
911 self.run_hook('send_tx', tx)
914 self.set_label(tx.hash(), label)
917 h = self.wallet.send_tx(tx)
918 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
919 status, msg = self.wallet.receive_tx( h )
921 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
923 self.update_contacts_tab()
925 QMessageBox.warning(self, _('Error'), msg, _('OK'))
927 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
929 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
930 with open(fileName,'w') as f:
931 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
932 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
934 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
939 def set_url(self, url):
940 address, amount, label, message, signature, identity, url = util.parse_url(url)
941 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
943 if label and self.wallet.labels.get(address) != label:
944 if self.question('Give label "%s" to address %s ?'%(label,address)):
945 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
946 self.wallet.addressbook.append(address)
947 self.set_label(address, label)
949 self.run_hook('set_url', url, self.show_message, self.question)
951 self.tabs.setCurrentIndex(1)
952 label = self.wallet.labels.get(address)
953 m_addr = label + ' <'+ address +'>' if label else address
954 self.payto_e.setText(m_addr)
956 self.message_e.setText(message)
957 self.amount_e.setText(amount)
959 self.set_frozen(self.payto_e,True)
960 self.set_frozen(self.amount_e,True)
961 self.set_frozen(self.message_e,True)
962 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
964 self.payto_sig.setVisible(False)
967 self.payto_sig.setVisible(False)
968 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
970 self.set_frozen(e,False)
973 def set_frozen(self,entry,frozen):
975 entry.setReadOnly(True)
976 entry.setFrame(False)
978 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
979 entry.setPalette(palette)
981 entry.setReadOnly(False)
984 palette.setColor(entry.backgroundRole(), QColor('white'))
985 entry.setPalette(palette)
988 def toggle_freeze(self,addr):
990 if addr in self.wallet.frozen_addresses:
991 self.wallet.unfreeze(addr)
993 self.wallet.freeze(addr)
994 self.update_receive_tab()
996 def toggle_priority(self,addr):
998 if addr in self.wallet.prioritized_addresses:
999 self.wallet.unprioritize(addr)
1001 self.wallet.prioritize(addr)
1002 self.update_receive_tab()
1005 def create_list_tab(self, headers):
1006 "generic tab creation method"
1007 l = MyTreeWidget(self)
1008 l.setColumnCount( len(headers) )
1009 l.setHeaderLabels( headers )
1012 vbox = QVBoxLayout()
1019 vbox.addWidget(buttons)
1021 hbox = QHBoxLayout()
1024 buttons.setLayout(hbox)
1029 def create_receive_tab(self):
1030 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1031 l.setContextMenuPolicy(Qt.CustomContextMenu)
1032 l.customContextMenuRequested.connect(self.create_receive_menu)
1033 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1034 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1035 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1036 self.receive_list = l
1037 self.receive_buttons_hbox = hbox
1042 def receive_tab_set_mode(self, i):
1043 self.save_column_widths()
1044 self.expert_mode = (i == 1)
1045 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1046 self.update_receive_tab()
1049 def save_column_widths(self):
1050 if not self.expert_mode:
1051 widths = [ self.receive_list.columnWidth(0) ]
1054 for i in range(self.receive_list.columnCount() -1):
1055 widths.append(self.receive_list.columnWidth(i))
1056 self.column_widths["receive"][self.expert_mode] = widths
1058 self.column_widths["history"] = []
1059 for i in range(self.history_list.columnCount() - 1):
1060 self.column_widths["history"].append(self.history_list.columnWidth(i))
1062 self.column_widths["contacts"] = []
1063 for i in range(self.contacts_list.columnCount() - 1):
1064 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1066 self.config.set_key("column_widths", self.column_widths, True)
1069 def create_contacts_tab(self):
1070 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1071 l.setContextMenuPolicy(Qt.CustomContextMenu)
1072 l.customContextMenuRequested.connect(self.create_contact_menu)
1073 for i,width in enumerate(self.column_widths['contacts']):
1074 l.setColumnWidth(i, width)
1076 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1077 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1078 self.contacts_list = l
1079 self.contacts_buttons_hbox = hbox
1084 def delete_imported_key(self, addr):
1085 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1086 self.wallet.delete_imported_key(addr)
1087 self.update_receive_tab()
1088 self.update_history_tab()
1091 def create_receive_menu(self, position):
1092 # fixme: this function apparently has a side effect.
1093 # if it is not called the menu pops up several times
1094 #self.receive_list.selectedIndexes()
1096 item = self.receive_list.itemAt(position)
1098 addr = unicode(item.text(0))
1099 if not is_valid(addr):
1100 item.setExpanded(not item.isExpanded())
1103 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1104 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1105 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1106 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1107 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1108 if addr in self.wallet.imported_keys:
1109 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1111 if self.expert_mode:
1112 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1113 menu.addAction(t, lambda: self.toggle_freeze(addr))
1114 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1115 menu.addAction(t, lambda: self.toggle_priority(addr))
1117 self.run_hook('receive_menu', menu)
1118 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1121 def payto(self, addr):
1123 label = self.wallet.labels.get(addr)
1124 m_addr = label + ' <' + addr + '>' if label else addr
1125 self.tabs.setCurrentIndex(1)
1126 self.payto_e.setText(m_addr)
1127 self.amount_e.setFocus()
1130 def delete_contact(self, x):
1131 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1132 self.wallet.delete_contact(x)
1133 self.set_label(x, None)
1134 self.update_history_tab()
1135 self.update_contacts_tab()
1136 self.update_completions()
1139 def create_contact_menu(self, position):
1140 item = self.contacts_list.itemAt(position)
1142 addr = unicode(item.text(0))
1143 label = unicode(item.text(1))
1144 is_editable = item.data(0,32).toBool()
1145 payto_addr = item.data(0,33).toString()
1147 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1148 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1149 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1151 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1152 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1154 self.run_hook('create_contact_menu', menu, item)
1155 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1158 def update_receive_item(self, item):
1159 item.setFont(0, QFont(MONOSPACE_FONT))
1160 address = str(item.data(0,0).toString())
1161 label = self.wallet.labels.get(address,'')
1162 item.setData(1,0,label)
1163 item.setData(0,32, True) # is editable
1165 self.run_hook('update_receive_item', address, item)
1167 c, u = self.wallet.get_addr_balance(address)
1168 balance = self.format_amount(c + u)
1169 item.setData(2,0,balance)
1171 if self.expert_mode:
1172 if address in self.wallet.frozen_addresses:
1173 item.setBackgroundColor(0, QColor('lightblue'))
1174 elif address in self.wallet.prioritized_addresses:
1175 item.setBackgroundColor(0, QColor('lightgreen'))
1178 def update_receive_tab(self):
1179 l = self.receive_list
1182 l.setColumnHidden(2, not self.expert_mode)
1183 l.setColumnHidden(3, not self.expert_mode)
1184 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1185 l.setColumnWidth(i, width)
1187 if self.current_account is None:
1188 account_items = self.wallet.accounts.items()
1189 elif self.current_account != -1:
1190 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1194 for k, account in account_items:
1195 name = account.get('name',str(k))
1196 c,u = self.wallet.get_account_balance(k)
1197 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1198 l.addTopLevelItem(account_item)
1199 account_item.setExpanded(True)
1201 for is_change in ([0,1] if self.expert_mode else [0]):
1202 if self.expert_mode:
1203 name = "Receiving" if not is_change else "Change"
1204 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1205 account_item.addChild(seq_item)
1206 if not is_change: seq_item.setExpanded(True)
1208 seq_item = account_item
1212 for address in account[is_change]:
1213 h = self.wallet.history.get(address,[])
1217 if gap > self.wallet.gap_limit:
1222 num_tx = '*' if h == ['*'] else "%d"%len(h)
1223 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1224 self.update_receive_item(item)
1226 item.setBackgroundColor(1, QColor('red'))
1227 seq_item.addChild(item)
1230 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1231 c,u = self.wallet.get_imported_balance()
1232 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1233 l.addTopLevelItem(account_item)
1234 account_item.setExpanded(True)
1235 for address in self.wallet.imported_keys.keys():
1236 item = QTreeWidgetItem( [ address, '', '', ''] )
1237 self.update_receive_item(item)
1238 account_item.addChild(item)
1241 # we use column 1 because column 0 may be hidden
1242 l.setCurrentItem(l.topLevelItem(0),1)
1245 def update_contacts_tab(self):
1247 l = self.contacts_list
1250 for address in self.wallet.addressbook:
1251 label = self.wallet.labels.get(address,'')
1252 n = self.wallet.get_num_tx(address)
1253 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1254 item.setFont(0, QFont(MONOSPACE_FONT))
1255 # 32 = label can be edited (bool)
1256 item.setData(0,32, True)
1258 item.setData(0,33, address)
1259 l.addTopLevelItem(item)
1261 self.run_hook('update_contacts_tab', l)
1262 l.setCurrentItem(l.topLevelItem(0))
1266 def create_console_tab(self):
1267 from qt_console import Console
1268 self.console = console = Console()
1269 self.console.history = self.config.get("console-history",[])
1270 self.console.history_index = len(self.console.history)
1272 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1273 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1275 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1277 def mkfunc(f, method):
1278 return lambda *args: apply( f, (method, args, self.password_dialog ))
1280 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1281 methods[m] = mkfunc(c._run, m)
1283 console.updateNamespace(methods)
1286 def change_account(self,s):
1287 if s == _("All accounts"):
1288 self.current_account = None
1290 accounts = self.wallet.get_accounts()
1291 for k, v in accounts.items():
1293 self.current_account = k
1294 self.update_history_tab()
1295 self.update_status()
1296 self.update_receive_tab()
1298 def create_status_bar(self):
1301 sb.setFixedHeight(35)
1302 qtVersion = qVersion()
1304 self.balance_label = QLabel("")
1305 sb.addWidget(self.balance_label)
1307 update_notification = UpdateLabel(self.config)
1308 if(update_notification.new_version):
1309 sb.addPermanentWidget(update_notification)
1311 accounts = self.wallet.get_accounts()
1312 if len(accounts) > 1:
1313 from_combo = QComboBox()
1314 from_combo.addItems([_("All accounts")] + accounts.values())
1315 from_combo.setCurrentIndex(0)
1316 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1317 sb.addPermanentWidget(from_combo)
1319 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1320 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1321 if self.wallet.seed:
1322 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1323 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1324 sb.addPermanentWidget( self.password_button )
1325 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1326 if self.wallet.seed:
1327 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1328 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1329 sb.addPermanentWidget( self.status_button )
1331 self.run_hook('create_status_bar', (sb,))
1333 self.setStatusBar(sb)
1337 self.config.set_key('gui', 'lite', True)
1340 self.lite.mini.show()
1342 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1343 self.lite.main(None)
1345 def new_contact_dialog(self):
1346 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1347 address = unicode(text)
1349 if is_valid(address):
1350 self.wallet.add_contact(address)
1351 self.update_contacts_tab()
1352 self.update_history_tab()
1353 self.update_completions()
1355 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1357 def show_master_public_key(self):
1358 dialog = QDialog(self)
1360 dialog.setWindowTitle(_("Master Public Key"))
1362 main_text = QTextEdit()
1363 main_text.setText(self.wallet.get_master_public_key())
1364 main_text.setReadOnly(True)
1365 main_text.setMaximumHeight(170)
1366 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1368 ok_button = QPushButton(_("OK"))
1369 ok_button.setDefault(True)
1370 ok_button.clicked.connect(dialog.accept)
1372 main_layout = QGridLayout()
1373 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1375 main_layout.addWidget(main_text, 1, 0)
1376 main_layout.addWidget(qrw, 1, 1 )
1378 vbox = QVBoxLayout()
1379 vbox.addLayout(main_layout)
1380 hbox = QHBoxLayout()
1382 hbox.addWidget(ok_button)
1383 vbox.addLayout(hbox)
1385 dialog.setLayout(vbox)
1390 def show_seed_dialog(self, password):
1391 if not self.wallet.seed:
1392 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1395 seed = self.wallet.decode_seed(password)
1397 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1399 self.show_seed(seed, self.wallet.imported_keys, self)
1403 def show_seed(self, seed, imported_keys, parent=None):
1404 dialog = QDialog(parent)
1406 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1408 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1410 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1412 seed_text = QTextEdit(brainwallet)
1413 seed_text.setReadOnly(True)
1414 seed_text.setMaximumHeight(130)
1416 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1417 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1418 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1419 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1421 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1422 label2 = QLabel(msg2)
1423 label2.setWordWrap(True)
1426 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1427 logo.setMaximumWidth(60)
1429 qrw = QRCodeWidget(seed)
1431 ok_button = QPushButton(_("OK"))
1432 ok_button.setDefault(True)
1433 ok_button.clicked.connect(dialog.accept)
1435 grid = QGridLayout()
1436 #main_layout.addWidget(logo, 0, 0)
1438 grid.addWidget(logo, 0, 0)
1439 grid.addWidget(label1, 0, 1)
1441 grid.addWidget(seed_text, 1, 0, 1, 2)
1443 grid.addWidget(qrw, 0, 2, 2, 1)
1445 vbox = QVBoxLayout()
1446 vbox.addLayout(grid)
1447 vbox.addWidget(label2)
1449 hbox = QHBoxLayout()
1451 hbox.addWidget(ok_button)
1452 vbox.addLayout(hbox)
1454 dialog.setLayout(vbox)
1457 def show_qrcode(self, data, title = "QR code"):
1461 d.setWindowTitle(title)
1462 d.setMinimumSize(270, 300)
1463 vbox = QVBoxLayout()
1464 qrw = QRCodeWidget(data)
1465 vbox.addWidget(qrw, 1)
1466 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1467 hbox = QHBoxLayout()
1471 filename = "qrcode.bmp"
1472 bmp.save_qrcode(qrw.qr, filename)
1473 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1475 b = QPushButton(_("Save"))
1477 b.clicked.connect(print_qr)
1479 b = QPushButton(_("Close"))
1481 b.clicked.connect(d.accept)
1484 vbox.addLayout(hbox)
1489 def do_protect(self, func, args):
1490 if self.wallet.use_encryption:
1491 password = self.password_dialog()
1497 if args != (False,):
1498 args = (self,) + args + (password,)
1500 args = (self,password)
1505 def show_private_key(self, address, password):
1506 if not address: return
1508 pk = self.wallet.get_private_key(address, password)
1509 except BaseException, e:
1510 self.show_message(str(e))
1512 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1516 def do_sign(self, address, message, signature, password):
1518 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1519 signature.setText(sig)
1520 except BaseException, e:
1521 self.show_message(str(e))
1523 def sign_message(self, address):
1524 if not address: return
1527 d.setWindowTitle(_('Sign Message'))
1528 d.setMinimumSize(410, 290)
1530 tab_widget = QTabWidget()
1532 layout = QGridLayout(tab)
1534 sign_address = QLineEdit()
1536 sign_address.setText(address)
1537 layout.addWidget(QLabel(_('Address')), 1, 0)
1538 layout.addWidget(sign_address, 1, 1)
1540 sign_message = QTextEdit()
1541 layout.addWidget(QLabel(_('Message')), 2, 0)
1542 layout.addWidget(sign_message, 2, 1)
1543 layout.setRowStretch(2,3)
1545 sign_signature = QTextEdit()
1546 layout.addWidget(QLabel(_('Signature')), 3, 0)
1547 layout.addWidget(sign_signature, 3, 1)
1548 layout.setRowStretch(3,1)
1551 hbox = QHBoxLayout()
1552 b = QPushButton(_("Sign"))
1554 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1555 b = QPushButton(_("Close"))
1556 b.clicked.connect(d.accept)
1558 layout.addLayout(hbox, 4, 1)
1559 tab_widget.addTab(tab, _("Sign"))
1563 layout = QGridLayout(tab)
1565 verify_address = QLineEdit()
1566 layout.addWidget(QLabel(_('Address')), 1, 0)
1567 layout.addWidget(verify_address, 1, 1)
1569 verify_message = QTextEdit()
1570 layout.addWidget(QLabel(_('Message')), 2, 0)
1571 layout.addWidget(verify_message, 2, 1)
1572 layout.setRowStretch(2,3)
1574 verify_signature = QTextEdit()
1575 layout.addWidget(QLabel(_('Signature')), 3, 0)
1576 layout.addWidget(verify_signature, 3, 1)
1577 layout.setRowStretch(3,1)
1580 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1581 self.show_message(_("Signature verified"))
1583 self.show_message(_("Error: wrong signature"))
1585 hbox = QHBoxLayout()
1586 b = QPushButton(_("Verify"))
1587 b.clicked.connect(do_verify)
1589 b = QPushButton(_("Close"))
1590 b.clicked.connect(d.accept)
1592 layout.addLayout(hbox, 4, 1)
1593 tab_widget.addTab(tab, _("Verify"))
1595 vbox = QVBoxLayout()
1596 vbox.addWidget(tab_widget)
1603 def question(self, msg):
1604 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1606 def show_message(self, msg):
1607 QMessageBox.information(self, _('Message'), msg, _('OK'))
1609 def password_dialog(self ):
1616 vbox = QVBoxLayout()
1617 msg = _('Please enter your password')
1618 vbox.addWidget(QLabel(msg))
1620 grid = QGridLayout()
1622 grid.addWidget(QLabel(_('Password')), 1, 0)
1623 grid.addWidget(pw, 1, 1)
1624 vbox.addLayout(grid)
1626 vbox.addLayout(ok_cancel_buttons(d))
1629 self.run_hook('password_dialog', pw, grid, 1)
1630 if not d.exec_(): return
1631 return unicode(pw.text())
1638 def change_password_dialog( wallet, parent=None ):
1641 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1649 new_pw = QLineEdit()
1650 new_pw.setEchoMode(2)
1651 conf_pw = QLineEdit()
1652 conf_pw.setEchoMode(2)
1654 vbox = QVBoxLayout()
1656 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1657 +_('To disable wallet encryption, enter an empty new password.')) \
1658 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1660 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1661 +_("Leave these fields empty if you want to disable encryption.")
1662 vbox.addWidget(QLabel(msg))
1664 grid = QGridLayout()
1667 if wallet.use_encryption:
1668 grid.addWidget(QLabel(_('Password')), 1, 0)
1669 grid.addWidget(pw, 1, 1)
1671 grid.addWidget(QLabel(_('New Password')), 2, 0)
1672 grid.addWidget(new_pw, 2, 1)
1674 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1675 grid.addWidget(conf_pw, 3, 1)
1676 vbox.addLayout(grid)
1678 vbox.addLayout(ok_cancel_buttons(d))
1681 if not d.exec_(): return
1683 password = unicode(pw.text()) if wallet.use_encryption else None
1684 new_password = unicode(new_pw.text())
1685 new_password2 = unicode(conf_pw.text())
1688 seed = wallet.decode_seed(password)
1690 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1693 if new_password != new_password2:
1694 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1695 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1698 wallet.update_password(seed, password, new_password)
1700 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1703 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1706 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1707 parent.password_button.setIcon( icon )
1711 def generate_transaction_information_widget(self, tx):
1712 tabs = QTabWidget(self)
1715 grid_ui = QGridLayout(tab1)
1716 grid_ui.setColumnStretch(0,1)
1717 tabs.addTab(tab1, _('Outputs') )
1719 tree_widget = MyTreeWidget(self)
1720 tree_widget.setColumnCount(2)
1721 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1722 tree_widget.setColumnWidth(0, 300)
1723 tree_widget.setColumnWidth(1, 50)
1725 for address, value in tx.outputs:
1726 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1727 tree_widget.addTopLevelItem(item)
1729 tree_widget.setMaximumHeight(100)
1731 grid_ui.addWidget(tree_widget)
1734 grid_ui = QGridLayout(tab2)
1735 grid_ui.setColumnStretch(0,1)
1736 tabs.addTab(tab2, _('Inputs') )
1738 tree_widget = MyTreeWidget(self)
1739 tree_widget.setColumnCount(2)
1740 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1742 for input_line in tx.inputs:
1743 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1744 tree_widget.addTopLevelItem(item)
1746 tree_widget.setMaximumHeight(100)
1748 grid_ui.addWidget(tree_widget)
1752 def tx_dict_from_text(self, txt):
1754 tx_dict = json.loads(str(txt))
1755 assert "hex" in tx_dict.keys()
1756 assert "complete" in tx_dict.keys()
1757 if not tx_dict["complete"]:
1758 assert "input_info" in tx_dict.keys()
1760 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1765 def read_tx_from_file(self):
1766 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1770 with open(fileName, "r") as f:
1771 file_content = f.read()
1772 except (ValueError, IOError, os.error), reason:
1773 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1775 return self.tx_dict_from_text(file_content)
1779 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1781 self.wallet.signrawtransaction(tx, input_info, [], password)
1783 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1785 with open(fileName, "w+") as f:
1786 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1787 self.show_message(_("Transaction saved successfully"))
1790 except BaseException, e:
1791 self.show_message(str(e))
1794 def send_raw_transaction(self, raw_tx, dialog = ""):
1795 result, result_message = self.wallet.sendtx( raw_tx )
1797 self.show_message("Transaction successfully sent: %s" % (result_message))
1801 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1803 def do_process_from_text(self):
1804 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1807 tx_dict = self.tx_dict_from_text(text)
1809 self.create_process_transaction_window(tx_dict)
1811 def do_process_from_file(self):
1812 tx_dict = self.read_tx_from_file()
1814 self.create_process_transaction_window(tx_dict)
1816 def create_process_transaction_window(self, tx_dict):
1817 tx = Transaction(tx_dict["hex"])
1819 dialog = QDialog(self)
1820 dialog.setMinimumWidth(500)
1821 dialog.setWindowTitle(_('Process raw transaction'))
1827 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1828 l.addWidget(QLabel(_("Actions")), 4,0)
1830 if tx_dict["complete"] == False:
1831 l.addWidget(QLabel(_("Unsigned")), 3,1)
1832 if self.wallet.seed :
1833 b = QPushButton("Sign transaction")
1834 input_info = json.loads(tx_dict["input_info"])
1835 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1836 l.addWidget(b, 4, 1)
1838 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1840 l.addWidget(QLabel(_("Signed")), 3,1)
1841 b = QPushButton("Broadcast transaction")
1842 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1845 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1846 cancelButton = QPushButton(_("Cancel"))
1847 cancelButton.clicked.connect(lambda: dialog.done(0))
1848 l.addWidget(cancelButton, 4,2)
1854 def do_export_privkeys(self, password):
1855 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.")))
1858 select_export = _('Select file to export your private keys to')
1859 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1861 with open(fileName, "w+") as csvfile:
1862 transaction = csv.writer(csvfile)
1863 transaction.writerow(["address", "private_key"])
1866 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1867 transaction.writerow(["%34s"%addr,pk])
1869 self.show_message(_("Private keys exported."))
1871 except (IOError, os.error), reason:
1872 export_error_label = _("Electrum was unable to produce a private key-export.")
1873 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1875 except BaseException, e:
1876 self.show_message(str(e))
1880 def do_import_labels(self):
1881 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1882 if not labelsFile: return
1884 f = open(labelsFile, 'r')
1887 for key, value in json.loads(data).items():
1888 self.wallet.labels[key] = value
1890 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1891 except (IOError, os.error), reason:
1892 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1895 def do_export_labels(self):
1896 labels = self.wallet.labels
1898 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1900 with open(fileName, 'w+') as f:
1901 json.dump(labels, f)
1902 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1903 except (IOError, os.error), reason:
1904 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1907 def do_export_history(self):
1908 from gui_lite import csv_transaction
1909 csv_transaction(self.wallet)
1913 def do_import_privkey(self, password):
1914 if not self.wallet.imported_keys:
1915 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1916 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1917 + _('Are you sure you understand what you are doing?'), 3, 4)
1920 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1923 text = str(text).split()
1928 addr = self.wallet.import_key(key, password)
1929 except BaseException as e:
1935 addrlist.append(addr)
1937 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1939 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1940 self.update_receive_tab()
1941 self.update_history_tab()
1944 def settings_dialog(self):
1946 d.setWindowTitle(_('Electrum Settings'))
1948 vbox = QVBoxLayout()
1950 tabs = QTabWidget(self)
1951 self.settings_tab = tabs
1952 vbox.addWidget(tabs)
1955 grid_ui = QGridLayout(tab1)
1956 grid_ui.setColumnStretch(0,1)
1957 tabs.addTab(tab1, _('Display') )
1959 nz_label = QLabel(_('Display zeros'))
1960 grid_ui.addWidget(nz_label, 0, 0)
1961 nz_e = AmountEdit(None,True)
1962 nz_e.setText("%d"% self.wallet.num_zeros)
1963 grid_ui.addWidget(nz_e, 0, 1)
1964 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1965 grid_ui.addWidget(HelpButton(msg), 0, 2)
1966 if not self.config.is_modifiable('num_zeros'):
1967 for w in [nz_e, nz_label]: w.setEnabled(False)
1969 lang_label=QLabel(_('Language') + ':')
1970 grid_ui.addWidget(lang_label, 1, 0)
1971 lang_combo = QComboBox()
1972 from i18n import languages
1973 lang_combo.addItems(languages.values())
1975 index = languages.keys().index(self.config.get("language",''))
1978 lang_combo.setCurrentIndex(index)
1979 grid_ui.addWidget(lang_combo, 1, 1)
1980 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1981 if not self.config.is_modifiable('language'):
1982 for w in [lang_combo, lang_label]: w.setEnabled(False)
1984 currencies = self.exchanger.get_currencies()
1985 currencies.insert(0, "None")
1987 cur_label=QLabel(_('Currency') + ':')
1988 grid_ui.addWidget(cur_label , 2, 0)
1989 cur_combo = QComboBox()
1990 cur_combo.addItems(currencies)
1992 index = currencies.index(self.config.get('currency', "None"))
1995 cur_combo.setCurrentIndex(index)
1996 grid_ui.addWidget(cur_combo, 2, 1)
1997 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1999 expert_cb = QCheckBox(_('Expert mode'))
2000 expert_cb.setChecked(self.expert_mode)
2001 grid_ui.addWidget(expert_cb, 3, 0)
2002 hh = _('In expert mode, your client will:') + '\n' \
2003 + _(' - Show change addresses in the Receive tab') + '\n' \
2004 + _(' - Display the balance of each address') + '\n' \
2005 + _(' - Add freeze/prioritize actions to addresses.')
2006 grid_ui.addWidget(HelpButton(hh), 3, 2)
2007 grid_ui.setRowStretch(4,1)
2011 grid_wallet = QGridLayout(tab2)
2012 grid_wallet.setColumnStretch(0,1)
2013 tabs.addTab(tab2, _('Wallet') )
2015 fee_label = QLabel(_('Transaction fee'))
2016 grid_wallet.addWidget(fee_label, 0, 0)
2017 fee_e = AmountEdit(self.base_unit)
2018 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2019 grid_wallet.addWidget(fee_e, 0, 2)
2020 msg = _('Fee per kilobyte of transaction.') + ' ' \
2021 + _('Recommended value') + ': ' + self.format_amount(50000)
2022 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2023 if not self.config.is_modifiable('fee_per_kb'):
2024 for w in [fee_e, fee_label]: w.setEnabled(False)
2026 usechange_cb = QCheckBox(_('Use change addresses'))
2027 usechange_cb.setChecked(self.wallet.use_change)
2028 grid_wallet.addWidget(usechange_cb, 1, 0)
2029 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2030 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2032 gap_label = QLabel(_('Gap limit'))
2033 grid_wallet.addWidget(gap_label, 2, 0)
2034 gap_e = AmountEdit(None,True)
2035 gap_e.setText("%d"% self.wallet.gap_limit)
2036 grid_wallet.addWidget(gap_e, 2, 2)
2037 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2038 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2039 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2040 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2041 + _('Warning') + ': ' \
2042 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2043 + _('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'
2044 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2045 if not self.config.is_modifiable('gap_limit'):
2046 for w in [gap_e, gap_label]: w.setEnabled(False)
2048 units = ['BTC', 'mBTC']
2049 unit_label = QLabel(_('Base unit'))
2050 grid_wallet.addWidget(unit_label, 3, 0)
2051 unit_combo = QComboBox()
2052 unit_combo.addItems(units)
2053 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2054 grid_wallet.addWidget(unit_combo, 3, 2)
2055 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2056 + '\n1BTC=1000mBTC.\n' \
2057 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2058 grid_wallet.setRowStretch(4,1)
2062 tab5 = QScrollArea()
2063 tab5.setEnabled(True)
2064 tab5.setWidgetResizable(True)
2066 grid_plugins = QGridLayout()
2067 grid_plugins.setColumnStretch(0,1)
2070 w.setLayout(grid_plugins)
2073 w.setMinimumHeight(len(self.plugins)*35)
2075 tabs.addTab(tab5, _('Plugins') )
2076 def mk_toggle(cb, p):
2077 return lambda: cb.setChecked(p.toggle())
2078 for i, p in enumerate(self.plugins):
2080 name, description = p.get_info()
2081 cb = QCheckBox(name)
2082 cb.setDisabled(not p.is_available())
2083 cb.setChecked(p.is_enabled())
2084 cb.clicked.connect(mk_toggle(cb,p))
2085 grid_plugins.addWidget(cb, i, 0)
2086 if p.requires_settings():
2087 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2088 grid_plugins.addWidget(HelpButton(description), i, 2)
2090 print_msg("Error: cannot display plugin", p)
2091 traceback.print_exc(file=sys.stdout)
2092 grid_plugins.setRowStretch(i+1,1)
2094 self.run_hook('create_settings_tab', tabs)
2096 vbox.addLayout(ok_cancel_buttons(d))
2100 if not d.exec_(): return
2102 fee = unicode(fee_e.text())
2104 fee = self.read_amount(fee)
2106 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2109 self.wallet.set_fee(fee)
2111 nz = unicode(nz_e.text())
2116 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2119 if self.wallet.num_zeros != nz:
2120 self.wallet.num_zeros = nz
2121 self.config.set_key('num_zeros', nz, True)
2122 self.update_history_tab()
2123 self.update_receive_tab()
2125 usechange_result = usechange_cb.isChecked()
2126 if self.wallet.use_change != usechange_result:
2127 self.wallet.use_change = usechange_result
2128 self.config.set_key('use_change', self.wallet.use_change, True)
2130 unit_result = units[unit_combo.currentIndex()]
2131 if self.base_unit() != unit_result:
2132 self.decimal_point = 8 if unit_result == 'BTC' else 5
2133 self.config.set_key('decimal_point', self.decimal_point, True)
2134 self.update_history_tab()
2135 self.update_status()
2138 n = int(gap_e.text())
2140 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2143 if self.wallet.gap_limit != n:
2144 r = self.wallet.change_gap_limit(n)
2146 self.update_receive_tab()
2147 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2149 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2151 need_restart = False
2153 lang_request = languages.keys()[lang_combo.currentIndex()]
2154 if lang_request != self.config.get('language'):
2155 self.config.set_key("language", lang_request, True)
2158 cur_request = str(currencies[cur_combo.currentIndex()])
2159 if cur_request != self.config.get('currency', "None"):
2160 self.config.set_key('currency', cur_request, True)
2161 self.update_wallet()
2163 self.run_hook('close_settings_dialog')
2166 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2168 self.receive_tab_set_mode(expert_cb.isChecked())
2170 def run_network_dialog(self):
2171 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2173 def closeEvent(self, event):
2175 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2176 self.save_column_widths()
2177 self.config.set_key("console-history",self.console.history[-50:])
2186 def __init__(self, wallet, config, app=None):
2187 self.wallet = wallet
2188 self.config = config
2190 self.app = QApplication(sys.argv)
2193 def restore_or_create(self):
2194 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2195 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2196 if r==2: return None
2197 return 'restore' if r==1 else 'create'
2200 def verify_seed(self):
2201 r = self.seed_dialog(False)
2202 if r != self.wallet.seed:
2203 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2210 def seed_dialog(self, is_restore=True):
2214 vbox = QVBoxLayout()
2216 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2218 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2220 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2223 label.setWordWrap(True)
2224 vbox.addWidget(label)
2226 seed_e = QTextEdit()
2227 seed_e.setMaximumHeight(100)
2228 vbox.addWidget(seed_e)
2231 grid = QGridLayout()
2233 gap_e = AmountEdit(None, True)
2235 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2236 grid.addWidget(gap_e, 2, 1)
2237 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2238 vbox.addLayout(grid)
2240 vbox.addLayout(ok_cancel_buttons(d))
2243 if not d.exec_(): return
2246 seed = str(seed_e.toPlainText())
2250 seed = mnemonic.mn_decode( seed.split() )
2252 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2256 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2263 gap = int(unicode(gap_e.text()))
2265 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2270 def network_dialog(self):
2271 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2274 def show_seed(self):
2275 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2277 def password_dialog(self):
2278 if self.wallet.seed:
2279 ElectrumWindow.change_password_dialog(self.wallet)
2282 def restore_wallet(self):
2283 wallet = self.wallet
2284 # wait until we are connected, because the user might have selected another server
2285 if not wallet.interface.is_connected:
2286 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2287 waiting_dialog(waiting)
2289 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2290 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2292 wallet.set_up_to_date(False)
2293 wallet.interface.poke('synchronizer')
2294 waiting_dialog(waiting)
2295 if wallet.is_found():
2296 print_error( "Recovery successful" )
2298 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2305 w = ElectrumWindow(self.wallet, self.config)
2306 if url: w.set_url(url)