3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
31 from PyQt4.QtGui import *
32 from PyQt4.QtCore import *
33 import PyQt4.QtCore as QtCore
35 from electrum.bitcoin import MIN_RELAY_TX_FEE
40 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
42 from electrum.wallet import format_satoshis
43 from electrum.bitcoin import Transaction, is_valid
44 from electrum import mnemonic
45 from electrum import util, bitcoin, commands
47 import bmp, pyqrnative
50 from amountedit import AmountEdit
51 from network_dialog import NetworkDialog
52 from qrcodewidget import QRCodeWidget
54 from decimal import Decimal
62 if platform.system() == 'Windows':
63 MONOSPACE_FONT = 'Lucida Console'
64 elif platform.system() == 'Darwin':
65 MONOSPACE_FONT = 'Monaco'
67 MONOSPACE_FONT = 'monospace'
69 from electrum import ELECTRUM_VERSION
74 class UpdateLabel(QLabel):
75 def __init__(self, config, parent=None):
76 QLabel.__init__(self, parent)
77 self.new_version = False
80 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
81 con.request("GET", "/version")
82 res = con.getresponse()
83 except socket.error as msg:
84 print_error("Could not retrieve version information")
88 self.latest_version = res.read()
89 self.latest_version = self.latest_version.replace("\n","")
90 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
92 self.current_version = ELECTRUM_VERSION
93 if(self.compare_versions(self.latest_version, self.current_version) == 1):
94 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
95 if(self.compare_versions(self.latest_version, latest_seen) == 1):
96 self.new_version = True
97 self.setText(_("New version available") + ": " + self.latest_version)
100 def compare_versions(self, version1, version2):
102 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
103 return cmp(normalize(version1), normalize(version2))
105 def ignore_this_version(self):
107 self.config.set_key("last_seen_version", self.latest_version, True)
108 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
111 def ignore_all_version(self):
113 self.config.set_key("last_seen_version", "9.9.9", True)
114 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
117 def open_website(self):
118 webbrowser.open("http://electrum.org/download.html")
121 def mouseReleaseEvent(self, event):
122 dialog = QDialog(self)
123 dialog.setWindowTitle(_('Electrum update'))
126 main_layout = QGridLayout()
127 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
129 ignore_version = QPushButton(_("Ignore this version"))
130 ignore_version.clicked.connect(self.ignore_this_version)
132 ignore_all_versions = QPushButton(_("Ignore all versions"))
133 ignore_all_versions.clicked.connect(self.ignore_all_version)
135 open_website = QPushButton(_("Goto download page"))
136 open_website.clicked.connect(self.open_website)
138 main_layout.addWidget(ignore_version, 1, 0)
139 main_layout.addWidget(ignore_all_versions, 1, 1)
140 main_layout.addWidget(open_website, 1, 2)
142 dialog.setLayout(main_layout)
146 if not dialog.exec_(): return
150 class Timer(QtCore.QThread):
153 self.emit(QtCore.SIGNAL('timersignal'))
156 class HelpButton(QPushButton):
157 def __init__(self, text):
158 QPushButton.__init__(self, '?')
159 self.setFocusPolicy(Qt.NoFocus)
160 self.setFixedWidth(20)
161 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
164 class EnterButton(QPushButton):
165 def __init__(self, text, func):
166 QPushButton.__init__(self, text)
168 self.clicked.connect(func)
170 def keyPressEvent(self, e):
171 if e.key() == QtCore.Qt.Key_Return:
174 class MyTreeWidget(QTreeWidget):
175 def __init__(self, parent):
176 QTreeWidget.__init__(self, parent)
179 for i in range(0,self.viewport().height()/5):
180 if self.itemAt(QPoint(0,i*5)) == item:
184 for j in range(0,30):
185 if self.itemAt(QPoint(0,i*5 + j)) != item:
187 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
189 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
194 class StatusBarButton(QPushButton):
195 def __init__(self, icon, tooltip, func):
196 QPushButton.__init__(self, icon, '')
197 self.setToolTip(tooltip)
199 self.setMaximumWidth(25)
200 self.clicked.connect(func)
203 def keyPressEvent(self, e):
204 if e.key() == QtCore.Qt.Key_Return:
211 def waiting_dialog(f):
217 w.setWindowTitle('Electrum')
227 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
236 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
238 class ElectrumWindow(QMainWindow):
240 def __init__(self, wallet, config):
241 QMainWindow.__init__(self)
245 self.current_account = self.config.get("current_account", None)
247 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
248 self.notifier = QSystemTrayIcon(self.icon, self)
249 self.notifier.setToolTip('Electrum')
253 self.create_status_bar()
255 self.need_update = threading.Event()
256 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
257 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
258 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
259 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
260 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
262 self.expert_mode = config.get('classic_expert_mode', False)
263 self.decimal_point = config.get('decimal_point', 8)
265 set_language(config.get('language'))
267 self.funds_error = False
268 self.completions = QStringListModel()
270 self.tabs = tabs = QTabWidget(self)
271 self.column_widths = self.config.get("column_widths", default_column_widths )
272 tabs.addTab(self.create_history_tab(), _('History') )
273 tabs.addTab(self.create_send_tab(), _('Send') )
274 tabs.addTab(self.create_receive_tab(), _('Receive') )
275 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
276 tabs.addTab(self.create_console_tab(), _('Console') )
277 tabs.setMinimumSize(600, 400)
278 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
279 self.setCentralWidget(tabs)
281 g = self.config.get("winpos-qt",[100, 100, 840, 400])
282 self.setGeometry(g[0], g[1], g[2], g[3])
283 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
284 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
285 self.setWindowTitle( title )
289 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
290 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
291 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
292 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
293 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
295 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
296 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
297 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
298 self.history_list.setFocus(True)
300 self.exchanger = exchange_rate.Exchanger(self)
301 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
303 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
304 if platform.system() == 'Windows':
305 n = 3 if self.wallet.seed else 2
306 tabs.setCurrentIndex (n)
307 tabs.setCurrentIndex (0)
310 if self.wallet.fee < 50000:
311 self.wallet.set_fee(50000)
312 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
314 # set initial message
315 self.console.showMessage(self.wallet.interface.banner)
317 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
318 self.notify_transactions()
320 # plugins that need to change the GUI do it here
321 self.run_hook('init_gui')
324 def select_wallet_file(self):
325 wallet_folder = self.wallet.config.path
326 re.sub("(\/\w*.dat)$", "", wallet_folder)
327 file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
331 self.load_wallet(file_name)
334 def init_menubar(self):
337 electrum_menu = menubar.addMenu(_("&File"))
338 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
339 open_wallet_action.triggered.connect(self.select_wallet_file)
341 preferences_name = _("Preferences")
342 if sys.platform == 'darwin':
343 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
345 preferences_menu = electrum_menu.addAction(preferences_name)
346 preferences_menu.triggered.connect(self.settings_dialog)
347 electrum_menu.addSeparator()
349 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
351 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
352 raw_transaction_file.triggered.connect(self.do_process_from_file)
354 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
355 raw_transaction_text.triggered.connect(self.do_process_from_text)
357 electrum_menu.addSeparator()
358 quit_item = electrum_menu.addAction(_("&Close"))
359 quit_item.triggered.connect(self.close)
361 wallet_menu = menubar.addMenu(_("&Wallet"))
362 wallet_backup = wallet_menu.addAction(_("&Create backup"))
363 wallet_backup.triggered.connect(backup_wallet)
365 show_menu = wallet_menu.addMenu(_("Show"))
368 show_seed = show_menu.addAction(_("&Seed"))
369 show_seed.triggered.connect(self.show_seed_dialog)
371 show_mpk = show_menu.addAction(_("&Master Public Key"))
372 show_mpk.triggered.connect(self.show_master_public_key)
374 wallet_menu.addSeparator()
375 new_contact = wallet_menu.addAction(_("&New contact"))
376 new_contact.triggered.connect(self.new_contact_dialog)
378 import_menu = menubar.addMenu(_("&Import"))
379 in_labels = import_menu.addAction(_("&Labels"))
380 in_labels.triggered.connect(self.do_import_labels)
382 in_private_keys = import_menu.addAction(_("&Private keys"))
383 in_private_keys.triggered.connect(self.do_import_privkey)
385 export_menu = menubar.addMenu(_("&Export"))
386 ex_private_keys = export_menu.addAction(_("&Private keys"))
387 ex_private_keys.triggered.connect(self.do_export_privkeys)
389 ex_history = export_menu.addAction(_("&History"))
390 ex_history.triggered.connect(self.do_export_history)
392 ex_labels = export_menu.addAction(_("&Labels"))
393 ex_labels.triggered.connect(self.do_export_labels)
395 help_menu = menubar.addMenu(_("&Help"))
396 doc_open = help_menu.addAction(_("&Documentation"))
397 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
398 web_open = help_menu.addAction(_("&Official website"))
399 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
401 self.setMenuBar(menubar)
403 def load_wallet(self, filename):
406 config = electrum.SimpleConfig({'wallet_path': filename})
407 if not config.wallet_file_exists:
408 self.show_message("file not found "+ filename)
411 #self.wallet.verifier.stop()
412 interface = self.wallet.interface
413 verifier = self.wallet.verifier
414 self.wallet.synchronizer.stop()
417 self.wallet = electrum.Wallet(self.config)
418 self.wallet.interface = interface
419 self.wallet.verifier = verifier
421 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
426 def notify_transactions(self):
427 print_error("Notifying GUI")
428 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
429 # Combine the transactions if there are more then three
430 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
433 for tx in self.wallet.interface.pending_transactions_for_notifications:
434 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
438 self.notify("%s new transactions received. Total amount received in the new transactions %s BTC" % (tx_amount, self.format_amount(total_amount)))
440 self.wallet.interface.pending_transactions_for_notifications = []
442 for tx in self.wallet.interface.pending_transactions_for_notifications:
444 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
445 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
447 self.notify("New transaction received. %s BTC" % (self.format_amount(v)))
449 def notify(self, message):
450 self.notifier.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
453 def init_plugins(self):
454 import imp, pkgutil, __builtin__
455 if __builtin__.use_local_modules:
456 fp, pathname, description = imp.find_module('plugins')
457 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
458 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
459 imp.load_module('electrum_plugins', fp, pathname, description)
460 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
462 import electrum_plugins
463 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
464 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
469 self.plugins.append( p.Plugin(self) )
471 print_msg("Error:cannot initialize plugin",p)
472 traceback.print_exc(file=sys.stdout)
475 def run_hook(self, name, *args):
476 for p in self.plugins:
477 if not p.is_enabled():
486 print_error("Plugin error")
487 traceback.print_exc(file=sys.stdout)
492 def set_label(self, name, text = None):
494 old_text = self.wallet.labels.get(name)
497 self.wallet.labels[name] = text
498 self.wallet.config.set_key('labels', self.wallet.labels)
502 self.wallet.labels.pop(name)
504 self.run_hook('set_label', name, text, changed)
508 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
509 def getOpenFileName(self, title, filter = None):
510 directory = self.config.get('io_dir', os.path.expanduser('~'))
511 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
512 if fileName and directory != os.path.dirname(fileName):
513 self.config.set_key('io_dir', os.path.dirname(fileName), True)
516 def getSaveFileName(self, title, filename, filter = None):
517 directory = self.config.get('io_dir', os.path.expanduser('~'))
518 path = os.path.join( directory, filename )
519 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
520 if fileName and directory != os.path.dirname(fileName):
521 self.config.set_key('io_dir', os.path.dirname(fileName), True)
527 QMainWindow.close(self)
528 self.run_hook('close_main_window')
530 def connect_slots(self, sender):
531 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
532 self.previous_payto_e=''
534 def timer_actions(self):
535 if self.need_update.is_set():
537 self.need_update.clear()
538 self.run_hook('timer_actions')
540 def format_amount(self, x, is_diff=False, whitespaces=False):
541 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
543 def read_amount(self, x):
544 if x in['.', '']: return None
545 p = pow(10, self.decimal_point)
546 return int( p * Decimal(x) )
549 assert self.decimal_point in [5,8]
550 return "BTC" if self.decimal_point == 8 else "mBTC"
552 def update_status(self):
553 if self.wallet.interface and self.wallet.interface.is_connected:
554 if not self.wallet.up_to_date:
555 text = _("Synchronizing...")
556 icon = QIcon(":icons/status_waiting.png")
558 c, u = self.wallet.get_account_balance(self.current_account)
559 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
560 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
561 text += self.create_quote_text(Decimal(c+u)/100000000)
562 icon = QIcon(":icons/status_connected.png")
564 text = _("Not connected")
565 icon = QIcon(":icons/status_disconnected.png")
567 self.balance_label.setText(text)
568 self.status_button.setIcon( icon )
570 def update_wallet(self):
572 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
573 self.update_history_tab()
574 self.update_receive_tab()
575 self.update_contacts_tab()
576 self.update_completions()
579 def create_quote_text(self, btc_balance):
580 quote_currency = self.config.get("currency", "None")
581 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
582 if quote_balance is None:
585 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
588 def create_history_tab(self):
589 self.history_list = l = MyTreeWidget(self)
591 for i,width in enumerate(self.column_widths['history']):
592 l.setColumnWidth(i, width)
593 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
594 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
595 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
597 l.setContextMenuPolicy(Qt.CustomContextMenu)
598 l.customContextMenuRequested.connect(self.create_history_menu)
602 def create_history_menu(self, position):
603 self.history_list.selectedIndexes()
604 item = self.history_list.currentItem()
606 tx_hash = str(item.data(0, Qt.UserRole).toString())
607 if not tx_hash: return
609 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
610 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
611 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
612 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
615 def show_tx_details(self, tx):
616 dialog = QDialog(self)
618 dialog.setWindowTitle(_("Transaction Details"))
620 dialog.setLayout(vbox)
621 dialog.setMinimumSize(600,300)
624 if tx_hash in self.wallet.transactions.keys():
625 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
626 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
628 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
634 vbox.addWidget(QLabel("Transaction ID:"))
635 e = QLineEdit(tx_hash)
639 vbox.addWidget(QLabel("Date: %s"%time_str))
640 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
643 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
644 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
646 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
647 vbox.addWidget(QLabel("Transaction fee: unknown"))
649 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
651 vbox.addWidget( self.generate_transaction_information_widget(tx) )
653 ok_button = QPushButton(_("Close"))
654 ok_button.setDefault(True)
655 ok_button.clicked.connect(dialog.accept)
659 hbox.addWidget(ok_button)
663 def tx_label_clicked(self, item, column):
664 if column==2 and item.isSelected():
666 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
667 self.history_list.editItem( item, column )
668 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
671 def tx_label_changed(self, item, column):
675 tx_hash = str(item.data(0, Qt.UserRole).toString())
676 tx = self.wallet.transactions.get(tx_hash)
677 text = unicode( item.text(2) )
678 self.set_label(tx_hash, text)
680 item.setForeground(2, QBrush(QColor('black')))
682 text = self.wallet.get_default_label(tx_hash)
683 item.setText(2, text)
684 item.setForeground(2, QBrush(QColor('gray')))
688 def edit_label(self, is_recv):
689 l = self.receive_list if is_recv else self.contacts_list
690 item = l.currentItem()
691 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
692 l.editItem( item, 1 )
693 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
697 def address_label_clicked(self, item, column, l, column_addr, column_label):
698 if column == column_label and item.isSelected():
699 is_editable = item.data(0, 32).toBool()
702 addr = unicode( item.text(column_addr) )
703 label = unicode( item.text(column_label) )
704 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
705 l.editItem( item, column )
706 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
709 def address_label_changed(self, item, column, l, column_addr, column_label):
710 if column == column_label:
711 addr = unicode( item.text(column_addr) )
712 text = unicode( item.text(column_label) )
713 is_editable = item.data(0, 32).toBool()
717 changed = self.set_label(addr, text)
719 self.update_history_tab()
720 self.update_completions()
722 self.current_item_changed(item)
724 self.run_hook('item_changed', item, column)
727 def current_item_changed(self, a):
728 self.run_hook('current_item_changed', a)
732 def update_history_tab(self):
734 self.history_list.clear()
735 for item in self.wallet.get_tx_history(self.current_account):
736 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
739 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
744 time_str = 'unverified'
745 icon = QIcon(":icons/unconfirmed.png")
748 icon = QIcon(":icons/unconfirmed.png")
750 icon = QIcon(":icons/clock%d.png"%conf)
752 icon = QIcon(":icons/confirmed.png")
754 if value is not None:
755 v_str = self.format_amount(value, True, whitespaces=True)
759 balance_str = self.format_amount(balance, whitespaces=True)
762 label, is_default_label = self.wallet.get_label(tx_hash)
764 label = _('Pruned transaction outputs')
765 is_default_label = False
767 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
768 item.setFont(2, QFont(MONOSPACE_FONT))
769 item.setFont(3, QFont(MONOSPACE_FONT))
770 item.setFont(4, QFont(MONOSPACE_FONT))
772 item.setForeground(3, QBrush(QColor("#BC1E1E")))
774 item.setData(0, Qt.UserRole, tx_hash)
775 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
777 item.setForeground(2, QBrush(QColor('grey')))
779 item.setIcon(0, icon)
780 self.history_list.insertTopLevelItem(0,item)
783 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
786 def create_send_tab(self):
791 grid.setColumnMinimumWidth(3,300)
792 grid.setColumnStretch(5,1)
795 self.payto_e = QLineEdit()
796 grid.addWidget(QLabel(_('Pay to')), 1, 0)
797 grid.addWidget(self.payto_e, 1, 1, 1, 3)
799 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)
801 completer = QCompleter()
802 completer.setCaseSensitivity(False)
803 self.payto_e.setCompleter(completer)
804 completer.setModel(self.completions)
806 self.message_e = QLineEdit()
807 grid.addWidget(QLabel(_('Description')), 2, 0)
808 grid.addWidget(self.message_e, 2, 1, 1, 3)
809 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)
811 self.amount_e = AmountEdit(self.base_unit)
812 grid.addWidget(QLabel(_('Amount')), 3, 0)
813 grid.addWidget(self.amount_e, 3, 1, 1, 2)
814 grid.addWidget(HelpButton(
815 _('Amount to be sent.') + '\n\n' \
816 + _('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.') \
817 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
819 self.fee_e = AmountEdit(self.base_unit)
820 grid.addWidget(QLabel(_('Fee')), 4, 0)
821 grid.addWidget(self.fee_e, 4, 1, 1, 2)
822 grid.addWidget(HelpButton(
823 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
824 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
825 + _('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)
828 b = EnterButton(_("Send"), self.do_send)
830 b = EnterButton(_("Create unsigned transaction"), self.do_send)
831 grid.addWidget(b, 6, 1)
833 b = EnterButton(_("Clear"),self.do_clear)
834 grid.addWidget(b, 6, 2)
836 self.payto_sig = QLabel('')
837 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
839 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
840 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
849 def entry_changed( is_fee ):
850 self.funds_error = False
852 if self.amount_e.is_shortcut:
853 self.amount_e.is_shortcut = False
854 c, u = self.wallet.get_account_balance(self.current_account)
855 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
856 fee = self.wallet.estimated_fee(inputs)
858 self.amount_e.setText( self.format_amount(amount) )
859 self.fee_e.setText( self.format_amount( fee ) )
862 amount = self.read_amount(str(self.amount_e.text()))
863 fee = self.read_amount(str(self.fee_e.text()))
865 if not is_fee: fee = None
868 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
870 self.fee_e.setText( self.format_amount( fee ) )
873 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
877 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
878 self.funds_error = True
879 text = _( "Not enough funds" )
880 c, u = self.wallet.get_frozen_balance()
881 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
883 self.statusBar().showMessage(text)
884 self.amount_e.setPalette(palette)
885 self.fee_e.setPalette(palette)
887 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
888 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
890 self.run_hook('create_send_tab', grid)
894 def update_completions(self):
896 for addr,label in self.wallet.labels.items():
897 if addr in self.wallet.addressbook:
898 l.append( label + ' <' + addr + '>')
900 self.run_hook('update_completions', l)
901 self.completions.setStringList(l)
905 return lambda s, *args: s.do_protect(func, args)
910 label = unicode( self.message_e.text() )
911 r = unicode( self.payto_e.text() )
914 # label or alias, with address in brackets
915 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
916 to_address = m.group(2) if m else r
918 if not is_valid(to_address):
919 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
923 amount = self.read_amount(unicode( self.amount_e.text()))
925 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
928 fee = self.read_amount(unicode( self.fee_e.text()))
930 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
933 confirm_amount = self.config.get('confirm_amount', 100000000)
934 if amount >= confirm_amount:
935 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
938 self.send_tx(to_address, amount, fee, label)
942 def send_tx(self, to_address, amount, fee, label, password):
945 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
946 except BaseException, e:
947 self.show_message(str(e))
950 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
951 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
954 self.run_hook('send_tx', tx)
957 self.set_label(tx.hash(), label)
960 h = self.wallet.send_tx(tx)
961 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
962 status, msg = self.wallet.receive_tx( h )
964 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
966 self.update_contacts_tab()
968 QMessageBox.warning(self, _('Error'), msg, _('OK'))
970 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
972 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
973 with open(fileName,'w') as f:
974 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
975 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
977 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
982 def set_url(self, url):
983 address, amount, label, message, signature, identity, url = util.parse_url(url)
984 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
986 if label and self.wallet.labels.get(address) != label:
987 if self.question('Give label "%s" to address %s ?'%(label,address)):
988 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
989 self.wallet.addressbook.append(address)
990 self.set_label(address, label)
992 self.run_hook('set_url', url, self.show_message, self.question)
994 self.tabs.setCurrentIndex(1)
995 label = self.wallet.labels.get(address)
996 m_addr = label + ' <'+ address +'>' if label else address
997 self.payto_e.setText(m_addr)
999 self.message_e.setText(message)
1000 self.amount_e.setText(amount)
1002 self.set_frozen(self.payto_e,True)
1003 self.set_frozen(self.amount_e,True)
1004 self.set_frozen(self.message_e,True)
1005 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1007 self.payto_sig.setVisible(False)
1010 self.payto_sig.setVisible(False)
1011 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1013 self.set_frozen(e,False)
1014 self.update_status()
1016 def set_frozen(self,entry,frozen):
1018 entry.setReadOnly(True)
1019 entry.setFrame(False)
1020 palette = QPalette()
1021 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1022 entry.setPalette(palette)
1024 entry.setReadOnly(False)
1025 entry.setFrame(True)
1026 palette = QPalette()
1027 palette.setColor(entry.backgroundRole(), QColor('white'))
1028 entry.setPalette(palette)
1031 def toggle_freeze(self,addr):
1033 if addr in self.wallet.frozen_addresses:
1034 self.wallet.unfreeze(addr)
1036 self.wallet.freeze(addr)
1037 self.update_receive_tab()
1039 def toggle_priority(self,addr):
1041 if addr in self.wallet.prioritized_addresses:
1042 self.wallet.unprioritize(addr)
1044 self.wallet.prioritize(addr)
1045 self.update_receive_tab()
1048 def create_list_tab(self, headers):
1049 "generic tab creation method"
1050 l = MyTreeWidget(self)
1051 l.setColumnCount( len(headers) )
1052 l.setHeaderLabels( headers )
1055 vbox = QVBoxLayout()
1062 vbox.addWidget(buttons)
1064 hbox = QHBoxLayout()
1067 buttons.setLayout(hbox)
1072 def create_receive_tab(self):
1073 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1074 l.setContextMenuPolicy(Qt.CustomContextMenu)
1075 l.customContextMenuRequested.connect(self.create_receive_menu)
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.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1079 self.receive_list = l
1080 self.receive_buttons_hbox = hbox
1085 def receive_tab_set_mode(self, i):
1086 self.save_column_widths()
1087 self.expert_mode = (i == 1)
1088 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1089 self.update_receive_tab()
1092 def save_column_widths(self):
1093 if not self.expert_mode:
1094 widths = [ self.receive_list.columnWidth(0) ]
1097 for i in range(self.receive_list.columnCount() -1):
1098 widths.append(self.receive_list.columnWidth(i))
1099 self.column_widths["receive"][self.expert_mode] = widths
1101 self.column_widths["history"] = []
1102 for i in range(self.history_list.columnCount() - 1):
1103 self.column_widths["history"].append(self.history_list.columnWidth(i))
1105 self.column_widths["contacts"] = []
1106 for i in range(self.contacts_list.columnCount() - 1):
1107 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1109 self.config.set_key("column_widths", self.column_widths, True)
1112 def create_contacts_tab(self):
1113 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1114 l.setContextMenuPolicy(Qt.CustomContextMenu)
1115 l.customContextMenuRequested.connect(self.create_contact_menu)
1116 for i,width in enumerate(self.column_widths['contacts']):
1117 l.setColumnWidth(i, width)
1119 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1120 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1121 self.contacts_list = l
1122 self.contacts_buttons_hbox = hbox
1127 def delete_imported_key(self, addr):
1128 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1129 self.wallet.delete_imported_key(addr)
1130 self.update_receive_tab()
1131 self.update_history_tab()
1134 def create_receive_menu(self, position):
1135 # fixme: this function apparently has a side effect.
1136 # if it is not called the menu pops up several times
1137 #self.receive_list.selectedIndexes()
1139 item = self.receive_list.itemAt(position)
1141 addr = unicode(item.text(0))
1142 if not is_valid(addr):
1143 item.setExpanded(not item.isExpanded())
1146 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1147 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1148 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1149 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1150 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1151 if addr in self.wallet.imported_keys:
1152 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1154 if self.expert_mode:
1155 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1156 menu.addAction(t, lambda: self.toggle_freeze(addr))
1157 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1158 menu.addAction(t, lambda: self.toggle_priority(addr))
1160 self.run_hook('receive_menu', menu)
1161 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1164 def payto(self, addr):
1166 label = self.wallet.labels.get(addr)
1167 m_addr = label + ' <' + addr + '>' if label else addr
1168 self.tabs.setCurrentIndex(1)
1169 self.payto_e.setText(m_addr)
1170 self.amount_e.setFocus()
1173 def delete_contact(self, x):
1174 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1175 self.wallet.delete_contact(x)
1176 self.set_label(x, None)
1177 self.update_history_tab()
1178 self.update_contacts_tab()
1179 self.update_completions()
1182 def create_contact_menu(self, position):
1183 item = self.contacts_list.itemAt(position)
1185 addr = unicode(item.text(0))
1186 label = unicode(item.text(1))
1187 is_editable = item.data(0,32).toBool()
1188 payto_addr = item.data(0,33).toString()
1190 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1191 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1192 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1194 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1195 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1197 self.run_hook('create_contact_menu', menu, item)
1198 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1201 def update_receive_item(self, item):
1202 item.setFont(0, QFont(MONOSPACE_FONT))
1203 address = str(item.data(0,0).toString())
1204 label = self.wallet.labels.get(address,'')
1205 item.setData(1,0,label)
1206 item.setData(0,32, True) # is editable
1208 self.run_hook('update_receive_item', address, item)
1210 c, u = self.wallet.get_addr_balance(address)
1211 balance = self.format_amount(c + u)
1212 item.setData(2,0,balance)
1214 if self.expert_mode:
1215 if address in self.wallet.frozen_addresses:
1216 item.setBackgroundColor(0, QColor('lightblue'))
1217 elif address in self.wallet.prioritized_addresses:
1218 item.setBackgroundColor(0, QColor('lightgreen'))
1221 def update_receive_tab(self):
1222 l = self.receive_list
1225 l.setColumnHidden(2, not self.expert_mode)
1226 l.setColumnHidden(3, not self.expert_mode)
1227 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1228 l.setColumnWidth(i, width)
1230 if self.current_account is None:
1231 account_items = self.wallet.accounts.items()
1232 elif self.current_account != -1:
1233 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1237 for k, account in account_items:
1238 name = account.get('name',str(k))
1239 c,u = self.wallet.get_account_balance(k)
1240 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1241 l.addTopLevelItem(account_item)
1242 account_item.setExpanded(True)
1244 for is_change in ([0,1] if self.expert_mode else [0]):
1245 if self.expert_mode:
1246 name = "Receiving" if not is_change else "Change"
1247 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1248 account_item.addChild(seq_item)
1249 if not is_change: seq_item.setExpanded(True)
1251 seq_item = account_item
1255 for address in account[is_change]:
1256 h = self.wallet.history.get(address,[])
1260 if gap > self.wallet.gap_limit:
1265 num_tx = '*' if h == ['*'] else "%d"%len(h)
1266 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1267 self.update_receive_item(item)
1269 item.setBackgroundColor(1, QColor('red'))
1270 seq_item.addChild(item)
1273 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1274 c,u = self.wallet.get_imported_balance()
1275 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1276 l.addTopLevelItem(account_item)
1277 account_item.setExpanded(True)
1278 for address in self.wallet.imported_keys.keys():
1279 item = QTreeWidgetItem( [ address, '', '', ''] )
1280 self.update_receive_item(item)
1281 account_item.addChild(item)
1284 # we use column 1 because column 0 may be hidden
1285 l.setCurrentItem(l.topLevelItem(0),1)
1288 def update_contacts_tab(self):
1289 l = self.contacts_list
1292 for address in self.wallet.addressbook:
1293 label = self.wallet.labels.get(address,'')
1294 n = self.wallet.get_num_tx(address)
1295 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1296 item.setFont(0, QFont(MONOSPACE_FONT))
1297 # 32 = label can be edited (bool)
1298 item.setData(0,32, True)
1300 item.setData(0,33, address)
1301 l.addTopLevelItem(item)
1303 self.run_hook('update_contacts_tab', l)
1304 l.setCurrentItem(l.topLevelItem(0))
1308 def create_console_tab(self):
1309 from qt_console import Console
1310 self.console = console = Console()
1311 self.console.history = self.config.get("console-history",[])
1312 self.console.history_index = len(self.console.history)
1314 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1315 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1317 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1319 def mkfunc(f, method):
1320 return lambda *args: apply( f, (method, args, self.password_dialog ))
1322 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1323 methods[m] = mkfunc(c._run, m)
1325 console.updateNamespace(methods)
1328 def change_account(self,s):
1329 if s == _("All accounts"):
1330 self.current_account = None
1332 accounts = self.wallet.get_accounts()
1333 for k, v in accounts.items():
1335 self.current_account = k
1336 self.update_history_tab()
1337 self.update_status()
1338 self.update_receive_tab()
1340 def create_status_bar(self):
1343 sb.setFixedHeight(35)
1344 qtVersion = qVersion()
1346 self.balance_label = QLabel("")
1347 sb.addWidget(self.balance_label)
1349 update_notification = UpdateLabel(self.config)
1350 if(update_notification.new_version):
1351 sb.addPermanentWidget(update_notification)
1353 accounts = self.wallet.get_accounts()
1354 if len(accounts) > 1:
1355 from_combo = QComboBox()
1356 from_combo.addItems([_("All accounts")] + accounts.values())
1357 from_combo.setCurrentIndex(0)
1358 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1359 sb.addPermanentWidget(from_combo)
1361 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1362 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1363 if self.wallet.seed:
1364 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1365 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1366 sb.addPermanentWidget( self.password_button )
1367 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1368 if self.wallet.seed:
1369 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1370 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1371 sb.addPermanentWidget( self.status_button )
1373 self.run_hook('create_status_bar', (sb,))
1375 self.setStatusBar(sb)
1379 self.config.set_key('gui', 'lite', True)
1382 self.lite.mini.show()
1384 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1385 self.lite.main(None)
1387 def new_contact_dialog(self):
1388 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1389 address = unicode(text)
1391 if is_valid(address):
1392 self.wallet.add_contact(address)
1393 self.update_contacts_tab()
1394 self.update_history_tab()
1395 self.update_completions()
1397 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1399 def show_master_public_key(self):
1400 dialog = QDialog(self)
1402 dialog.setWindowTitle(_("Master Public Key"))
1404 main_text = QTextEdit()
1405 main_text.setText(self.wallet.get_master_public_key())
1406 main_text.setReadOnly(True)
1407 main_text.setMaximumHeight(170)
1408 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1410 ok_button = QPushButton(_("OK"))
1411 ok_button.setDefault(True)
1412 ok_button.clicked.connect(dialog.accept)
1414 main_layout = QGridLayout()
1415 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1417 main_layout.addWidget(main_text, 1, 0)
1418 main_layout.addWidget(qrw, 1, 1 )
1420 vbox = QVBoxLayout()
1421 vbox.addLayout(main_layout)
1422 hbox = QHBoxLayout()
1424 hbox.addWidget(ok_button)
1425 vbox.addLayout(hbox)
1427 dialog.setLayout(vbox)
1432 def show_seed_dialog(self, password):
1433 if not self.wallet.seed:
1434 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1437 seed = self.wallet.decode_seed(password)
1439 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1441 self.show_seed(seed, self.wallet.imported_keys, self)
1445 def show_seed(self, seed, imported_keys, parent=None):
1446 dialog = QDialog(parent)
1448 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1450 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1452 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1454 seed_text = QTextEdit(brainwallet)
1455 seed_text.setReadOnly(True)
1456 seed_text.setMaximumHeight(130)
1458 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1459 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1460 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1461 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1463 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1464 label2 = QLabel(msg2)
1465 label2.setWordWrap(True)
1468 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1469 logo.setMaximumWidth(60)
1471 qrw = QRCodeWidget(seed)
1473 ok_button = QPushButton(_("OK"))
1474 ok_button.setDefault(True)
1475 ok_button.clicked.connect(dialog.accept)
1477 grid = QGridLayout()
1478 #main_layout.addWidget(logo, 0, 0)
1480 grid.addWidget(logo, 0, 0)
1481 grid.addWidget(label1, 0, 1)
1483 grid.addWidget(seed_text, 1, 0, 1, 2)
1485 grid.addWidget(qrw, 0, 2, 2, 1)
1487 vbox = QVBoxLayout()
1488 vbox.addLayout(grid)
1489 vbox.addWidget(label2)
1491 hbox = QHBoxLayout()
1493 hbox.addWidget(ok_button)
1494 vbox.addLayout(hbox)
1496 dialog.setLayout(vbox)
1499 def show_qrcode(self, data, title = "QR code"):
1503 d.setWindowTitle(title)
1504 d.setMinimumSize(270, 300)
1505 vbox = QVBoxLayout()
1506 qrw = QRCodeWidget(data)
1507 vbox.addWidget(qrw, 1)
1508 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1509 hbox = QHBoxLayout()
1513 filename = "qrcode.bmp"
1514 bmp.save_qrcode(qrw.qr, filename)
1515 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1517 b = QPushButton(_("Save"))
1519 b.clicked.connect(print_qr)
1521 b = QPushButton(_("Close"))
1523 b.clicked.connect(d.accept)
1526 vbox.addLayout(hbox)
1531 def do_protect(self, func, args):
1532 if self.wallet.use_encryption:
1533 password = self.password_dialog()
1539 if args != (False,):
1540 args = (self,) + args + (password,)
1542 args = (self,password)
1547 def show_private_key(self, address, password):
1548 if not address: return
1550 pk = self.wallet.get_private_key(address, password)
1551 except BaseException, e:
1552 self.show_message(str(e))
1554 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1558 def do_sign(self, address, message, signature, password):
1560 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1561 signature.setText(sig)
1562 except BaseException, e:
1563 self.show_message(str(e))
1565 def sign_message(self, address):
1566 if not address: return
1569 d.setWindowTitle(_('Sign Message'))
1570 d.setMinimumSize(410, 290)
1572 tab_widget = QTabWidget()
1574 layout = QGridLayout(tab)
1576 sign_address = QLineEdit()
1578 sign_address.setText(address)
1579 layout.addWidget(QLabel(_('Address')), 1, 0)
1580 layout.addWidget(sign_address, 1, 1)
1582 sign_message = QTextEdit()
1583 layout.addWidget(QLabel(_('Message')), 2, 0)
1584 layout.addWidget(sign_message, 2, 1)
1585 layout.setRowStretch(2,3)
1587 sign_signature = QTextEdit()
1588 layout.addWidget(QLabel(_('Signature')), 3, 0)
1589 layout.addWidget(sign_signature, 3, 1)
1590 layout.setRowStretch(3,1)
1593 hbox = QHBoxLayout()
1594 b = QPushButton(_("Sign"))
1596 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1597 b = QPushButton(_("Close"))
1598 b.clicked.connect(d.accept)
1600 layout.addLayout(hbox, 4, 1)
1601 tab_widget.addTab(tab, _("Sign"))
1605 layout = QGridLayout(tab)
1607 verify_address = QLineEdit()
1608 layout.addWidget(QLabel(_('Address')), 1, 0)
1609 layout.addWidget(verify_address, 1, 1)
1611 verify_message = QTextEdit()
1612 layout.addWidget(QLabel(_('Message')), 2, 0)
1613 layout.addWidget(verify_message, 2, 1)
1614 layout.setRowStretch(2,3)
1616 verify_signature = QTextEdit()
1617 layout.addWidget(QLabel(_('Signature')), 3, 0)
1618 layout.addWidget(verify_signature, 3, 1)
1619 layout.setRowStretch(3,1)
1622 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1623 self.show_message(_("Signature verified"))
1625 self.show_message(_("Error: wrong signature"))
1627 hbox = QHBoxLayout()
1628 b = QPushButton(_("Verify"))
1629 b.clicked.connect(do_verify)
1631 b = QPushButton(_("Close"))
1632 b.clicked.connect(d.accept)
1634 layout.addLayout(hbox, 4, 1)
1635 tab_widget.addTab(tab, _("Verify"))
1637 vbox = QVBoxLayout()
1638 vbox.addWidget(tab_widget)
1645 def question(self, msg):
1646 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1648 def show_message(self, msg):
1649 QMessageBox.information(self, _('Message'), msg, _('OK'))
1651 def password_dialog(self ):
1658 vbox = QVBoxLayout()
1659 msg = _('Please enter your password')
1660 vbox.addWidget(QLabel(msg))
1662 grid = QGridLayout()
1664 grid.addWidget(QLabel(_('Password')), 1, 0)
1665 grid.addWidget(pw, 1, 1)
1666 vbox.addLayout(grid)
1668 vbox.addLayout(ok_cancel_buttons(d))
1671 self.run_hook('password_dialog', pw, grid, 1)
1672 if not d.exec_(): return
1673 return unicode(pw.text())
1680 def change_password_dialog( wallet, parent=None ):
1683 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1691 new_pw = QLineEdit()
1692 new_pw.setEchoMode(2)
1693 conf_pw = QLineEdit()
1694 conf_pw.setEchoMode(2)
1696 vbox = QVBoxLayout()
1698 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1699 +_('To disable wallet encryption, enter an empty new password.')) \
1700 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1702 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1703 +_("Leave these fields empty if you want to disable encryption.")
1704 vbox.addWidget(QLabel(msg))
1706 grid = QGridLayout()
1709 if wallet.use_encryption:
1710 grid.addWidget(QLabel(_('Password')), 1, 0)
1711 grid.addWidget(pw, 1, 1)
1713 grid.addWidget(QLabel(_('New Password')), 2, 0)
1714 grid.addWidget(new_pw, 2, 1)
1716 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1717 grid.addWidget(conf_pw, 3, 1)
1718 vbox.addLayout(grid)
1720 vbox.addLayout(ok_cancel_buttons(d))
1723 if not d.exec_(): return
1725 password = unicode(pw.text()) if wallet.use_encryption else None
1726 new_password = unicode(new_pw.text())
1727 new_password2 = unicode(conf_pw.text())
1730 seed = wallet.decode_seed(password)
1732 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1735 if new_password != new_password2:
1736 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1737 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1740 wallet.update_password(seed, password, new_password)
1742 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1745 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1748 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1749 parent.password_button.setIcon( icon )
1753 def generate_transaction_information_widget(self, tx):
1754 tabs = QTabWidget(self)
1757 grid_ui = QGridLayout(tab1)
1758 grid_ui.setColumnStretch(0,1)
1759 tabs.addTab(tab1, _('Outputs') )
1761 tree_widget = MyTreeWidget(self)
1762 tree_widget.setColumnCount(2)
1763 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1764 tree_widget.setColumnWidth(0, 300)
1765 tree_widget.setColumnWidth(1, 50)
1767 for address, value in tx.outputs:
1768 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1769 tree_widget.addTopLevelItem(item)
1771 tree_widget.setMaximumHeight(100)
1773 grid_ui.addWidget(tree_widget)
1776 grid_ui = QGridLayout(tab2)
1777 grid_ui.setColumnStretch(0,1)
1778 tabs.addTab(tab2, _('Inputs') )
1780 tree_widget = MyTreeWidget(self)
1781 tree_widget.setColumnCount(2)
1782 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1784 for input_line in tx.inputs:
1785 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1786 tree_widget.addTopLevelItem(item)
1788 tree_widget.setMaximumHeight(100)
1790 grid_ui.addWidget(tree_widget)
1794 def tx_dict_from_text(self, txt):
1796 tx_dict = json.loads(str(txt))
1797 assert "hex" in tx_dict.keys()
1798 assert "complete" in tx_dict.keys()
1799 if not tx_dict["complete"]:
1800 assert "input_info" in tx_dict.keys()
1802 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1807 def read_tx_from_file(self):
1808 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1812 with open(fileName, "r") as f:
1813 file_content = f.read()
1814 except (ValueError, IOError, os.error), reason:
1815 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1817 return self.tx_dict_from_text(file_content)
1821 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1823 self.wallet.signrawtransaction(tx, input_info, [], password)
1825 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1827 with open(fileName, "w+") as f:
1828 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1829 self.show_message(_("Transaction saved successfully"))
1832 except BaseException, e:
1833 self.show_message(str(e))
1836 def send_raw_transaction(self, raw_tx, dialog = ""):
1837 result, result_message = self.wallet.sendtx( raw_tx )
1839 self.show_message("Transaction successfully sent: %s" % (result_message))
1843 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1845 def do_process_from_text(self):
1846 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1849 tx_dict = self.tx_dict_from_text(text)
1851 self.create_process_transaction_window(tx_dict)
1853 def do_process_from_file(self):
1854 tx_dict = self.read_tx_from_file()
1856 self.create_process_transaction_window(tx_dict)
1858 def create_process_transaction_window(self, tx_dict):
1859 tx = Transaction(tx_dict["hex"])
1861 dialog = QDialog(self)
1862 dialog.setMinimumWidth(500)
1863 dialog.setWindowTitle(_('Process raw transaction'))
1869 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1870 l.addWidget(QLabel(_("Actions")), 4,0)
1872 if tx_dict["complete"] == False:
1873 l.addWidget(QLabel(_("Unsigned")), 3,1)
1874 if self.wallet.seed :
1875 b = QPushButton("Sign transaction")
1876 input_info = json.loads(tx_dict["input_info"])
1877 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1878 l.addWidget(b, 4, 1)
1880 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1882 l.addWidget(QLabel(_("Signed")), 3,1)
1883 b = QPushButton("Broadcast transaction")
1884 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1887 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1888 cancelButton = QPushButton(_("Cancel"))
1889 cancelButton.clicked.connect(lambda: dialog.done(0))
1890 l.addWidget(cancelButton, 4,2)
1896 def do_export_privkeys(self, password):
1897 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.")))
1900 select_export = _('Select file to export your private keys to')
1901 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1903 with open(fileName, "w+") as csvfile:
1904 transaction = csv.writer(csvfile)
1905 transaction.writerow(["address", "private_key"])
1908 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1909 transaction.writerow(["%34s"%addr,pk])
1911 self.show_message(_("Private keys exported."))
1913 except (IOError, os.error), reason:
1914 export_error_label = _("Electrum was unable to produce a private key-export.")
1915 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1917 except BaseException, e:
1918 self.show_message(str(e))
1922 def do_import_labels(self):
1923 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1924 if not labelsFile: return
1926 f = open(labelsFile, 'r')
1929 for key, value in json.loads(data).items():
1930 self.wallet.labels[key] = value
1932 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1933 except (IOError, os.error), reason:
1934 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1937 def do_export_labels(self):
1938 labels = self.wallet.labels
1940 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1942 with open(fileName, 'w+') as f:
1943 json.dump(labels, f)
1944 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1945 except (IOError, os.error), reason:
1946 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1949 def do_export_history(self):
1950 from gui_lite import csv_transaction
1951 csv_transaction(self.wallet)
1955 def do_import_privkey(self, password):
1956 if not self.wallet.imported_keys:
1957 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1958 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1959 + _('Are you sure you understand what you are doing?'), 3, 4)
1962 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1965 text = str(text).split()
1970 addr = self.wallet.import_key(key, password)
1971 except BaseException as e:
1977 addrlist.append(addr)
1979 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1981 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1982 self.update_receive_tab()
1983 self.update_history_tab()
1986 def settings_dialog(self):
1988 d.setWindowTitle(_('Electrum Settings'))
1990 vbox = QVBoxLayout()
1992 tabs = QTabWidget(self)
1993 self.settings_tab = tabs
1994 vbox.addWidget(tabs)
1997 grid_ui = QGridLayout(tab1)
1998 grid_ui.setColumnStretch(0,1)
1999 tabs.addTab(tab1, _('Display') )
2001 nz_label = QLabel(_('Display zeros'))
2002 grid_ui.addWidget(nz_label, 0, 0)
2003 nz_e = AmountEdit(None,True)
2004 nz_e.setText("%d"% self.wallet.num_zeros)
2005 grid_ui.addWidget(nz_e, 0, 1)
2006 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2007 grid_ui.addWidget(HelpButton(msg), 0, 2)
2008 if not self.config.is_modifiable('num_zeros'):
2009 for w in [nz_e, nz_label]: w.setEnabled(False)
2011 lang_label=QLabel(_('Language') + ':')
2012 grid_ui.addWidget(lang_label, 1, 0)
2013 lang_combo = QComboBox()
2014 from i18n import languages
2015 lang_combo.addItems(languages.values())
2017 index = languages.keys().index(self.config.get("language",''))
2020 lang_combo.setCurrentIndex(index)
2021 grid_ui.addWidget(lang_combo, 1, 1)
2022 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2023 if not self.config.is_modifiable('language'):
2024 for w in [lang_combo, lang_label]: w.setEnabled(False)
2026 currencies = self.exchanger.get_currencies()
2027 currencies.insert(0, "None")
2029 cur_label=QLabel(_('Currency') + ':')
2030 grid_ui.addWidget(cur_label , 2, 0)
2031 cur_combo = QComboBox()
2032 cur_combo.addItems(currencies)
2034 index = currencies.index(self.config.get('currency', "None"))
2037 cur_combo.setCurrentIndex(index)
2038 grid_ui.addWidget(cur_combo, 2, 1)
2039 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2041 expert_cb = QCheckBox(_('Expert mode'))
2042 expert_cb.setChecked(self.expert_mode)
2043 grid_ui.addWidget(expert_cb, 3, 0)
2044 hh = _('In expert mode, your client will:') + '\n' \
2045 + _(' - Show change addresses in the Receive tab') + '\n' \
2046 + _(' - Display the balance of each address') + '\n' \
2047 + _(' - Add freeze/prioritize actions to addresses.')
2048 grid_ui.addWidget(HelpButton(hh), 3, 2)
2049 grid_ui.setRowStretch(4,1)
2053 grid_wallet = QGridLayout(tab2)
2054 grid_wallet.setColumnStretch(0,1)
2055 tabs.addTab(tab2, _('Wallet') )
2057 fee_label = QLabel(_('Transaction fee'))
2058 grid_wallet.addWidget(fee_label, 0, 0)
2059 fee_e = AmountEdit(self.base_unit)
2060 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2061 grid_wallet.addWidget(fee_e, 0, 2)
2062 msg = _('Fee per kilobyte of transaction.') + ' ' \
2063 + _('Recommended value') + ': ' + self.format_amount(50000)
2064 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2065 if not self.config.is_modifiable('fee_per_kb'):
2066 for w in [fee_e, fee_label]: w.setEnabled(False)
2068 usechange_cb = QCheckBox(_('Use change addresses'))
2069 usechange_cb.setChecked(self.wallet.use_change)
2070 grid_wallet.addWidget(usechange_cb, 1, 0)
2071 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2072 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2074 gap_label = QLabel(_('Gap limit'))
2075 grid_wallet.addWidget(gap_label, 2, 0)
2076 gap_e = AmountEdit(None,True)
2077 gap_e.setText("%d"% self.wallet.gap_limit)
2078 grid_wallet.addWidget(gap_e, 2, 2)
2079 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2080 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2081 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2082 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2083 + _('Warning') + ': ' \
2084 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2085 + _('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'
2086 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2087 if not self.config.is_modifiable('gap_limit'):
2088 for w in [gap_e, gap_label]: w.setEnabled(False)
2090 units = ['BTC', 'mBTC']
2091 unit_label = QLabel(_('Base unit'))
2092 grid_wallet.addWidget(unit_label, 3, 0)
2093 unit_combo = QComboBox()
2094 unit_combo.addItems(units)
2095 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2096 grid_wallet.addWidget(unit_combo, 3, 2)
2097 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2098 + '\n1BTC=1000mBTC.\n' \
2099 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2100 grid_wallet.setRowStretch(4,1)
2104 tab5 = QScrollArea()
2105 tab5.setEnabled(True)
2106 tab5.setWidgetResizable(True)
2108 grid_plugins = QGridLayout()
2109 grid_plugins.setColumnStretch(0,1)
2112 w.setLayout(grid_plugins)
2115 w.setMinimumHeight(len(self.plugins)*35)
2117 tabs.addTab(tab5, _('Plugins') )
2118 def mk_toggle(cb, p):
2119 return lambda: cb.setChecked(p.toggle())
2120 for i, p in enumerate(self.plugins):
2122 name, description = p.get_info()
2123 cb = QCheckBox(name)
2124 cb.setDisabled(not p.is_available())
2125 cb.setChecked(p.is_enabled())
2126 cb.clicked.connect(mk_toggle(cb,p))
2127 grid_plugins.addWidget(cb, i, 0)
2128 if p.requires_settings():
2129 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2130 grid_plugins.addWidget(HelpButton(description), i, 2)
2132 print_msg("Error: cannot display plugin", p)
2133 traceback.print_exc(file=sys.stdout)
2134 grid_plugins.setRowStretch(i+1,1)
2136 self.run_hook('create_settings_tab', tabs)
2138 vbox.addLayout(ok_cancel_buttons(d))
2142 if not d.exec_(): return
2144 fee = unicode(fee_e.text())
2146 fee = self.read_amount(fee)
2148 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2151 self.wallet.set_fee(fee)
2153 nz = unicode(nz_e.text())
2158 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2161 if self.wallet.num_zeros != nz:
2162 self.wallet.num_zeros = nz
2163 self.config.set_key('num_zeros', nz, True)
2164 self.update_history_tab()
2165 self.update_receive_tab()
2167 usechange_result = usechange_cb.isChecked()
2168 if self.wallet.use_change != usechange_result:
2169 self.wallet.use_change = usechange_result
2170 self.config.set_key('use_change', self.wallet.use_change, True)
2172 unit_result = units[unit_combo.currentIndex()]
2173 if self.base_unit() != unit_result:
2174 self.decimal_point = 8 if unit_result == 'BTC' else 5
2175 self.config.set_key('decimal_point', self.decimal_point, True)
2176 self.update_history_tab()
2177 self.update_status()
2180 n = int(gap_e.text())
2182 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2185 if self.wallet.gap_limit != n:
2186 r = self.wallet.change_gap_limit(n)
2188 self.update_receive_tab()
2189 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2191 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2193 need_restart = False
2195 lang_request = languages.keys()[lang_combo.currentIndex()]
2196 if lang_request != self.config.get('language'):
2197 self.config.set_key("language", lang_request, True)
2200 cur_request = str(currencies[cur_combo.currentIndex()])
2201 if cur_request != self.config.get('currency', "None"):
2202 self.config.set_key('currency', cur_request, True)
2203 self.update_wallet()
2205 self.run_hook('close_settings_dialog')
2208 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2210 self.receive_tab_set_mode(expert_cb.isChecked())
2212 def run_network_dialog(self):
2213 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2215 def closeEvent(self, event):
2217 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2218 self.save_column_widths()
2219 self.config.set_key("console-history",self.console.history[-50:])
2228 def __init__(self, wallet, config, app=None):
2229 self.wallet = wallet
2230 self.config = config
2232 self.app = QApplication(sys.argv)
2235 def restore_or_create(self):
2236 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2237 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2238 if r==2: return None
2239 return 'restore' if r==1 else 'create'
2242 def verify_seed(self):
2243 r = self.seed_dialog(False)
2244 if r != self.wallet.seed:
2245 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2252 def seed_dialog(self, is_restore=True):
2256 vbox = QVBoxLayout()
2258 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2260 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2262 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2265 label.setWordWrap(True)
2266 vbox.addWidget(label)
2268 seed_e = QTextEdit()
2269 seed_e.setMaximumHeight(100)
2270 vbox.addWidget(seed_e)
2273 grid = QGridLayout()
2275 gap_e = AmountEdit(None, True)
2277 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2278 grid.addWidget(gap_e, 2, 1)
2279 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2280 vbox.addLayout(grid)
2282 vbox.addLayout(ok_cancel_buttons(d))
2285 if not d.exec_(): return
2288 seed = str(seed_e.toPlainText())
2292 seed = mnemonic.mn_decode( seed.split() )
2294 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2298 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2305 gap = int(unicode(gap_e.text()))
2307 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2312 def network_dialog(self):
2313 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2316 def show_seed(self):
2317 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2319 def password_dialog(self):
2320 if self.wallet.seed:
2321 ElectrumWindow.change_password_dialog(self.wallet)
2324 def restore_wallet(self):
2325 wallet = self.wallet
2326 # wait until we are connected, because the user might have selected another server
2327 if not wallet.interface.is_connected:
2328 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2329 waiting_dialog(waiting)
2331 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2332 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2334 wallet.set_up_to_date(False)
2335 wallet.interface.poke('synchronizer')
2336 waiting_dialog(waiting)
2337 if wallet.is_found():
2338 print_error( "Recovery successful" )
2340 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2347 w = ElectrumWindow(self.wallet, self.config)
2348 if url: w.set_url(url)