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+Q"), self, self.close)
285 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
286 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
288 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
289 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
290 self.history_list.setFocus(True)
292 self.exchanger = exchange_rate.Exchanger(self)
293 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
295 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
296 if platform.system() == 'Windows':
297 n = 3 if self.wallet.seed else 2
298 tabs.setCurrentIndex (n)
299 tabs.setCurrentIndex (0)
302 if self.wallet.fee < 50000:
303 self.wallet.set_fee(50000)
304 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
306 # set initial message
307 self.console.showMessage(self.wallet.interface.banner)
309 # plugins that need to change the GUI do it here
310 self.run_hook('init_gui')
313 def select_wallet_file(self):
314 file_name = self.getOpenFileName("Select your wallet file", "*.dat")
318 self.load_wallet(file_name)
320 # TODO: I rather import this from the lite gui, is that possible?
321 def backup_wallet(self):
323 folderName = QFileDialog.getExistingDirectory(QWidget(), _('Select folder to save a copy of your wallet to'), os.path.expanduser('~/'))
325 # TODO: Can we get the current wallet file instead of bruteforcing the default one?
326 sourceFile = util.user_dir() + '/electrum.dat'
327 shutil.copy2(sourceFile, str(folderName))
328 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(folderName))
329 except (IOError, os.error), reason:
330 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
332 def init_menubar(self):
335 electrum_menu = menubar.addMenu(_("&File"))
336 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
337 open_wallet_action.triggered.connect(self.select_wallet_file)
339 preferences_name = _("Preferences")
340 if sys.platform == 'darwin':
341 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
343 preferences_menu = electrum_menu.addAction(preferences_name)
344 preferences_menu.triggered.connect(self.settings_dialog)
345 electrum_menu.addSeparator()
347 accounts_menu = electrum_menu.addMenu(_("&Accounts"))
348 all_accounts = accounts_menu.addAction(_("All accounts"))
349 all_accounts.triggered.connect(lambda: self.change_account(_("All accounts")))
351 main_account = accounts_menu.addAction(_("Main account"))
352 main_account.triggered.connect(lambda: self.change_account(_("Main account")))
354 imported_account = accounts_menu.addAction(_("Imported keys"))
355 imported_account.triggered.connect(lambda: self.change_account(_("Imported keys")))
357 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
359 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
360 raw_transaction_file.triggered.connect(self.do_process_from_file)
362 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
363 raw_transaction_text.triggered.connect(self.do_process_from_text)
365 electrum_menu.addSeparator()
366 quit_item = electrum_menu.addAction(_("&Close"))
367 quit_item.triggered.connect(self.close)
369 wallet_menu = menubar.addMenu(_("&Wallet"))
370 wallet_backup = wallet_menu.addAction(_("&Create backup"))
371 wallet_backup.triggered.connect(self.backup_wallet)
373 show_menu = wallet_menu.addMenu(_("Show"))
376 show_seed = show_menu.addAction(_("&Seed"))
377 show_seed.triggered.connect(self.show_seed_dialog)
379 show_mpk = show_menu.addAction(_("&Master Public Key"))
380 show_mpk.triggered.connect(self.show_master_public_key)
382 wallet_menu.addSeparator()
383 new_contact = wallet_menu.addAction(_("&New contact"))
384 new_contact.triggered.connect(self.new_contact_dialog)
386 import_menu = menubar.addMenu(_("&Import"))
387 in_labels = import_menu.addAction(_("&Labels"))
388 in_labels.triggered.connect(self.do_import_labels)
390 in_private_keys = import_menu.addAction(_("&Private keys"))
391 in_private_keys.triggered.connect(self.do_import_privkey)
393 export_menu = menubar.addMenu(_("&Export"))
394 ex_private_keys = export_menu.addAction(_("&Private keys"))
395 ex_private_keys.triggered.connect(self.do_export_privkeys)
397 ex_history = export_menu.addAction(_("&History"))
398 ex_history.triggered.connect(self.do_export_history)
400 ex_labels = export_menu.addAction(_("&Labels"))
401 ex_labels.triggered.connect(self.do_export_labels)
403 help_menu = menubar.addMenu(_("&Help"))
404 doc_open = help_menu.addAction(_("&Documentation"))
405 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
406 web_open = help_menu.addAction(_("&Official website"))
407 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
409 self.setMenuBar(menubar)
413 def load_wallet(self, filename):
416 config = electrum.SimpleConfig({'wallet_path': filename})
417 if not config.wallet_file_exists:
418 self.show_message("file not found "+ filename)
421 #self.wallet.verifier.stop()
422 interface = self.wallet.interface
423 verifier = self.wallet.verifier
424 self.wallet.synchronizer.stop()
427 self.wallet = electrum.Wallet(self.config)
428 self.wallet.interface = interface
429 self.wallet.verifier = verifier
431 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
439 def init_plugins(self):
440 import imp, pkgutil, __builtin__
441 if __builtin__.use_local_modules:
442 fp, pathname, description = imp.find_module('plugins')
443 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
444 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
445 imp.load_module('electrum_plugins', fp, pathname, description)
446 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
448 import electrum_plugins
449 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
450 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
455 self.plugins.append( p.Plugin(self) )
457 print_msg("Error:cannot initialize plugin",p)
458 traceback.print_exc(file=sys.stdout)
461 def run_hook(self, name, *args):
462 for p in self.plugins:
463 if not p.is_enabled():
472 print_error("Plugin error")
473 traceback.print_exc(file=sys.stdout)
478 def set_label(self, name, text = None):
480 old_text = self.wallet.labels.get(name)
483 self.wallet.labels[name] = text
487 self.wallet.labels.pop(name)
489 self.run_hook('set_label', name, text, changed)
493 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
494 def getOpenFileName(self, title, filter = None):
495 directory = self.config.get('io_dir', os.path.expanduser('~'))
496 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
497 if fileName and directory != os.path.dirname(fileName):
498 self.config.set_key('io_dir', os.path.dirname(fileName), True)
501 def getSaveFileName(self, title, filename, filter = None):
502 directory = self.config.get('io_dir', os.path.expanduser('~'))
503 path = os.path.join( directory, filename )
504 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
505 if fileName and directory != os.path.dirname(fileName):
506 self.config.set_key('io_dir', os.path.dirname(fileName), True)
512 QMainWindow.close(self)
513 self.run_hook('close_main_window')
515 def connect_slots(self, sender):
516 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
517 self.previous_payto_e=''
519 def timer_actions(self):
520 if self.need_update.is_set():
522 self.need_update.clear()
523 self.run_hook('timer_actions')
525 def format_amount(self, x, is_diff=False):
526 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
528 def read_amount(self, x):
529 if x in['.', '']: return None
530 p = pow(10, self.decimal_point)
531 return int( p * Decimal(x) )
534 assert self.decimal_point in [5,8]
535 return "BTC" if self.decimal_point == 8 else "mBTC"
537 def update_status(self):
538 if self.wallet.interface and self.wallet.interface.is_connected:
539 if not self.wallet.up_to_date:
540 text = _("Synchronizing...")
541 icon = QIcon(":icons/status_waiting.png")
543 c, u = self.wallet.get_account_balance(self.current_account)
544 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
545 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
546 text += self.create_quote_text(Decimal(c+u)/100000000)
547 icon = QIcon(":icons/status_connected.png")
549 text = _("Not connected")
550 icon = QIcon(":icons/status_disconnected.png")
552 self.balance_label.setText(text)
553 self.status_button.setIcon( icon )
555 def update_wallet(self):
557 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
558 self.update_history_tab()
559 self.update_receive_tab()
560 self.update_contacts_tab()
561 self.update_completions()
564 def create_quote_text(self, btc_balance):
565 quote_currency = self.config.get("currency", "None")
566 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
567 if quote_balance is None:
570 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
573 def create_history_tab(self):
574 self.history_list = l = MyTreeWidget(self)
576 for i,width in enumerate(self.column_widths['history']):
577 l.setColumnWidth(i, width)
578 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
579 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
580 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
582 l.setContextMenuPolicy(Qt.CustomContextMenu)
583 l.customContextMenuRequested.connect(self.create_history_menu)
587 def create_history_menu(self, position):
588 self.history_list.selectedIndexes()
589 item = self.history_list.currentItem()
591 tx_hash = str(item.data(0, Qt.UserRole).toString())
592 if not tx_hash: return
594 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
595 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
596 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
597 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
600 def show_tx_details(self, tx):
601 dialog = QDialog(self)
603 dialog.setWindowTitle(_("Transaction Details"))
605 dialog.setLayout(vbox)
606 dialog.setMinimumSize(600,300)
609 if tx_hash in self.wallet.transactions.keys():
610 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
611 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
613 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
619 vbox.addWidget(QLabel("Transaction ID:"))
620 e = QLineEdit(tx_hash)
624 vbox.addWidget(QLabel("Date: %s"%time_str))
625 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
628 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
629 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
631 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
632 vbox.addWidget(QLabel("Transaction fee: unknown"))
634 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
636 vbox.addWidget( self.generate_transaction_information_widget(tx) )
638 ok_button = QPushButton(_("Close"))
639 ok_button.setDefault(True)
640 ok_button.clicked.connect(dialog.accept)
644 hbox.addWidget(ok_button)
648 def tx_label_clicked(self, item, column):
649 if column==2 and item.isSelected():
651 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
652 self.history_list.editItem( item, column )
653 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
656 def tx_label_changed(self, item, column):
660 tx_hash = str(item.data(0, Qt.UserRole).toString())
661 tx = self.wallet.transactions.get(tx_hash)
662 text = unicode( item.text(2) )
663 self.set_label(tx_hash, text)
665 item.setForeground(2, QBrush(QColor('black')))
667 text = self.wallet.get_default_label(tx_hash)
668 item.setText(2, text)
669 item.setForeground(2, QBrush(QColor('gray')))
673 def edit_label(self, is_recv):
674 l = self.receive_list if is_recv else self.contacts_list
675 item = l.currentItem()
676 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
677 l.editItem( item, 1 )
678 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
682 def address_label_clicked(self, item, column, l, column_addr, column_label):
683 if column == column_label and item.isSelected():
684 is_editable = item.data(0, 32).toBool()
687 addr = unicode( item.text(column_addr) )
688 label = unicode( item.text(column_label) )
689 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
690 l.editItem( item, column )
691 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
694 def address_label_changed(self, item, column, l, column_addr, column_label):
695 if column == column_label:
696 addr = unicode( item.text(column_addr) )
697 text = unicode( item.text(column_label) )
698 is_editable = item.data(0, 32).toBool()
702 changed = self.set_label(addr, text)
704 self.update_history_tab()
705 self.update_completions()
707 self.current_item_changed(item)
709 self.run_hook('item_changed', item, column)
712 def current_item_changed(self, a):
713 self.run_hook('current_item_changed', a)
717 def update_history_tab(self):
719 self.history_list.clear()
720 for item in self.wallet.get_tx_history(self.current_account):
721 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
724 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
729 time_str = 'unverified'
730 icon = QIcon(":icons/unconfirmed.png")
733 icon = QIcon(":icons/unconfirmed.png")
735 icon = QIcon(":icons/clock%d.png"%conf)
737 icon = QIcon(":icons/confirmed.png")
739 if value is not None:
740 v_str = self.format_amount(value, True)
744 balance_str = self.format_amount(balance)
747 label, is_default_label = self.wallet.get_label(tx_hash)
749 label = _('Pruned transaction outputs')
750 is_default_label = False
752 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
753 item.setFont(2, QFont(MONOSPACE_FONT))
754 item.setFont(3, QFont(MONOSPACE_FONT))
755 item.setFont(4, QFont(MONOSPACE_FONT))
757 item.setForeground(3, QBrush(QColor("#BC1E1E")))
759 item.setData(0, Qt.UserRole, tx_hash)
760 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
762 item.setForeground(2, QBrush(QColor('grey')))
764 item.setIcon(0, icon)
765 self.history_list.insertTopLevelItem(0,item)
768 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
771 def create_send_tab(self):
776 grid.setColumnMinimumWidth(3,300)
777 grid.setColumnStretch(5,1)
780 self.payto_e = QLineEdit()
781 grid.addWidget(QLabel(_('Pay to')), 1, 0)
782 grid.addWidget(self.payto_e, 1, 1, 1, 3)
784 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)
786 completer = QCompleter()
787 completer.setCaseSensitivity(False)
788 self.payto_e.setCompleter(completer)
789 completer.setModel(self.completions)
791 self.message_e = QLineEdit()
792 grid.addWidget(QLabel(_('Description')), 2, 0)
793 grid.addWidget(self.message_e, 2, 1, 1, 3)
794 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)
796 self.amount_e = AmountEdit(self.base_unit)
797 grid.addWidget(QLabel(_('Amount')), 3, 0)
798 grid.addWidget(self.amount_e, 3, 1, 1, 2)
799 grid.addWidget(HelpButton(
800 _('Amount to be sent.') + '\n\n' \
801 + _('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.') \
802 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
804 self.fee_e = AmountEdit(self.base_unit)
805 grid.addWidget(QLabel(_('Fee')), 4, 0)
806 grid.addWidget(self.fee_e, 4, 1, 1, 2)
807 grid.addWidget(HelpButton(
808 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
809 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
810 + _('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)
813 b = EnterButton(_("Send"), self.do_send)
815 b = EnterButton(_("Create unsigned transaction"), self.do_send)
816 grid.addWidget(b, 6, 1)
818 b = EnterButton(_("Clear"),self.do_clear)
819 grid.addWidget(b, 6, 2)
821 self.payto_sig = QLabel('')
822 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
824 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
825 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
834 def entry_changed( is_fee ):
835 self.funds_error = False
837 if self.amount_e.is_shortcut:
838 self.amount_e.is_shortcut = False
839 c, u = self.wallet.get_account_balance(self.current_account)
840 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
841 fee = self.wallet.estimated_fee(inputs)
843 self.amount_e.setText( self.format_amount(amount) )
844 self.fee_e.setText( self.format_amount( fee ) )
847 amount = self.read_amount(str(self.amount_e.text()))
848 fee = self.read_amount(str(self.fee_e.text()))
850 if not is_fee: fee = None
853 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
855 self.fee_e.setText( self.format_amount( fee ) )
858 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
862 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
863 self.funds_error = True
864 text = _( "Not enough funds" )
865 c, u = self.wallet.get_frozen_balance()
866 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
868 self.statusBar().showMessage(text)
869 self.amount_e.setPalette(palette)
870 self.fee_e.setPalette(palette)
872 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
873 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
875 self.run_hook('create_send_tab', grid)
879 def update_completions(self):
881 for addr,label in self.wallet.labels.items():
882 if addr in self.wallet.addressbook:
883 l.append( label + ' <' + addr + '>')
885 self.run_hook('update_completions', l)
886 self.completions.setStringList(l)
890 return lambda s, *args: s.do_protect(func, args)
894 def do_send(self, password):
896 label = unicode( self.message_e.text() )
897 r = unicode( self.payto_e.text() )
900 # label or alias, with address in brackets
901 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
902 to_address = m.group(2) if m else r
904 if not is_valid(to_address):
905 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
909 amount = self.read_amount(unicode( self.amount_e.text()))
911 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
914 fee = self.read_amount(unicode( self.fee_e.text()))
916 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
920 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
921 except BaseException, e:
922 self.show_message(str(e))
925 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
926 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
929 self.run_hook('send_tx', tx)
932 self.set_label(tx.hash(), label)
935 h = self.wallet.send_tx(tx)
936 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
937 status, msg = self.wallet.receive_tx( h )
939 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
941 self.update_contacts_tab()
943 QMessageBox.warning(self, _('Error'), msg, _('OK'))
945 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
947 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
948 with open(fileName,'w') as f:
949 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
950 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
952 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
957 def set_url(self, url):
958 address, amount, label, message, signature, identity, url = util.parse_url(url)
959 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
961 if label and self.wallet.labels.get(address) != label:
962 if self.question('Give label "%s" to address %s ?'%(label,address)):
963 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
964 self.wallet.addressbook.append(address)
965 self.set_label(address, label)
967 self.run_hook('set_url', url, self.show_message, self.question)
969 self.tabs.setCurrentIndex(1)
970 label = self.wallet.labels.get(address)
971 m_addr = label + ' <'+ address +'>' if label else address
972 self.payto_e.setText(m_addr)
974 self.message_e.setText(message)
975 self.amount_e.setText(amount)
977 self.set_frozen(self.payto_e,True)
978 self.set_frozen(self.amount_e,True)
979 self.set_frozen(self.message_e,True)
980 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
982 self.payto_sig.setVisible(False)
985 self.payto_sig.setVisible(False)
986 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
988 self.set_frozen(e,False)
991 def set_frozen(self,entry,frozen):
993 entry.setReadOnly(True)
994 entry.setFrame(False)
996 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
997 entry.setPalette(palette)
999 entry.setReadOnly(False)
1000 entry.setFrame(True)
1001 palette = QPalette()
1002 palette.setColor(entry.backgroundRole(), QColor('white'))
1003 entry.setPalette(palette)
1006 def toggle_freeze(self,addr):
1008 if addr in self.wallet.frozen_addresses:
1009 self.wallet.unfreeze(addr)
1011 self.wallet.freeze(addr)
1012 self.update_receive_tab()
1014 def toggle_priority(self,addr):
1016 if addr in self.wallet.prioritized_addresses:
1017 self.wallet.unprioritize(addr)
1019 self.wallet.prioritize(addr)
1020 self.update_receive_tab()
1023 def create_list_tab(self, headers):
1024 "generic tab creation method"
1025 l = MyTreeWidget(self)
1026 l.setColumnCount( len(headers) )
1027 l.setHeaderLabels( headers )
1030 vbox = QVBoxLayout()
1037 vbox.addWidget(buttons)
1039 hbox = QHBoxLayout()
1042 buttons.setLayout(hbox)
1047 def create_receive_tab(self):
1048 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1049 l.setContextMenuPolicy(Qt.CustomContextMenu)
1050 l.customContextMenuRequested.connect(self.create_receive_menu)
1051 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1052 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1053 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1054 self.receive_list = l
1055 self.receive_buttons_hbox = hbox
1060 def receive_tab_set_mode(self, i):
1061 self.save_column_widths()
1062 self.expert_mode = (i == 1)
1063 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1064 self.update_receive_tab()
1067 def save_column_widths(self):
1068 if not self.expert_mode:
1069 widths = [ self.receive_list.columnWidth(0) ]
1072 for i in range(self.receive_list.columnCount() -1):
1073 widths.append(self.receive_list.columnWidth(i))
1074 self.column_widths["receive"][self.expert_mode] = widths
1076 self.column_widths["history"] = []
1077 for i in range(self.history_list.columnCount() - 1):
1078 self.column_widths["history"].append(self.history_list.columnWidth(i))
1080 self.column_widths["contacts"] = []
1081 for i in range(self.contacts_list.columnCount() - 1):
1082 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1084 self.config.set_key("column_widths", self.column_widths, True)
1087 def create_contacts_tab(self):
1088 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1089 l.setContextMenuPolicy(Qt.CustomContextMenu)
1090 l.customContextMenuRequested.connect(self.create_contact_menu)
1091 for i,width in enumerate(self.column_widths['contacts']):
1092 l.setColumnWidth(i, width)
1094 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1095 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1096 self.contacts_list = l
1097 self.contacts_buttons_hbox = hbox
1098 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1103 def delete_imported_key(self, addr):
1104 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1105 self.wallet.delete_imported_key(addr)
1106 self.update_receive_tab()
1107 self.update_history_tab()
1110 def create_receive_menu(self, position):
1111 # fixme: this function apparently has a side effect.
1112 # if it is not called the menu pops up several times
1113 #self.receive_list.selectedIndexes()
1115 item = self.receive_list.itemAt(position)
1117 addr = unicode(item.text(0))
1118 if not is_valid(addr):
1119 item.setExpanded(not item.isExpanded())
1122 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1123 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1124 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1125 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1126 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1127 if addr in self.wallet.imported_keys:
1128 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1130 if self.expert_mode:
1131 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1132 menu.addAction(t, lambda: self.toggle_freeze(addr))
1133 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1134 menu.addAction(t, lambda: self.toggle_priority(addr))
1136 self.run_hook('receive_menu', menu)
1137 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1140 def payto(self, addr):
1142 label = self.wallet.labels.get(addr)
1143 m_addr = label + ' <' + addr + '>' if label else addr
1144 self.tabs.setCurrentIndex(1)
1145 self.payto_e.setText(m_addr)
1146 self.amount_e.setFocus()
1149 def delete_contact(self, x):
1150 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1151 self.wallet.delete_contact(x)
1152 self.set_label(x, None)
1153 self.update_history_tab()
1154 self.update_contacts_tab()
1155 self.update_completions()
1158 def create_contact_menu(self, position):
1159 item = self.contacts_list.itemAt(position)
1161 addr = unicode(item.text(0))
1162 label = unicode(item.text(1))
1163 is_editable = item.data(0,32).toBool()
1164 payto_addr = item.data(0,33).toString()
1166 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1167 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1168 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1170 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1171 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1173 self.run_hook('create_contact_menu', menu, item)
1174 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1177 def update_receive_item(self, item):
1178 item.setFont(0, QFont(MONOSPACE_FONT))
1179 address = str(item.data(0,0).toString())
1180 label = self.wallet.labels.get(address,'')
1181 item.setData(1,0,label)
1182 item.setData(0,32, True) # is editable
1184 self.run_hook('update_receive_item', address, item)
1186 c, u = self.wallet.get_addr_balance(address)
1187 balance = self.format_amount(c + u)
1188 item.setData(2,0,balance)
1190 if self.expert_mode:
1191 if address in self.wallet.frozen_addresses:
1192 item.setBackgroundColor(0, QColor('lightblue'))
1193 elif address in self.wallet.prioritized_addresses:
1194 item.setBackgroundColor(0, QColor('lightgreen'))
1197 def update_receive_tab(self):
1198 l = self.receive_list
1201 l.setColumnHidden(2, not self.expert_mode)
1202 l.setColumnHidden(3, not self.expert_mode)
1203 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1204 l.setColumnWidth(i, width)
1206 if self.current_account is None:
1207 account_items = self.wallet.accounts.items()
1208 elif self.current_account != -1:
1209 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1213 for k, account in account_items:
1214 name = account.get('name',str(k))
1215 c,u = self.wallet.get_account_balance(k)
1216 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1217 l.addTopLevelItem(account_item)
1218 account_item.setExpanded(True)
1220 for is_change in ([0,1] if self.expert_mode else [0]):
1221 if self.expert_mode:
1222 name = "Receiving" if not is_change else "Change"
1223 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1224 account_item.addChild(seq_item)
1225 if not is_change: seq_item.setExpanded(True)
1227 seq_item = account_item
1231 for address in account[is_change]:
1232 h = self.wallet.history.get(address,[])
1236 if gap > self.wallet.gap_limit:
1241 num_tx = '*' if h == ['*'] else "%d"%len(h)
1242 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1243 self.update_receive_item(item)
1245 item.setBackgroundColor(1, QColor('red'))
1246 seq_item.addChild(item)
1249 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1250 c,u = self.wallet.get_imported_balance()
1251 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1252 l.addTopLevelItem(account_item)
1253 account_item.setExpanded(True)
1254 for address in self.wallet.imported_keys.keys():
1255 item = QTreeWidgetItem( [ address, '', '', ''] )
1256 self.update_receive_item(item)
1257 account_item.addChild(item)
1260 # we use column 1 because column 0 may be hidden
1261 l.setCurrentItem(l.topLevelItem(0),1)
1264 def update_contacts_tab(self):
1266 l = self.contacts_list
1269 for address in self.wallet.addressbook:
1270 label = self.wallet.labels.get(address,'')
1271 n = self.wallet.get_num_tx(address)
1272 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1273 item.setFont(0, QFont(MONOSPACE_FONT))
1274 # 32 = label can be edited (bool)
1275 item.setData(0,32, True)
1277 item.setData(0,33, address)
1278 l.addTopLevelItem(item)
1280 self.run_hook('update_contacts_tab', l)
1281 l.setCurrentItem(l.topLevelItem(0))
1285 def create_console_tab(self):
1286 from qt_console import Console
1287 self.console = console = Console()
1288 self.console.history = self.config.get("console-history",[])
1289 self.console.history_index = len(self.console.history)
1291 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1292 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1294 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1296 def mkfunc(f, method):
1297 return lambda *args: apply( f, (method, args, self.password_dialog ))
1299 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1300 methods[m] = mkfunc(c._run, m)
1302 console.updateNamespace(methods)
1305 def change_account(self,s):
1306 if s == _("All accounts"):
1307 self.current_account = None
1309 accounts = self.wallet.get_accounts()
1310 for k, v in accounts.items():
1312 self.current_account = k
1313 self.update_history_tab()
1314 self.update_status()
1315 self.update_receive_tab()
1317 def create_status_bar(self):
1320 sb.setFixedHeight(35)
1321 qtVersion = qVersion()
1323 self.balance_label = QLabel("")
1324 sb.addWidget(self.balance_label)
1326 update_notification = UpdateLabel(self.config)
1327 if(update_notification.new_version):
1328 sb.addPermanentWidget(update_notification)
1330 accounts = self.wallet.get_accounts()
1331 if len(accounts) > 1:
1332 from_combo = QComboBox()
1333 from_combo.addItems([_("All accounts")] + accounts.values())
1334 from_combo.setCurrentIndex(0)
1335 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1336 sb.addPermanentWidget(from_combo)
1338 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1339 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1340 if self.wallet.seed:
1341 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1342 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1343 sb.addPermanentWidget( self.password_button )
1344 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1345 if self.wallet.seed:
1346 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1347 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1348 sb.addPermanentWidget( self.status_button )
1350 self.run_hook('create_status_bar', (sb,))
1352 self.setStatusBar(sb)
1356 self.config.set_key('gui', 'lite', True)
1359 self.lite.mini.show()
1361 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1362 self.lite.main(None)
1364 def new_contact_dialog(self):
1365 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1366 address = unicode(text)
1368 if is_valid(address):
1369 self.wallet.add_contact(address)
1370 self.update_contacts_tab()
1371 self.update_history_tab()
1372 self.update_completions()
1374 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1376 def show_master_public_key(self):
1377 dialog = QDialog(self)
1379 dialog.setWindowTitle(_("Master Public Key"))
1381 main_text = QTextEdit()
1382 main_text.setText(self.wallet.get_master_public_key())
1383 main_text.setReadOnly(True)
1384 main_text.setMaximumHeight(170)
1385 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1387 ok_button = QPushButton(_("OK"))
1388 ok_button.setDefault(True)
1389 ok_button.clicked.connect(dialog.accept)
1391 main_layout = QGridLayout()
1392 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1394 main_layout.addWidget(main_text, 1, 0)
1395 main_layout.addWidget(qrw, 1, 1 )
1397 vbox = QVBoxLayout()
1398 vbox.addLayout(main_layout)
1399 hbox = QHBoxLayout()
1401 hbox.addWidget(ok_button)
1402 vbox.addLayout(hbox)
1404 dialog.setLayout(vbox)
1409 def show_seed_dialog(self, password):
1410 if not self.wallet.seed:
1411 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1414 seed = self.wallet.decode_seed(password)
1416 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1418 self.show_seed(seed, self.wallet.imported_keys, self)
1422 def show_seed(self, seed, imported_keys, parent=None):
1423 dialog = QDialog(parent)
1425 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1427 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1429 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1431 seed_text = QTextEdit(brainwallet)
1432 seed_text.setReadOnly(True)
1433 seed_text.setMaximumHeight(130)
1435 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1436 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1437 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1438 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1440 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1441 label2 = QLabel(msg2)
1442 label2.setWordWrap(True)
1445 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1446 logo.setMaximumWidth(60)
1448 qrw = QRCodeWidget(seed)
1450 ok_button = QPushButton(_("OK"))
1451 ok_button.setDefault(True)
1452 ok_button.clicked.connect(dialog.accept)
1454 grid = QGridLayout()
1455 #main_layout.addWidget(logo, 0, 0)
1457 grid.addWidget(logo, 0, 0)
1458 grid.addWidget(label1, 0, 1)
1460 grid.addWidget(seed_text, 1, 0, 1, 2)
1462 grid.addWidget(qrw, 0, 2, 2, 1)
1464 vbox = QVBoxLayout()
1465 vbox.addLayout(grid)
1466 vbox.addWidget(label2)
1468 hbox = QHBoxLayout()
1470 hbox.addWidget(ok_button)
1471 vbox.addLayout(hbox)
1473 dialog.setLayout(vbox)
1476 def show_qrcode(self, data, title = "QR code"):
1480 d.setWindowTitle(title)
1481 d.setMinimumSize(270, 300)
1482 vbox = QVBoxLayout()
1483 qrw = QRCodeWidget(data)
1484 vbox.addWidget(qrw, 1)
1485 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1486 hbox = QHBoxLayout()
1490 filename = "qrcode.bmp"
1491 bmp.save_qrcode(qrw.qr, filename)
1492 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1494 b = QPushButton(_("Save"))
1496 b.clicked.connect(print_qr)
1498 b = QPushButton(_("Close"))
1500 b.clicked.connect(d.accept)
1503 vbox.addLayout(hbox)
1508 def do_protect(self, func, args):
1509 if self.wallet.use_encryption:
1510 password = self.password_dialog()
1516 if args != (False,):
1517 args = (self,) + args + (password,)
1519 args = (self,password)
1524 def show_private_key(self, address, password):
1525 if not address: return
1527 pk = self.wallet.get_private_key(address, password)
1528 except BaseException, e:
1529 self.show_message(str(e))
1531 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1535 def do_sign(self, address, message, signature, password):
1537 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1538 signature.setText(sig)
1539 except BaseException, e:
1540 self.show_message(str(e))
1542 def sign_message(self, address):
1543 if not address: return
1546 d.setWindowTitle(_('Sign Message'))
1547 d.setMinimumSize(410, 290)
1549 tab_widget = QTabWidget()
1551 layout = QGridLayout(tab)
1553 sign_address = QLineEdit()
1555 sign_address.setText(address)
1556 layout.addWidget(QLabel(_('Address')), 1, 0)
1557 layout.addWidget(sign_address, 1, 1)
1559 sign_message = QTextEdit()
1560 layout.addWidget(QLabel(_('Message')), 2, 0)
1561 layout.addWidget(sign_message, 2, 1)
1562 layout.setRowStretch(2,3)
1564 sign_signature = QTextEdit()
1565 layout.addWidget(QLabel(_('Signature')), 3, 0)
1566 layout.addWidget(sign_signature, 3, 1)
1567 layout.setRowStretch(3,1)
1570 hbox = QHBoxLayout()
1571 b = QPushButton(_("Sign"))
1573 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1574 b = QPushButton(_("Close"))
1575 b.clicked.connect(d.accept)
1577 layout.addLayout(hbox, 4, 1)
1578 tab_widget.addTab(tab, _("Sign"))
1582 layout = QGridLayout(tab)
1584 verify_address = QLineEdit()
1585 layout.addWidget(QLabel(_('Address')), 1, 0)
1586 layout.addWidget(verify_address, 1, 1)
1588 verify_message = QTextEdit()
1589 layout.addWidget(QLabel(_('Message')), 2, 0)
1590 layout.addWidget(verify_message, 2, 1)
1591 layout.setRowStretch(2,3)
1593 verify_signature = QTextEdit()
1594 layout.addWidget(QLabel(_('Signature')), 3, 0)
1595 layout.addWidget(verify_signature, 3, 1)
1596 layout.setRowStretch(3,1)
1599 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1600 self.show_message(_("Signature verified"))
1602 self.show_message(_("Error: wrong signature"))
1604 hbox = QHBoxLayout()
1605 b = QPushButton(_("Verify"))
1606 b.clicked.connect(do_verify)
1608 b = QPushButton(_("Close"))
1609 b.clicked.connect(d.accept)
1611 layout.addLayout(hbox, 4, 1)
1612 tab_widget.addTab(tab, _("Verify"))
1614 vbox = QVBoxLayout()
1615 vbox.addWidget(tab_widget)
1622 def question(self, msg):
1623 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1625 def show_message(self, msg):
1626 QMessageBox.information(self, _('Message'), msg, _('OK'))
1628 def password_dialog(self ):
1635 vbox = QVBoxLayout()
1636 msg = _('Please enter your password')
1637 vbox.addWidget(QLabel(msg))
1639 grid = QGridLayout()
1641 grid.addWidget(QLabel(_('Password')), 1, 0)
1642 grid.addWidget(pw, 1, 1)
1643 vbox.addLayout(grid)
1645 vbox.addLayout(ok_cancel_buttons(d))
1648 self.run_hook('password_dialog', pw, grid, 1)
1649 if not d.exec_(): return
1650 return unicode(pw.text())
1657 def change_password_dialog( wallet, parent=None ):
1660 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1668 new_pw = QLineEdit()
1669 new_pw.setEchoMode(2)
1670 conf_pw = QLineEdit()
1671 conf_pw.setEchoMode(2)
1673 vbox = QVBoxLayout()
1675 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1676 +_('To disable wallet encryption, enter an empty new password.')) \
1677 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1679 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1680 +_("Leave these fields empty if you want to disable encryption.")
1681 vbox.addWidget(QLabel(msg))
1683 grid = QGridLayout()
1686 if wallet.use_encryption:
1687 grid.addWidget(QLabel(_('Password')), 1, 0)
1688 grid.addWidget(pw, 1, 1)
1690 grid.addWidget(QLabel(_('New Password')), 2, 0)
1691 grid.addWidget(new_pw, 2, 1)
1693 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1694 grid.addWidget(conf_pw, 3, 1)
1695 vbox.addLayout(grid)
1697 vbox.addLayout(ok_cancel_buttons(d))
1700 if not d.exec_(): return
1702 password = unicode(pw.text()) if wallet.use_encryption else None
1703 new_password = unicode(new_pw.text())
1704 new_password2 = unicode(conf_pw.text())
1707 seed = wallet.decode_seed(password)
1709 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1712 if new_password != new_password2:
1713 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1714 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1717 wallet.update_password(seed, password, new_password)
1719 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1722 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1725 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1726 parent.password_button.setIcon( icon )
1730 def generate_transaction_information_widget(self, tx):
1731 tabs = QTabWidget(self)
1734 grid_ui = QGridLayout(tab1)
1735 grid_ui.setColumnStretch(0,1)
1736 tabs.addTab(tab1, _('Outputs') )
1738 tree_widget = MyTreeWidget(self)
1739 tree_widget.setColumnCount(2)
1740 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1741 tree_widget.setColumnWidth(0, 300)
1742 tree_widget.setColumnWidth(1, 50)
1744 for address, value in tx.outputs:
1745 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1746 tree_widget.addTopLevelItem(item)
1748 tree_widget.setMaximumHeight(100)
1750 grid_ui.addWidget(tree_widget)
1753 grid_ui = QGridLayout(tab2)
1754 grid_ui.setColumnStretch(0,1)
1755 tabs.addTab(tab2, _('Inputs') )
1757 tree_widget = MyTreeWidget(self)
1758 tree_widget.setColumnCount(2)
1759 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1761 for input_line in tx.inputs:
1762 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1763 tree_widget.addTopLevelItem(item)
1765 tree_widget.setMaximumHeight(100)
1767 grid_ui.addWidget(tree_widget)
1771 def tx_dict_from_text(self, txt):
1773 tx_dict = json.loads(str(txt))
1774 assert "hex" in tx_dict.keys()
1775 assert "complete" in tx_dict.keys()
1776 if not tx_dict["complete"]:
1777 assert "input_info" in tx_dict.keys()
1779 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1784 def read_tx_from_file(self):
1785 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1789 with open(fileName, "r") as f:
1790 file_content = f.read()
1791 except (ValueError, IOError, os.error), reason:
1792 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1794 return self.tx_dict_from_text(file_content)
1798 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1800 self.wallet.signrawtransaction(tx, input_info, [], password)
1802 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1804 with open(fileName, "w+") as f:
1805 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1806 self.show_message(_("Transaction saved successfully"))
1809 except BaseException, e:
1810 self.show_message(str(e))
1813 def send_raw_transaction(self, raw_tx, dialog = ""):
1814 result, result_message = self.wallet.sendtx( raw_tx )
1816 self.show_message("Transaction successfully sent: %s" % (result_message))
1820 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1822 def do_process_from_text(self):
1823 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1826 tx_dict = self.tx_dict_from_text(text)
1828 self.create_process_transaction_window(tx_dict)
1830 def do_process_from_file(self):
1831 tx_dict = self.read_tx_from_file()
1833 self.create_process_transaction_window(tx_dict)
1835 def create_process_transaction_window(self, tx_dict):
1836 tx = Transaction(tx_dict["hex"])
1838 dialog = QDialog(self)
1839 dialog.setMinimumWidth(500)
1840 dialog.setWindowTitle(_('Process raw transaction'))
1846 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1847 l.addWidget(QLabel(_("Actions")), 4,0)
1849 if tx_dict["complete"] == False:
1850 l.addWidget(QLabel(_("Unsigned")), 3,1)
1851 if self.wallet.seed :
1852 b = QPushButton("Sign transaction")
1853 input_info = json.loads(tx_dict["input_info"])
1854 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1855 l.addWidget(b, 4, 1)
1857 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1859 l.addWidget(QLabel(_("Signed")), 3,1)
1860 b = QPushButton("Broadcast transaction")
1861 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1864 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1865 cancelButton = QPushButton(_("Cancel"))
1866 cancelButton.clicked.connect(lambda: dialog.done(0))
1867 l.addWidget(cancelButton, 4,2)
1873 def do_export_privkeys(self, password):
1874 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.")))
1877 select_export = _('Select file to export your private keys to')
1878 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1880 with open(fileName, "w+") as csvfile:
1881 transaction = csv.writer(csvfile)
1882 transaction.writerow(["address", "private_key"])
1885 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1886 transaction.writerow(["%34s"%addr,pk])
1888 self.show_message(_("Private keys exported."))
1890 except (IOError, os.error), reason:
1891 export_error_label = _("Electrum was unable to produce a private key-export.")
1892 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1894 except BaseException, e:
1895 self.show_message(str(e))
1899 def do_import_labels(self):
1900 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1901 if not labelsFile: return
1903 f = open(labelsFile, 'r')
1906 for key, value in json.loads(data).items():
1907 self.wallet.labels[key] = value
1909 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1910 except (IOError, os.error), reason:
1911 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1914 def do_export_labels(self):
1915 labels = self.wallet.labels
1917 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1919 with open(fileName, 'w+') as f:
1920 json.dump(labels, f)
1921 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1922 except (IOError, os.error), reason:
1923 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1926 def do_export_history(self):
1927 from gui_lite import csv_transaction
1928 csv_transaction(self.wallet)
1932 def do_import_privkey(self, password):
1933 if not self.wallet.imported_keys:
1934 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1935 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1936 + _('Are you sure you understand what you are doing?'), 3, 4)
1939 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1942 text = str(text).split()
1947 addr = self.wallet.import_key(key, password)
1948 except BaseException as e:
1954 addrlist.append(addr)
1956 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1958 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1959 self.update_receive_tab()
1960 self.update_history_tab()
1963 def settings_dialog(self):
1965 d.setWindowTitle(_('Electrum Settings'))
1967 vbox = QVBoxLayout()
1969 tabs = QTabWidget(self)
1970 self.settings_tab = tabs
1971 vbox.addWidget(tabs)
1974 grid_ui = QGridLayout(tab1)
1975 grid_ui.setColumnStretch(0,1)
1976 tabs.addTab(tab1, _('Display') )
1978 nz_label = QLabel(_('Display zeros'))
1979 grid_ui.addWidget(nz_label, 0, 0)
1980 nz_e = AmountEdit(None,True)
1981 nz_e.setText("%d"% self.wallet.num_zeros)
1982 grid_ui.addWidget(nz_e, 0, 1)
1983 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1984 grid_ui.addWidget(HelpButton(msg), 0, 2)
1985 if not self.config.is_modifiable('num_zeros'):
1986 for w in [nz_e, nz_label]: w.setEnabled(False)
1988 lang_label=QLabel(_('Language') + ':')
1989 grid_ui.addWidget(lang_label, 1, 0)
1990 lang_combo = QComboBox()
1991 from i18n import languages
1992 lang_combo.addItems(languages.values())
1994 index = languages.keys().index(self.config.get("language",''))
1997 lang_combo.setCurrentIndex(index)
1998 grid_ui.addWidget(lang_combo, 1, 1)
1999 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2000 if not self.config.is_modifiable('language'):
2001 for w in [lang_combo, lang_label]: w.setEnabled(False)
2003 currencies = self.exchanger.get_currencies()
2004 currencies.insert(0, "None")
2006 cur_label=QLabel(_('Currency') + ':')
2007 grid_ui.addWidget(cur_label , 2, 0)
2008 cur_combo = QComboBox()
2009 cur_combo.addItems(currencies)
2011 index = currencies.index(self.config.get('currency', "None"))
2014 cur_combo.setCurrentIndex(index)
2015 grid_ui.addWidget(cur_combo, 2, 1)
2016 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2018 expert_cb = QCheckBox(_('Expert mode'))
2019 expert_cb.setChecked(self.expert_mode)
2020 grid_ui.addWidget(expert_cb, 3, 0)
2021 hh = _('In expert mode, your client will:') + '\n' \
2022 + _(' - Show change addresses in the Receive tab') + '\n' \
2023 + _(' - Display the balance of each address') + '\n' \
2024 + _(' - Add freeze/prioritize actions to addresses.')
2025 grid_ui.addWidget(HelpButton(hh), 3, 2)
2026 grid_ui.setRowStretch(4,1)
2030 grid_wallet = QGridLayout(tab2)
2031 grid_wallet.setColumnStretch(0,1)
2032 tabs.addTab(tab2, _('Wallet') )
2034 fee_label = QLabel(_('Transaction fee'))
2035 grid_wallet.addWidget(fee_label, 0, 0)
2036 fee_e = AmountEdit(self.base_unit)
2037 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2038 grid_wallet.addWidget(fee_e, 0, 2)
2039 msg = _('Fee per kilobyte of transaction.') + ' ' \
2040 + _('Recommended value') + ': ' + self.format_amount(50000)
2041 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2042 if not self.config.is_modifiable('fee_per_kb'):
2043 for w in [fee_e, fee_label]: w.setEnabled(False)
2045 usechange_cb = QCheckBox(_('Use change addresses'))
2046 usechange_cb.setChecked(self.wallet.use_change)
2047 grid_wallet.addWidget(usechange_cb, 1, 0)
2048 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2049 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2051 gap_label = QLabel(_('Gap limit'))
2052 grid_wallet.addWidget(gap_label, 2, 0)
2053 gap_e = AmountEdit(None,True)
2054 gap_e.setText("%d"% self.wallet.gap_limit)
2055 grid_wallet.addWidget(gap_e, 2, 2)
2056 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2057 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2058 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2059 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2060 + _('Warning') + ': ' \
2061 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2062 + _('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'
2063 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2064 if not self.config.is_modifiable('gap_limit'):
2065 for w in [gap_e, gap_label]: w.setEnabled(False)
2067 units = ['BTC', 'mBTC']
2068 unit_label = QLabel(_('Base unit'))
2069 grid_wallet.addWidget(unit_label, 3, 0)
2070 unit_combo = QComboBox()
2071 unit_combo.addItems(units)
2072 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2073 grid_wallet.addWidget(unit_combo, 3, 2)
2074 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2075 + '\n1BTC=1000mBTC.\n' \
2076 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2077 grid_wallet.setRowStretch(4,1)
2082 grid_io = QGridLayout(tab3)
2083 grid_io.setColumnStretch(0,1)
2084 tabs.addTab(tab3, _('Import/Export') )
2086 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2087 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2088 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2089 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2091 grid_io.addWidget(QLabel(_('History')), 2, 0)
2092 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2093 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2095 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2097 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2098 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2099 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2101 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2102 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2103 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2104 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2105 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2108 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2109 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2110 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2111 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2113 grid_io.setRowStretch(6,1)
2118 tab5 = QScrollArea()
2119 tab5.setEnabled(True)
2120 tab5.setWidgetResizable(True)
2122 grid_plugins = QGridLayout()
2123 grid_plugins.setColumnStretch(0,1)
2126 w.setLayout(grid_plugins)
2128 tab5.setMaximumSize(tab3.size()) # optional
2130 w.setMinimumHeight(len(self.plugins)*35)
2132 tabs.addTab(tab5, _('Plugins') )
2133 def mk_toggle(cb, p):
2134 return lambda: cb.setChecked(p.toggle())
2135 for i, p in enumerate(self.plugins):
2137 name, description = p.get_info()
2138 cb = QCheckBox(name)
2139 cb.setDisabled(not p.is_available())
2140 cb.setChecked(p.is_enabled())
2141 cb.clicked.connect(mk_toggle(cb,p))
2142 grid_plugins.addWidget(cb, i, 0)
2143 if p.requires_settings():
2144 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2145 grid_plugins.addWidget(HelpButton(description), i, 2)
2147 print_msg("Error: cannot display plugin", p)
2148 traceback.print_exc(file=sys.stdout)
2149 grid_plugins.setRowStretch(i+1,1)
2151 self.run_hook('create_settings_tab', tabs)
2153 vbox.addLayout(ok_cancel_buttons(d))
2157 if not d.exec_(): return
2159 fee = unicode(fee_e.text())
2161 fee = self.read_amount(fee)
2163 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2166 self.wallet.set_fee(fee)
2168 nz = unicode(nz_e.text())
2173 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2176 if self.wallet.num_zeros != nz:
2177 self.wallet.num_zeros = nz
2178 self.config.set_key('num_zeros', nz, True)
2179 self.update_history_tab()
2180 self.update_receive_tab()
2182 usechange_result = usechange_cb.isChecked()
2183 if self.wallet.use_change != usechange_result:
2184 self.wallet.use_change = usechange_result
2185 self.config.set_key('use_change', self.wallet.use_change, True)
2187 unit_result = units[unit_combo.currentIndex()]
2188 if self.base_unit() != unit_result:
2189 self.decimal_point = 8 if unit_result == 'BTC' else 5
2190 self.config.set_key('decimal_point', self.decimal_point, True)
2191 self.update_history_tab()
2192 self.update_status()
2195 n = int(gap_e.text())
2197 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2200 if self.wallet.gap_limit != n:
2201 r = self.wallet.change_gap_limit(n)
2203 self.update_receive_tab()
2204 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2206 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2208 need_restart = False
2210 lang_request = languages.keys()[lang_combo.currentIndex()]
2211 if lang_request != self.config.get('language'):
2212 self.config.set_key("language", lang_request, True)
2215 cur_request = str(currencies[cur_combo.currentIndex()])
2216 if cur_request != self.config.get('currency', "None"):
2217 self.config.set_key('currency', cur_request, True)
2218 self.update_wallet()
2220 self.run_hook('close_settings_dialog')
2223 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2225 self.receive_tab_set_mode(expert_cb.isChecked())
2227 def run_network_dialog(self):
2228 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2230 def closeEvent(self, event):
2232 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2233 self.save_column_widths()
2234 self.config.set_key("console-history",self.console.history[-50:])
2243 def __init__(self, wallet, config, app=None):
2244 self.wallet = wallet
2245 self.config = config
2247 self.app = QApplication(sys.argv)
2250 def restore_or_create(self):
2251 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2252 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2253 if r==2: return None
2254 return 'restore' if r==1 else 'create'
2257 def verify_seed(self):
2258 r = self.seed_dialog(False)
2259 if r != self.wallet.seed:
2260 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2267 def seed_dialog(self, is_restore=True):
2271 vbox = QVBoxLayout()
2273 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2275 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2277 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2280 label.setWordWrap(True)
2281 vbox.addWidget(label)
2283 seed_e = QTextEdit()
2284 seed_e.setMaximumHeight(100)
2285 vbox.addWidget(seed_e)
2288 grid = QGridLayout()
2290 gap_e = AmountEdit(None, True)
2292 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2293 grid.addWidget(gap_e, 2, 1)
2294 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2295 vbox.addLayout(grid)
2297 vbox.addLayout(ok_cancel_buttons(d))
2300 if not d.exec_(): return
2303 seed = str(seed_e.toPlainText())
2307 seed = mnemonic.mn_decode( seed.split() )
2309 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2313 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2320 gap = int(unicode(gap_e.text()))
2322 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2327 def network_dialog(self):
2328 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2331 def show_seed(self):
2332 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2334 def password_dialog(self):
2335 if self.wallet.seed:
2336 ElectrumWindow.change_password_dialog(self.wallet)
2339 def restore_wallet(self):
2340 wallet = self.wallet
2341 # wait until we are connected, because the user might have selected another server
2342 if not wallet.interface.is_connected:
2343 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2344 waiting_dialog(waiting)
2346 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2347 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2349 wallet.set_up_to_date(False)
2350 wallet.interface.poke('synchronizer')
2351 waiting_dialog(waiting)
2352 if wallet.is_found():
2353 print_error( "Recovery successful" )
2355 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2362 w = ElectrumWindow(self.wallet, self.config)
2363 if url: w.set_url(url)