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.tray = QSystemTrayIcon(self.icon, self)
249 self.tray.setToolTip('Electrum')
252 m.addAction(_("Exit Electrum"), self.close)
253 self.tray.setContextMenu(m)
258 self.create_status_bar()
260 self.need_update = threading.Event()
261 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
262 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
263 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
264 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
265 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
267 self.expert_mode = config.get('classic_expert_mode', False)
268 self.decimal_point = config.get('decimal_point', 8)
270 set_language(config.get('language'))
272 self.funds_error = False
273 self.completions = QStringListModel()
275 self.tabs = tabs = QTabWidget(self)
276 self.column_widths = self.config.get("column_widths", default_column_widths )
277 tabs.addTab(self.create_history_tab(), _('History') )
278 tabs.addTab(self.create_send_tab(), _('Send') )
279 tabs.addTab(self.create_receive_tab(), _('Receive') )
280 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
281 tabs.addTab(self.create_console_tab(), _('Console') )
282 tabs.setMinimumSize(600, 400)
283 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
284 self.setCentralWidget(tabs)
286 g = self.config.get("winpos-qt",[100, 100, 840, 400])
287 self.setGeometry(g[0], g[1], g[2], g[3])
288 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
289 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
290 self.setWindowTitle( title )
294 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
295 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
296 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
297 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
298 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
300 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
301 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
302 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
303 self.history_list.setFocus(True)
305 self.exchanger = exchange_rate.Exchanger(self)
306 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
308 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
309 if platform.system() == 'Windows':
310 n = 3 if self.wallet.seed else 2
311 tabs.setCurrentIndex (n)
312 tabs.setCurrentIndex (0)
315 if self.wallet.fee < 50000:
316 self.wallet.set_fee(50000)
317 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
319 # set initial message
320 self.console.showMessage(self.wallet.interface.banner)
322 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
323 self.notify_transactions()
325 # plugins that need to change the GUI do it here
326 self.run_hook('init_gui')
329 def select_wallet_file(self):
330 wallet_folder = self.wallet.config.path
331 re.sub("(\/\w*.dat)$", "", wallet_folder)
332 file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
336 self.load_wallet(file_name)
339 def init_menubar(self):
342 electrum_menu = menubar.addMenu(_("&File"))
343 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
344 open_wallet_action.triggered.connect(self.select_wallet_file)
346 preferences_name = _("Preferences")
347 if sys.platform == 'darwin':
348 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
350 preferences_menu = electrum_menu.addAction(preferences_name)
351 preferences_menu.triggered.connect(self.settings_dialog)
352 electrum_menu.addSeparator()
354 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
356 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
357 raw_transaction_file.triggered.connect(self.do_process_from_file)
359 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
360 raw_transaction_text.triggered.connect(self.do_process_from_text)
362 electrum_menu.addSeparator()
363 quit_item = electrum_menu.addAction(_("&Close"))
364 quit_item.triggered.connect(self.close)
366 wallet_menu = menubar.addMenu(_("&Wallet"))
367 wallet_backup = wallet_menu.addAction(_("&Create backup"))
368 wallet_backup.triggered.connect(backup_wallet)
370 show_menu = wallet_menu.addMenu(_("Show"))
373 show_seed = show_menu.addAction(_("&Seed"))
374 show_seed.triggered.connect(self.show_seed_dialog)
376 show_mpk = show_menu.addAction(_("&Master Public Key"))
377 show_mpk.triggered.connect(self.show_master_public_key)
379 wallet_menu.addSeparator()
380 new_contact = wallet_menu.addAction(_("&New contact"))
381 new_contact.triggered.connect(self.new_contact_dialog)
383 import_menu = menubar.addMenu(_("&Import"))
384 in_labels = import_menu.addAction(_("&Labels"))
385 in_labels.triggered.connect(self.do_import_labels)
387 in_private_keys = import_menu.addAction(_("&Private keys"))
388 in_private_keys.triggered.connect(self.do_import_privkey)
390 export_menu = menubar.addMenu(_("&Export"))
391 ex_private_keys = export_menu.addAction(_("&Private keys"))
392 ex_private_keys.triggered.connect(self.do_export_privkeys)
394 ex_history = export_menu.addAction(_("&History"))
395 ex_history.triggered.connect(self.do_export_history)
397 ex_labels = export_menu.addAction(_("&Labels"))
398 ex_labels.triggered.connect(self.do_export_labels)
400 help_menu = menubar.addMenu(_("&Help"))
401 doc_open = help_menu.addAction(_("&Documentation"))
402 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
403 web_open = help_menu.addAction(_("&Official website"))
404 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
406 self.setMenuBar(menubar)
408 def load_wallet(self, filename):
411 config = electrum.SimpleConfig({'wallet_path': filename})
412 if not config.wallet_file_exists:
413 self.show_message("file not found "+ filename)
416 #self.wallet.verifier.stop()
417 interface = self.wallet.interface
418 verifier = self.wallet.verifier
419 self.wallet.synchronizer.stop()
422 self.wallet = electrum.Wallet(self.config)
423 self.wallet.interface = interface
424 self.wallet.verifier = verifier
426 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
431 def notify_transactions(self):
432 print_error("Notifying GUI")
433 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
434 # Combine the transactions if there are more then three
435 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
438 for tx in self.wallet.interface.pending_transactions_for_notifications:
439 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
443 self.notify("%s new transactions received. Total amount received in the new transactions %s BTC" % (tx_amount, self.format_amount(total_amount)))
445 self.wallet.interface.pending_transactions_for_notifications = []
447 for tx in self.wallet.interface.pending_transactions_for_notifications:
449 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
450 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
452 self.notify("New transaction received. %s BTC" % (self.format_amount(v)))
454 def notify(self, message):
455 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
458 def init_plugins(self):
459 import imp, pkgutil, __builtin__
460 if __builtin__.use_local_modules:
461 fp, pathname, description = imp.find_module('plugins')
462 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
463 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
464 imp.load_module('electrum_plugins', fp, pathname, description)
465 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
467 import electrum_plugins
468 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
469 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
474 self.plugins.append( p.Plugin(self) )
476 print_msg("Error:cannot initialize plugin",p)
477 traceback.print_exc(file=sys.stdout)
480 def run_hook(self, name, *args):
481 for p in self.plugins:
482 if not p.is_enabled():
491 print_error("Plugin error")
492 traceback.print_exc(file=sys.stdout)
497 def set_label(self, name, text = None):
499 old_text = self.wallet.labels.get(name)
502 self.wallet.labels[name] = text
503 self.wallet.config.set_key('labels', self.wallet.labels)
507 self.wallet.labels.pop(name)
509 self.run_hook('set_label', name, text, changed)
513 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
514 def getOpenFileName(self, title, filter = None):
515 directory = self.config.get('io_dir', os.path.expanduser('~'))
516 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
517 if fileName and directory != os.path.dirname(fileName):
518 self.config.set_key('io_dir', os.path.dirname(fileName), True)
521 def getSaveFileName(self, title, filename, filter = None):
522 directory = self.config.get('io_dir', os.path.expanduser('~'))
523 path = os.path.join( directory, filename )
524 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
525 if fileName and directory != os.path.dirname(fileName):
526 self.config.set_key('io_dir', os.path.dirname(fileName), True)
532 QMainWindow.close(self)
533 self.run_hook('close_main_window')
535 def connect_slots(self, sender):
536 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
537 self.previous_payto_e=''
539 def timer_actions(self):
540 if self.need_update.is_set():
542 self.need_update.clear()
543 self.run_hook('timer_actions')
545 def format_amount(self, x, is_diff=False, whitespaces=False):
546 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
548 def read_amount(self, x):
549 if x in['.', '']: return None
550 p = pow(10, self.decimal_point)
551 return int( p * Decimal(x) )
554 assert self.decimal_point in [5,8]
555 return "BTC" if self.decimal_point == 8 else "mBTC"
557 def update_status(self):
558 if self.wallet.interface and self.wallet.interface.is_connected:
559 if not self.wallet.up_to_date:
560 text = _("Synchronizing...")
561 icon = QIcon(":icons/status_waiting.png")
563 c, u = self.wallet.get_account_balance(self.current_account)
564 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
565 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
566 text += self.create_quote_text(Decimal(c+u)/100000000)
567 icon = QIcon(":icons/status_connected.png")
569 text = _("Not connected")
570 icon = QIcon(":icons/status_disconnected.png")
572 self.balance_label.setText(text)
573 self.status_button.setIcon( icon )
575 def update_wallet(self):
577 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
578 self.update_history_tab()
579 self.update_receive_tab()
580 self.update_contacts_tab()
581 self.update_completions()
584 def create_quote_text(self, btc_balance):
585 quote_currency = self.config.get("currency", "None")
586 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
587 if quote_balance is None:
590 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
593 def create_history_tab(self):
594 self.history_list = l = MyTreeWidget(self)
596 for i,width in enumerate(self.column_widths['history']):
597 l.setColumnWidth(i, width)
598 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
599 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
600 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
602 l.setContextMenuPolicy(Qt.CustomContextMenu)
603 l.customContextMenuRequested.connect(self.create_history_menu)
607 def create_history_menu(self, position):
608 self.history_list.selectedIndexes()
609 item = self.history_list.currentItem()
611 tx_hash = str(item.data(0, Qt.UserRole).toString())
612 if not tx_hash: return
614 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
615 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
616 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
617 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
620 def show_tx_details(self, tx):
621 dialog = QDialog(self)
623 dialog.setWindowTitle(_("Transaction Details"))
625 dialog.setLayout(vbox)
626 dialog.setMinimumSize(600,300)
629 if tx_hash in self.wallet.transactions.keys():
630 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
631 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
633 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
639 vbox.addWidget(QLabel("Transaction ID:"))
640 e = QLineEdit(tx_hash)
644 vbox.addWidget(QLabel("Date: %s"%time_str))
645 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
648 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
649 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
651 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
652 vbox.addWidget(QLabel("Transaction fee: unknown"))
654 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
656 vbox.addWidget( self.generate_transaction_information_widget(tx) )
658 ok_button = QPushButton(_("Close"))
659 ok_button.setDefault(True)
660 ok_button.clicked.connect(dialog.accept)
664 hbox.addWidget(ok_button)
668 def tx_label_clicked(self, item, column):
669 if column==2 and item.isSelected():
671 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
672 self.history_list.editItem( item, column )
673 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
676 def tx_label_changed(self, item, column):
680 tx_hash = str(item.data(0, Qt.UserRole).toString())
681 tx = self.wallet.transactions.get(tx_hash)
682 text = unicode( item.text(2) )
683 self.set_label(tx_hash, text)
685 item.setForeground(2, QBrush(QColor('black')))
687 text = self.wallet.get_default_label(tx_hash)
688 item.setText(2, text)
689 item.setForeground(2, QBrush(QColor('gray')))
693 def edit_label(self, is_recv):
694 l = self.receive_list if is_recv else self.contacts_list
695 item = l.currentItem()
696 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
697 l.editItem( item, 1 )
698 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
702 def address_label_clicked(self, item, column, l, column_addr, column_label):
703 if column == column_label and item.isSelected():
704 is_editable = item.data(0, 32).toBool()
707 addr = unicode( item.text(column_addr) )
708 label = unicode( item.text(column_label) )
709 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
710 l.editItem( item, column )
711 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
714 def address_label_changed(self, item, column, l, column_addr, column_label):
715 if column == column_label:
716 addr = unicode( item.text(column_addr) )
717 text = unicode( item.text(column_label) )
718 is_editable = item.data(0, 32).toBool()
722 changed = self.set_label(addr, text)
724 self.update_history_tab()
725 self.update_completions()
727 self.current_item_changed(item)
729 self.run_hook('item_changed', item, column)
732 def current_item_changed(self, a):
733 self.run_hook('current_item_changed', a)
737 def update_history_tab(self):
739 self.history_list.clear()
740 for item in self.wallet.get_tx_history(self.current_account):
741 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
744 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
749 time_str = 'unverified'
750 icon = QIcon(":icons/unconfirmed.png")
753 icon = QIcon(":icons/unconfirmed.png")
755 icon = QIcon(":icons/clock%d.png"%conf)
757 icon = QIcon(":icons/confirmed.png")
759 if value is not None:
760 v_str = self.format_amount(value, True, whitespaces=True)
764 balance_str = self.format_amount(balance, whitespaces=True)
767 label, is_default_label = self.wallet.get_label(tx_hash)
769 label = _('Pruned transaction outputs')
770 is_default_label = False
772 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
773 item.setFont(2, QFont(MONOSPACE_FONT))
774 item.setFont(3, QFont(MONOSPACE_FONT))
775 item.setFont(4, QFont(MONOSPACE_FONT))
777 item.setForeground(3, QBrush(QColor("#BC1E1E")))
779 item.setData(0, Qt.UserRole, tx_hash)
780 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
782 item.setForeground(2, QBrush(QColor('grey')))
784 item.setIcon(0, icon)
785 self.history_list.insertTopLevelItem(0,item)
788 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
791 def create_send_tab(self):
796 grid.setColumnMinimumWidth(3,300)
797 grid.setColumnStretch(5,1)
800 self.payto_e = QLineEdit()
801 grid.addWidget(QLabel(_('Pay to')), 1, 0)
802 grid.addWidget(self.payto_e, 1, 1, 1, 3)
804 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)
806 completer = QCompleter()
807 completer.setCaseSensitivity(False)
808 self.payto_e.setCompleter(completer)
809 completer.setModel(self.completions)
811 self.message_e = QLineEdit()
812 grid.addWidget(QLabel(_('Description')), 2, 0)
813 grid.addWidget(self.message_e, 2, 1, 1, 3)
814 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)
816 self.amount_e = AmountEdit(self.base_unit)
817 grid.addWidget(QLabel(_('Amount')), 3, 0)
818 grid.addWidget(self.amount_e, 3, 1, 1, 2)
819 grid.addWidget(HelpButton(
820 _('Amount to be sent.') + '\n\n' \
821 + _('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.') \
822 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
824 self.fee_e = AmountEdit(self.base_unit)
825 grid.addWidget(QLabel(_('Fee')), 4, 0)
826 grid.addWidget(self.fee_e, 4, 1, 1, 2)
827 grid.addWidget(HelpButton(
828 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
829 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
830 + _('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)
833 b = EnterButton(_("Send"), self.do_send)
835 b = EnterButton(_("Create unsigned transaction"), self.do_send)
836 grid.addWidget(b, 6, 1)
838 b = EnterButton(_("Clear"),self.do_clear)
839 grid.addWidget(b, 6, 2)
841 self.payto_sig = QLabel('')
842 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
844 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
845 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
854 def entry_changed( is_fee ):
855 self.funds_error = False
857 if self.amount_e.is_shortcut:
858 self.amount_e.is_shortcut = False
859 c, u = self.wallet.get_account_balance(self.current_account)
860 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
861 fee = self.wallet.estimated_fee(inputs)
863 self.amount_e.setText( self.format_amount(amount) )
864 self.fee_e.setText( self.format_amount( fee ) )
867 amount = self.read_amount(str(self.amount_e.text()))
868 fee = self.read_amount(str(self.fee_e.text()))
870 if not is_fee: fee = None
873 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
875 self.fee_e.setText( self.format_amount( fee ) )
878 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
882 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
883 self.funds_error = True
884 text = _( "Not enough funds" )
885 c, u = self.wallet.get_frozen_balance()
886 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
888 self.statusBar().showMessage(text)
889 self.amount_e.setPalette(palette)
890 self.fee_e.setPalette(palette)
892 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
893 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
895 self.run_hook('create_send_tab', grid)
899 def update_completions(self):
901 for addr,label in self.wallet.labels.items():
902 if addr in self.wallet.addressbook:
903 l.append( label + ' <' + addr + '>')
905 self.run_hook('update_completions', l)
906 self.completions.setStringList(l)
910 return lambda s, *args: s.do_protect(func, args)
915 label = unicode( self.message_e.text() )
916 r = unicode( self.payto_e.text() )
919 # label or alias, with address in brackets
920 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
921 to_address = m.group(2) if m else r
923 if not is_valid(to_address):
924 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
928 amount = self.read_amount(unicode( self.amount_e.text()))
930 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
933 fee = self.read_amount(unicode( self.fee_e.text()))
935 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
938 confirm_amount = self.config.get('confirm_amount', 100000000)
939 if amount >= confirm_amount:
940 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
943 self.send_tx(to_address, amount, fee, label)
947 def send_tx(self, to_address, amount, fee, label, password):
950 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
951 except BaseException, e:
952 self.show_message(str(e))
955 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
956 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
959 self.run_hook('send_tx', tx)
962 self.set_label(tx.hash(), label)
965 h = self.wallet.send_tx(tx)
966 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
967 status, msg = self.wallet.receive_tx( h )
969 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
971 self.update_contacts_tab()
973 QMessageBox.warning(self, _('Error'), msg, _('OK'))
975 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
977 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
978 with open(fileName,'w') as f:
979 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
980 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
982 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
987 def set_url(self, url):
988 address, amount, label, message, signature, identity, url = util.parse_url(url)
989 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
991 if label and self.wallet.labels.get(address) != label:
992 if self.question('Give label "%s" to address %s ?'%(label,address)):
993 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
994 self.wallet.addressbook.append(address)
995 self.set_label(address, label)
997 self.run_hook('set_url', url, self.show_message, self.question)
999 self.tabs.setCurrentIndex(1)
1000 label = self.wallet.labels.get(address)
1001 m_addr = label + ' <'+ address +'>' if label else address
1002 self.payto_e.setText(m_addr)
1004 self.message_e.setText(message)
1005 self.amount_e.setText(amount)
1007 self.set_frozen(self.payto_e,True)
1008 self.set_frozen(self.amount_e,True)
1009 self.set_frozen(self.message_e,True)
1010 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1012 self.payto_sig.setVisible(False)
1015 self.payto_sig.setVisible(False)
1016 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1018 self.set_frozen(e,False)
1019 self.update_status()
1021 def set_frozen(self,entry,frozen):
1023 entry.setReadOnly(True)
1024 entry.setFrame(False)
1025 palette = QPalette()
1026 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1027 entry.setPalette(palette)
1029 entry.setReadOnly(False)
1030 entry.setFrame(True)
1031 palette = QPalette()
1032 palette.setColor(entry.backgroundRole(), QColor('white'))
1033 entry.setPalette(palette)
1036 def toggle_freeze(self,addr):
1038 if addr in self.wallet.frozen_addresses:
1039 self.wallet.unfreeze(addr)
1041 self.wallet.freeze(addr)
1042 self.update_receive_tab()
1044 def toggle_priority(self,addr):
1046 if addr in self.wallet.prioritized_addresses:
1047 self.wallet.unprioritize(addr)
1049 self.wallet.prioritize(addr)
1050 self.update_receive_tab()
1053 def create_list_tab(self, headers):
1054 "generic tab creation method"
1055 l = MyTreeWidget(self)
1056 l.setColumnCount( len(headers) )
1057 l.setHeaderLabels( headers )
1060 vbox = QVBoxLayout()
1067 vbox.addWidget(buttons)
1069 hbox = QHBoxLayout()
1072 buttons.setLayout(hbox)
1077 def create_receive_tab(self):
1078 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1079 l.setContextMenuPolicy(Qt.CustomContextMenu)
1080 l.customContextMenuRequested.connect(self.create_receive_menu)
1081 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1082 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1083 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1084 self.receive_list = l
1085 self.receive_buttons_hbox = hbox
1090 def receive_tab_set_mode(self, i):
1091 self.save_column_widths()
1092 self.expert_mode = (i == 1)
1093 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1094 self.update_receive_tab()
1097 def save_column_widths(self):
1098 if not self.expert_mode:
1099 widths = [ self.receive_list.columnWidth(0) ]
1102 for i in range(self.receive_list.columnCount() -1):
1103 widths.append(self.receive_list.columnWidth(i))
1104 self.column_widths["receive"][self.expert_mode] = widths
1106 self.column_widths["history"] = []
1107 for i in range(self.history_list.columnCount() - 1):
1108 self.column_widths["history"].append(self.history_list.columnWidth(i))
1110 self.column_widths["contacts"] = []
1111 for i in range(self.contacts_list.columnCount() - 1):
1112 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1114 self.config.set_key("column_widths", self.column_widths, True)
1117 def create_contacts_tab(self):
1118 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1119 l.setContextMenuPolicy(Qt.CustomContextMenu)
1120 l.customContextMenuRequested.connect(self.create_contact_menu)
1121 for i,width in enumerate(self.column_widths['contacts']):
1122 l.setColumnWidth(i, width)
1124 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1125 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1126 self.contacts_list = l
1127 self.contacts_buttons_hbox = hbox
1132 def delete_imported_key(self, addr):
1133 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1134 self.wallet.delete_imported_key(addr)
1135 self.update_receive_tab()
1136 self.update_history_tab()
1139 def create_receive_menu(self, position):
1140 # fixme: this function apparently has a side effect.
1141 # if it is not called the menu pops up several times
1142 #self.receive_list.selectedIndexes()
1144 item = self.receive_list.itemAt(position)
1146 addr = unicode(item.text(0))
1147 if not is_valid(addr):
1148 item.setExpanded(not item.isExpanded())
1151 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1152 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1153 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1154 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1155 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1156 if addr in self.wallet.imported_keys:
1157 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1159 if self.expert_mode:
1160 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1161 menu.addAction(t, lambda: self.toggle_freeze(addr))
1162 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1163 menu.addAction(t, lambda: self.toggle_priority(addr))
1165 self.run_hook('receive_menu', menu)
1166 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1169 def payto(self, addr):
1171 label = self.wallet.labels.get(addr)
1172 m_addr = label + ' <' + addr + '>' if label else addr
1173 self.tabs.setCurrentIndex(1)
1174 self.payto_e.setText(m_addr)
1175 self.amount_e.setFocus()
1178 def delete_contact(self, x):
1179 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1180 self.wallet.delete_contact(x)
1181 self.set_label(x, None)
1182 self.update_history_tab()
1183 self.update_contacts_tab()
1184 self.update_completions()
1187 def create_contact_menu(self, position):
1188 item = self.contacts_list.itemAt(position)
1190 addr = unicode(item.text(0))
1191 label = unicode(item.text(1))
1192 is_editable = item.data(0,32).toBool()
1193 payto_addr = item.data(0,33).toString()
1195 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1196 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1197 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1199 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1200 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1202 self.run_hook('create_contact_menu', menu, item)
1203 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1206 def update_receive_item(self, item):
1207 item.setFont(0, QFont(MONOSPACE_FONT))
1208 address = str(item.data(0,0).toString())
1209 label = self.wallet.labels.get(address,'')
1210 item.setData(1,0,label)
1211 item.setData(0,32, True) # is editable
1213 self.run_hook('update_receive_item', address, item)
1215 c, u = self.wallet.get_addr_balance(address)
1216 balance = self.format_amount(c + u)
1217 item.setData(2,0,balance)
1219 if self.expert_mode:
1220 if address in self.wallet.frozen_addresses:
1221 item.setBackgroundColor(0, QColor('lightblue'))
1222 elif address in self.wallet.prioritized_addresses:
1223 item.setBackgroundColor(0, QColor('lightgreen'))
1226 def update_receive_tab(self):
1227 l = self.receive_list
1230 l.setColumnHidden(2, not self.expert_mode)
1231 l.setColumnHidden(3, not self.expert_mode)
1232 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1233 l.setColumnWidth(i, width)
1235 if self.current_account is None:
1236 account_items = self.wallet.accounts.items()
1237 elif self.current_account != -1:
1238 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1242 for k, account in account_items:
1243 name = account.get('name',str(k))
1244 c,u = self.wallet.get_account_balance(k)
1245 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1246 l.addTopLevelItem(account_item)
1247 account_item.setExpanded(True)
1249 for is_change in ([0,1] if self.expert_mode else [0]):
1250 if self.expert_mode:
1251 name = "Receiving" if not is_change else "Change"
1252 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1253 account_item.addChild(seq_item)
1254 if not is_change: seq_item.setExpanded(True)
1256 seq_item = account_item
1260 for address in account[is_change]:
1261 h = self.wallet.history.get(address,[])
1265 if gap > self.wallet.gap_limit:
1270 num_tx = '*' if h == ['*'] else "%d"%len(h)
1271 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1272 self.update_receive_item(item)
1274 item.setBackgroundColor(1, QColor('red'))
1275 seq_item.addChild(item)
1278 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1279 c,u = self.wallet.get_imported_balance()
1280 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1281 l.addTopLevelItem(account_item)
1282 account_item.setExpanded(True)
1283 for address in self.wallet.imported_keys.keys():
1284 item = QTreeWidgetItem( [ address, '', '', ''] )
1285 self.update_receive_item(item)
1286 account_item.addChild(item)
1289 # we use column 1 because column 0 may be hidden
1290 l.setCurrentItem(l.topLevelItem(0),1)
1293 def update_contacts_tab(self):
1294 l = self.contacts_list
1297 for address in self.wallet.addressbook:
1298 label = self.wallet.labels.get(address,'')
1299 n = self.wallet.get_num_tx(address)
1300 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1301 item.setFont(0, QFont(MONOSPACE_FONT))
1302 # 32 = label can be edited (bool)
1303 item.setData(0,32, True)
1305 item.setData(0,33, address)
1306 l.addTopLevelItem(item)
1308 self.run_hook('update_contacts_tab', l)
1309 l.setCurrentItem(l.topLevelItem(0))
1313 def create_console_tab(self):
1314 from qt_console import Console
1315 self.console = console = Console()
1316 self.console.history = self.config.get("console-history",[])
1317 self.console.history_index = len(self.console.history)
1319 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1320 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1322 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1324 def mkfunc(f, method):
1325 return lambda *args: apply( f, (method, args, self.password_dialog ))
1327 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1328 methods[m] = mkfunc(c._run, m)
1330 console.updateNamespace(methods)
1333 def change_account(self,s):
1334 if s == _("All accounts"):
1335 self.current_account = None
1337 accounts = self.wallet.get_accounts()
1338 for k, v in accounts.items():
1340 self.current_account = k
1341 self.update_history_tab()
1342 self.update_status()
1343 self.update_receive_tab()
1345 def create_status_bar(self):
1348 sb.setFixedHeight(35)
1349 qtVersion = qVersion()
1351 self.balance_label = QLabel("")
1352 sb.addWidget(self.balance_label)
1354 update_notification = UpdateLabel(self.config)
1355 if(update_notification.new_version):
1356 sb.addPermanentWidget(update_notification)
1358 accounts = self.wallet.get_accounts()
1359 if len(accounts) > 1:
1360 from_combo = QComboBox()
1361 from_combo.addItems([_("All accounts")] + accounts.values())
1362 from_combo.setCurrentIndex(0)
1363 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1364 sb.addPermanentWidget(from_combo)
1366 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1367 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1368 if self.wallet.seed:
1369 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1370 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1371 sb.addPermanentWidget( self.password_button )
1372 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1373 if self.wallet.seed:
1374 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1375 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1376 sb.addPermanentWidget( self.status_button )
1378 self.run_hook('create_status_bar', (sb,))
1380 self.setStatusBar(sb)
1384 self.config.set_key('gui', 'lite', True)
1387 self.lite.mini.show()
1389 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1390 self.lite.main(None)
1392 def new_contact_dialog(self):
1393 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1394 address = unicode(text)
1396 if is_valid(address):
1397 self.wallet.add_contact(address)
1398 self.update_contacts_tab()
1399 self.update_history_tab()
1400 self.update_completions()
1402 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1404 def show_master_public_key(self):
1405 dialog = QDialog(self)
1407 dialog.setWindowTitle(_("Master Public Key"))
1409 main_text = QTextEdit()
1410 main_text.setText(self.wallet.get_master_public_key())
1411 main_text.setReadOnly(True)
1412 main_text.setMaximumHeight(170)
1413 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1415 ok_button = QPushButton(_("OK"))
1416 ok_button.setDefault(True)
1417 ok_button.clicked.connect(dialog.accept)
1419 main_layout = QGridLayout()
1420 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1422 main_layout.addWidget(main_text, 1, 0)
1423 main_layout.addWidget(qrw, 1, 1 )
1425 vbox = QVBoxLayout()
1426 vbox.addLayout(main_layout)
1427 hbox = QHBoxLayout()
1429 hbox.addWidget(ok_button)
1430 vbox.addLayout(hbox)
1432 dialog.setLayout(vbox)
1437 def show_seed_dialog(self, password):
1438 if not self.wallet.seed:
1439 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1442 seed = self.wallet.decode_seed(password)
1444 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1446 self.show_seed(seed, self.wallet.imported_keys, self)
1450 def show_seed(self, seed, imported_keys, parent=None):
1451 dialog = QDialog(parent)
1453 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1455 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1457 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1459 seed_text = QTextEdit(brainwallet)
1460 seed_text.setReadOnly(True)
1461 seed_text.setMaximumHeight(130)
1463 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1464 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1465 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1466 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1468 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1469 label2 = QLabel(msg2)
1470 label2.setWordWrap(True)
1473 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1474 logo.setMaximumWidth(60)
1476 qrw = QRCodeWidget(seed)
1478 ok_button = QPushButton(_("OK"))
1479 ok_button.setDefault(True)
1480 ok_button.clicked.connect(dialog.accept)
1482 grid = QGridLayout()
1483 #main_layout.addWidget(logo, 0, 0)
1485 grid.addWidget(logo, 0, 0)
1486 grid.addWidget(label1, 0, 1)
1488 grid.addWidget(seed_text, 1, 0, 1, 2)
1490 grid.addWidget(qrw, 0, 2, 2, 1)
1492 vbox = QVBoxLayout()
1493 vbox.addLayout(grid)
1494 vbox.addWidget(label2)
1496 hbox = QHBoxLayout()
1498 hbox.addWidget(ok_button)
1499 vbox.addLayout(hbox)
1501 dialog.setLayout(vbox)
1504 def show_qrcode(self, data, title = "QR code"):
1508 d.setWindowTitle(title)
1509 d.setMinimumSize(270, 300)
1510 vbox = QVBoxLayout()
1511 qrw = QRCodeWidget(data)
1512 vbox.addWidget(qrw, 1)
1513 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1514 hbox = QHBoxLayout()
1518 filename = "qrcode.bmp"
1519 bmp.save_qrcode(qrw.qr, filename)
1520 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1522 b = QPushButton(_("Save"))
1524 b.clicked.connect(print_qr)
1526 b = QPushButton(_("Close"))
1528 b.clicked.connect(d.accept)
1531 vbox.addLayout(hbox)
1536 def do_protect(self, func, args):
1537 if self.wallet.use_encryption:
1538 password = self.password_dialog()
1544 if args != (False,):
1545 args = (self,) + args + (password,)
1547 args = (self,password)
1552 def show_private_key(self, address, password):
1553 if not address: return
1555 pk = self.wallet.get_private_key(address, password)
1556 except BaseException, e:
1557 self.show_message(str(e))
1559 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1563 def do_sign(self, address, message, signature, password):
1565 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1566 signature.setText(sig)
1567 except BaseException, e:
1568 self.show_message(str(e))
1570 def sign_message(self, address):
1571 if not address: return
1574 d.setWindowTitle(_('Sign Message'))
1575 d.setMinimumSize(410, 290)
1577 tab_widget = QTabWidget()
1579 layout = QGridLayout(tab)
1581 sign_address = QLineEdit()
1583 sign_address.setText(address)
1584 layout.addWidget(QLabel(_('Address')), 1, 0)
1585 layout.addWidget(sign_address, 1, 1)
1587 sign_message = QTextEdit()
1588 layout.addWidget(QLabel(_('Message')), 2, 0)
1589 layout.addWidget(sign_message, 2, 1)
1590 layout.setRowStretch(2,3)
1592 sign_signature = QTextEdit()
1593 layout.addWidget(QLabel(_('Signature')), 3, 0)
1594 layout.addWidget(sign_signature, 3, 1)
1595 layout.setRowStretch(3,1)
1598 hbox = QHBoxLayout()
1599 b = QPushButton(_("Sign"))
1601 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1602 b = QPushButton(_("Close"))
1603 b.clicked.connect(d.accept)
1605 layout.addLayout(hbox, 4, 1)
1606 tab_widget.addTab(tab, _("Sign"))
1610 layout = QGridLayout(tab)
1612 verify_address = QLineEdit()
1613 layout.addWidget(QLabel(_('Address')), 1, 0)
1614 layout.addWidget(verify_address, 1, 1)
1616 verify_message = QTextEdit()
1617 layout.addWidget(QLabel(_('Message')), 2, 0)
1618 layout.addWidget(verify_message, 2, 1)
1619 layout.setRowStretch(2,3)
1621 verify_signature = QTextEdit()
1622 layout.addWidget(QLabel(_('Signature')), 3, 0)
1623 layout.addWidget(verify_signature, 3, 1)
1624 layout.setRowStretch(3,1)
1627 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1628 self.show_message(_("Signature verified"))
1630 self.show_message(_("Error: wrong signature"))
1632 hbox = QHBoxLayout()
1633 b = QPushButton(_("Verify"))
1634 b.clicked.connect(do_verify)
1636 b = QPushButton(_("Close"))
1637 b.clicked.connect(d.accept)
1639 layout.addLayout(hbox, 4, 1)
1640 tab_widget.addTab(tab, _("Verify"))
1642 vbox = QVBoxLayout()
1643 vbox.addWidget(tab_widget)
1650 def question(self, msg):
1651 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1653 def show_message(self, msg):
1654 QMessageBox.information(self, _('Message'), msg, _('OK'))
1656 def password_dialog(self ):
1663 vbox = QVBoxLayout()
1664 msg = _('Please enter your password')
1665 vbox.addWidget(QLabel(msg))
1667 grid = QGridLayout()
1669 grid.addWidget(QLabel(_('Password')), 1, 0)
1670 grid.addWidget(pw, 1, 1)
1671 vbox.addLayout(grid)
1673 vbox.addLayout(ok_cancel_buttons(d))
1676 self.run_hook('password_dialog', pw, grid, 1)
1677 if not d.exec_(): return
1678 return unicode(pw.text())
1685 def change_password_dialog( wallet, parent=None ):
1688 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1696 new_pw = QLineEdit()
1697 new_pw.setEchoMode(2)
1698 conf_pw = QLineEdit()
1699 conf_pw.setEchoMode(2)
1701 vbox = QVBoxLayout()
1703 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1704 +_('To disable wallet encryption, enter an empty new password.')) \
1705 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1707 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1708 +_("Leave these fields empty if you want to disable encryption.")
1709 vbox.addWidget(QLabel(msg))
1711 grid = QGridLayout()
1714 if wallet.use_encryption:
1715 grid.addWidget(QLabel(_('Password')), 1, 0)
1716 grid.addWidget(pw, 1, 1)
1718 grid.addWidget(QLabel(_('New Password')), 2, 0)
1719 grid.addWidget(new_pw, 2, 1)
1721 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1722 grid.addWidget(conf_pw, 3, 1)
1723 vbox.addLayout(grid)
1725 vbox.addLayout(ok_cancel_buttons(d))
1728 if not d.exec_(): return
1730 password = unicode(pw.text()) if wallet.use_encryption else None
1731 new_password = unicode(new_pw.text())
1732 new_password2 = unicode(conf_pw.text())
1735 seed = wallet.decode_seed(password)
1737 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1740 if new_password != new_password2:
1741 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1742 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1745 wallet.update_password(seed, password, new_password)
1747 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1750 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1753 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1754 parent.password_button.setIcon( icon )
1758 def generate_transaction_information_widget(self, tx):
1759 tabs = QTabWidget(self)
1762 grid_ui = QGridLayout(tab1)
1763 grid_ui.setColumnStretch(0,1)
1764 tabs.addTab(tab1, _('Outputs') )
1766 tree_widget = MyTreeWidget(self)
1767 tree_widget.setColumnCount(2)
1768 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1769 tree_widget.setColumnWidth(0, 300)
1770 tree_widget.setColumnWidth(1, 50)
1772 for address, value in tx.outputs:
1773 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1774 tree_widget.addTopLevelItem(item)
1776 tree_widget.setMaximumHeight(100)
1778 grid_ui.addWidget(tree_widget)
1781 grid_ui = QGridLayout(tab2)
1782 grid_ui.setColumnStretch(0,1)
1783 tabs.addTab(tab2, _('Inputs') )
1785 tree_widget = MyTreeWidget(self)
1786 tree_widget.setColumnCount(2)
1787 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1789 for input_line in tx.inputs:
1790 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1791 tree_widget.addTopLevelItem(item)
1793 tree_widget.setMaximumHeight(100)
1795 grid_ui.addWidget(tree_widget)
1799 def tx_dict_from_text(self, txt):
1801 tx_dict = json.loads(str(txt))
1802 assert "hex" in tx_dict.keys()
1803 assert "complete" in tx_dict.keys()
1804 if not tx_dict["complete"]:
1805 assert "input_info" in tx_dict.keys()
1807 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1812 def read_tx_from_file(self):
1813 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1817 with open(fileName, "r") as f:
1818 file_content = f.read()
1819 except (ValueError, IOError, os.error), reason:
1820 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1822 return self.tx_dict_from_text(file_content)
1826 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1828 self.wallet.signrawtransaction(tx, input_info, [], password)
1830 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1832 with open(fileName, "w+") as f:
1833 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1834 self.show_message(_("Transaction saved successfully"))
1837 except BaseException, e:
1838 self.show_message(str(e))
1841 def send_raw_transaction(self, raw_tx, dialog = ""):
1842 result, result_message = self.wallet.sendtx( raw_tx )
1844 self.show_message("Transaction successfully sent: %s" % (result_message))
1848 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1850 def do_process_from_text(self):
1851 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1854 tx_dict = self.tx_dict_from_text(text)
1856 self.create_process_transaction_window(tx_dict)
1858 def do_process_from_file(self):
1859 tx_dict = self.read_tx_from_file()
1861 self.create_process_transaction_window(tx_dict)
1863 def create_process_transaction_window(self, tx_dict):
1864 tx = Transaction(tx_dict["hex"])
1866 dialog = QDialog(self)
1867 dialog.setMinimumWidth(500)
1868 dialog.setWindowTitle(_('Process raw transaction'))
1874 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1875 l.addWidget(QLabel(_("Actions")), 4,0)
1877 if tx_dict["complete"] == False:
1878 l.addWidget(QLabel(_("Unsigned")), 3,1)
1879 if self.wallet.seed :
1880 b = QPushButton("Sign transaction")
1881 input_info = json.loads(tx_dict["input_info"])
1882 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1883 l.addWidget(b, 4, 1)
1885 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1887 l.addWidget(QLabel(_("Signed")), 3,1)
1888 b = QPushButton("Broadcast transaction")
1889 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1892 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1893 cancelButton = QPushButton(_("Cancel"))
1894 cancelButton.clicked.connect(lambda: dialog.done(0))
1895 l.addWidget(cancelButton, 4,2)
1901 def do_export_privkeys(self, password):
1902 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.")))
1905 select_export = _('Select file to export your private keys to')
1906 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1908 with open(fileName, "w+") as csvfile:
1909 transaction = csv.writer(csvfile)
1910 transaction.writerow(["address", "private_key"])
1913 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1914 transaction.writerow(["%34s"%addr,pk])
1916 self.show_message(_("Private keys exported."))
1918 except (IOError, os.error), reason:
1919 export_error_label = _("Electrum was unable to produce a private key-export.")
1920 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1922 except BaseException, e:
1923 self.show_message(str(e))
1927 def do_import_labels(self):
1928 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1929 if not labelsFile: return
1931 f = open(labelsFile, 'r')
1934 for key, value in json.loads(data).items():
1935 self.wallet.labels[key] = value
1937 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1938 except (IOError, os.error), reason:
1939 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1942 def do_export_labels(self):
1943 labels = self.wallet.labels
1945 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1947 with open(fileName, 'w+') as f:
1948 json.dump(labels, f)
1949 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1950 except (IOError, os.error), reason:
1951 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1954 def do_export_history(self):
1955 from gui_lite import csv_transaction
1956 csv_transaction(self.wallet)
1960 def do_import_privkey(self, password):
1961 if not self.wallet.imported_keys:
1962 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1963 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1964 + _('Are you sure you understand what you are doing?'), 3, 4)
1967 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1970 text = str(text).split()
1975 addr = self.wallet.import_key(key, password)
1976 except BaseException as e:
1982 addrlist.append(addr)
1984 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1986 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1987 self.update_receive_tab()
1988 self.update_history_tab()
1991 def settings_dialog(self):
1993 d.setWindowTitle(_('Electrum Settings'))
1995 vbox = QVBoxLayout()
1997 tabs = QTabWidget(self)
1998 self.settings_tab = tabs
1999 vbox.addWidget(tabs)
2002 grid_ui = QGridLayout(tab1)
2003 grid_ui.setColumnStretch(0,1)
2004 tabs.addTab(tab1, _('Display') )
2006 nz_label = QLabel(_('Display zeros'))
2007 grid_ui.addWidget(nz_label, 0, 0)
2008 nz_e = AmountEdit(None,True)
2009 nz_e.setText("%d"% self.wallet.num_zeros)
2010 grid_ui.addWidget(nz_e, 0, 1)
2011 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2012 grid_ui.addWidget(HelpButton(msg), 0, 2)
2013 if not self.config.is_modifiable('num_zeros'):
2014 for w in [nz_e, nz_label]: w.setEnabled(False)
2016 lang_label=QLabel(_('Language') + ':')
2017 grid_ui.addWidget(lang_label, 1, 0)
2018 lang_combo = QComboBox()
2019 from i18n import languages
2020 lang_combo.addItems(languages.values())
2022 index = languages.keys().index(self.config.get("language",''))
2025 lang_combo.setCurrentIndex(index)
2026 grid_ui.addWidget(lang_combo, 1, 1)
2027 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2028 if not self.config.is_modifiable('language'):
2029 for w in [lang_combo, lang_label]: w.setEnabled(False)
2031 currencies = self.exchanger.get_currencies()
2032 currencies.insert(0, "None")
2034 cur_label=QLabel(_('Currency') + ':')
2035 grid_ui.addWidget(cur_label , 2, 0)
2036 cur_combo = QComboBox()
2037 cur_combo.addItems(currencies)
2039 index = currencies.index(self.config.get('currency', "None"))
2042 cur_combo.setCurrentIndex(index)
2043 grid_ui.addWidget(cur_combo, 2, 1)
2044 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2046 expert_cb = QCheckBox(_('Expert mode'))
2047 expert_cb.setChecked(self.expert_mode)
2048 grid_ui.addWidget(expert_cb, 3, 0)
2049 hh = _('In expert mode, your client will:') + '\n' \
2050 + _(' - Show change addresses in the Receive tab') + '\n' \
2051 + _(' - Display the balance of each address') + '\n' \
2052 + _(' - Add freeze/prioritize actions to addresses.')
2053 grid_ui.addWidget(HelpButton(hh), 3, 2)
2054 grid_ui.setRowStretch(4,1)
2058 grid_wallet = QGridLayout(tab2)
2059 grid_wallet.setColumnStretch(0,1)
2060 tabs.addTab(tab2, _('Wallet') )
2062 fee_label = QLabel(_('Transaction fee'))
2063 grid_wallet.addWidget(fee_label, 0, 0)
2064 fee_e = AmountEdit(self.base_unit)
2065 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2066 grid_wallet.addWidget(fee_e, 0, 2)
2067 msg = _('Fee per kilobyte of transaction.') + ' ' \
2068 + _('Recommended value') + ': ' + self.format_amount(50000)
2069 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2070 if not self.config.is_modifiable('fee_per_kb'):
2071 for w in [fee_e, fee_label]: w.setEnabled(False)
2073 usechange_cb = QCheckBox(_('Use change addresses'))
2074 usechange_cb.setChecked(self.wallet.use_change)
2075 grid_wallet.addWidget(usechange_cb, 1, 0)
2076 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2077 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2079 gap_label = QLabel(_('Gap limit'))
2080 grid_wallet.addWidget(gap_label, 2, 0)
2081 gap_e = AmountEdit(None,True)
2082 gap_e.setText("%d"% self.wallet.gap_limit)
2083 grid_wallet.addWidget(gap_e, 2, 2)
2084 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2085 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2086 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2087 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2088 + _('Warning') + ': ' \
2089 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2090 + _('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'
2091 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2092 if not self.config.is_modifiable('gap_limit'):
2093 for w in [gap_e, gap_label]: w.setEnabled(False)
2095 units = ['BTC', 'mBTC']
2096 unit_label = QLabel(_('Base unit'))
2097 grid_wallet.addWidget(unit_label, 3, 0)
2098 unit_combo = QComboBox()
2099 unit_combo.addItems(units)
2100 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2101 grid_wallet.addWidget(unit_combo, 3, 2)
2102 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2103 + '\n1BTC=1000mBTC.\n' \
2104 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2105 grid_wallet.setRowStretch(4,1)
2109 tab5 = QScrollArea()
2110 tab5.setEnabled(True)
2111 tab5.setWidgetResizable(True)
2113 grid_plugins = QGridLayout()
2114 grid_plugins.setColumnStretch(0,1)
2117 w.setLayout(grid_plugins)
2120 w.setMinimumHeight(len(self.plugins)*35)
2122 tabs.addTab(tab5, _('Plugins') )
2123 def mk_toggle(cb, p):
2124 return lambda: cb.setChecked(p.toggle())
2125 for i, p in enumerate(self.plugins):
2127 name, description = p.get_info()
2128 cb = QCheckBox(name)
2129 cb.setDisabled(not p.is_available())
2130 cb.setChecked(p.is_enabled())
2131 cb.clicked.connect(mk_toggle(cb,p))
2132 grid_plugins.addWidget(cb, i, 0)
2133 if p.requires_settings():
2134 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2135 grid_plugins.addWidget(HelpButton(description), i, 2)
2137 print_msg("Error: cannot display plugin", p)
2138 traceback.print_exc(file=sys.stdout)
2139 grid_plugins.setRowStretch(i+1,1)
2141 self.run_hook('create_settings_tab', tabs)
2143 vbox.addLayout(ok_cancel_buttons(d))
2147 if not d.exec_(): return
2149 fee = unicode(fee_e.text())
2151 fee = self.read_amount(fee)
2153 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2156 self.wallet.set_fee(fee)
2158 nz = unicode(nz_e.text())
2163 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2166 if self.wallet.num_zeros != nz:
2167 self.wallet.num_zeros = nz
2168 self.config.set_key('num_zeros', nz, True)
2169 self.update_history_tab()
2170 self.update_receive_tab()
2172 usechange_result = usechange_cb.isChecked()
2173 if self.wallet.use_change != usechange_result:
2174 self.wallet.use_change = usechange_result
2175 self.config.set_key('use_change', self.wallet.use_change, True)
2177 unit_result = units[unit_combo.currentIndex()]
2178 if self.base_unit() != unit_result:
2179 self.decimal_point = 8 if unit_result == 'BTC' else 5
2180 self.config.set_key('decimal_point', self.decimal_point, True)
2181 self.update_history_tab()
2182 self.update_status()
2185 n = int(gap_e.text())
2187 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2190 if self.wallet.gap_limit != n:
2191 r = self.wallet.change_gap_limit(n)
2193 self.update_receive_tab()
2194 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2196 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2198 need_restart = False
2200 lang_request = languages.keys()[lang_combo.currentIndex()]
2201 if lang_request != self.config.get('language'):
2202 self.config.set_key("language", lang_request, True)
2205 cur_request = str(currencies[cur_combo.currentIndex()])
2206 if cur_request != self.config.get('currency', "None"):
2207 self.config.set_key('currency', cur_request, True)
2208 self.update_wallet()
2210 self.run_hook('close_settings_dialog')
2213 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2215 self.receive_tab_set_mode(expert_cb.isChecked())
2217 def run_network_dialog(self):
2218 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2220 def closeEvent(self, event):
2222 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2223 self.save_column_widths()
2224 self.config.set_key("console-history",self.console.history[-50:])
2233 def __init__(self, wallet, config, app=None):
2234 self.wallet = wallet
2235 self.config = config
2237 self.app = QApplication(sys.argv)
2240 def restore_or_create(self):
2241 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2242 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2243 if r==2: return None
2244 return 'restore' if r==1 else 'create'
2247 def verify_seed(self):
2248 r = self.seed_dialog(False)
2249 if r != self.wallet.seed:
2250 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2257 def seed_dialog(self, is_restore=True):
2261 vbox = QVBoxLayout()
2263 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2265 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2267 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2270 label.setWordWrap(True)
2271 vbox.addWidget(label)
2273 seed_e = QTextEdit()
2274 seed_e.setMaximumHeight(100)
2275 vbox.addWidget(seed_e)
2278 grid = QGridLayout()
2280 gap_e = AmountEdit(None, True)
2282 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2283 grid.addWidget(gap_e, 2, 1)
2284 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2285 vbox.addLayout(grid)
2287 vbox.addLayout(ok_cancel_buttons(d))
2290 if not d.exec_(): return
2293 seed = str(seed_e.toPlainText())
2297 seed = mnemonic.mn_decode( seed.split() )
2299 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2303 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2310 gap = int(unicode(gap_e.text()))
2312 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2317 def network_dialog(self):
2318 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2321 def show_seed(self):
2322 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2324 def password_dialog(self):
2325 if self.wallet.seed:
2326 ElectrumWindow.change_password_dialog(self.wallet)
2329 def restore_wallet(self):
2330 wallet = self.wallet
2331 # wait until we are connected, because the user might have selected another server
2332 if not wallet.interface.is_connected:
2333 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2334 waiting_dialog(waiting)
2336 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2337 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2339 wallet.set_up_to_date(False)
2340 wallet.interface.poke('synchronizer')
2341 waiting_dialog(waiting)
2342 if wallet.is_found():
2343 print_error( "Recovery successful" )
2345 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2352 w = ElectrumWindow(self.wallet, self.config)
2353 if url: w.set_url(url)