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):
239 def changeEvent(self, event):
240 flags = self.windowFlags();
241 if event and event.type() == QtCore.QEvent.WindowStateChange:
242 if self.windowState() & QtCore.Qt.WindowMinimized:
243 self.build_menu(True)
244 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
245 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
246 # Electrum from closing.
247 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
248 # self.setWindowFlags(flags & ~Qt.ToolTip)
249 elif event.oldState() & QtCore.Qt.WindowMinimized:
250 self.build_menu(False)
251 #self.setWindowFlags(flags | Qt.ToolTip)
253 def build_menu(self, is_hidden = False):
255 if self.isMinimized():
256 m.addAction(_("Show"), self.showNormal)
258 m.addAction(_("Hide"), self.showMinimized)
261 m.addAction(_("Exit Electrum"), self.close)
262 self.tray.setContextMenu(m)
264 def tray_activated(self, reason):
265 if reason == QSystemTrayIcon.DoubleClick:
268 def __init__(self, wallet, config):
269 QMainWindow.__init__(self)
270 self._close_electrum = False
274 self.current_account = self.config.get("current_account", None)
276 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
277 self.tray = QSystemTrayIcon(self.icon, self)
278 self.tray.setToolTip('Electrum')
279 self.tray.activated.connect(self.tray_activated)
285 self.create_status_bar()
287 self.need_update = threading.Event()
288 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
289 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
290 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
291 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
292 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
294 self.expert_mode = config.get('classic_expert_mode', False)
295 self.decimal_point = config.get('decimal_point', 8)
297 set_language(config.get('language'))
299 self.funds_error = False
300 self.completions = QStringListModel()
302 self.tabs = tabs = QTabWidget(self)
303 self.column_widths = self.config.get("column_widths", default_column_widths )
304 tabs.addTab(self.create_history_tab(), _('History') )
305 tabs.addTab(self.create_send_tab(), _('Send') )
306 tabs.addTab(self.create_receive_tab(), _('Receive') )
307 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
308 tabs.addTab(self.create_console_tab(), _('Console') )
309 tabs.setMinimumSize(600, 400)
310 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
311 self.setCentralWidget(tabs)
313 g = self.config.get("winpos-qt",[100, 100, 840, 400])
314 self.setGeometry(g[0], g[1], g[2], g[3])
315 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
316 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
317 self.setWindowTitle( title )
321 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
322 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
323 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
324 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
325 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
327 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
328 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
329 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
330 self.history_list.setFocus(True)
332 self.exchanger = exchange_rate.Exchanger(self)
333 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
335 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
336 if platform.system() == 'Windows':
337 n = 3 if self.wallet.seed else 2
338 tabs.setCurrentIndex (n)
339 tabs.setCurrentIndex (0)
341 # set initial message
342 self.console.showMessage(self.wallet.interface.banner)
344 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
345 self.notify_transactions()
347 # plugins that need to change the GUI do it here
348 self.run_hook('init_gui')
350 def select_wallet_file(self):
351 wallet_folder = self.wallet.config.path
352 re.sub("(\/\w*.dat)$", "", wallet_folder)
353 file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
357 self.load_wallet(file_name)
360 def init_menubar(self):
363 electrum_menu = menubar.addMenu(_("&File"))
364 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
365 open_wallet_action.triggered.connect(self.select_wallet_file)
367 preferences_name = _("Preferences")
368 if sys.platform == 'darwin':
369 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
371 preferences_menu = electrum_menu.addAction(preferences_name)
372 preferences_menu.triggered.connect(self.settings_dialog)
373 electrum_menu.addSeparator()
375 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
377 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
378 raw_transaction_file.triggered.connect(self.do_process_from_file)
380 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
381 raw_transaction_text.triggered.connect(self.do_process_from_text)
383 electrum_menu.addSeparator()
384 quit_item = electrum_menu.addAction(_("&Close"))
385 quit_item.triggered.connect(self.close)
387 wallet_menu = menubar.addMenu(_("&Wallet"))
388 wallet_backup = wallet_menu.addAction(_("&Create backup"))
389 wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
391 show_menu = wallet_menu.addMenu(_("Show"))
394 show_seed = show_menu.addAction(_("&Seed"))
395 show_seed.triggered.connect(self.show_seed_dialog)
397 show_mpk = show_menu.addAction(_("&Master Public Key"))
398 show_mpk.triggered.connect(self.show_master_public_key)
400 wallet_menu.addSeparator()
401 new_contact = wallet_menu.addAction(_("&New contact"))
402 new_contact.triggered.connect(self.new_contact_dialog)
404 new_account = wallet_menu.addAction(_("&New account"))
405 new_account.triggered.connect(self.new_account_dialog)
407 import_menu = menubar.addMenu(_("&Import"))
408 in_labels = import_menu.addAction(_("&Labels"))
409 in_labels.triggered.connect(self.do_import_labels)
411 in_private_keys = import_menu.addAction(_("&Private keys"))
412 in_private_keys.triggered.connect(self.do_import_privkey)
414 export_menu = menubar.addMenu(_("&Export"))
415 ex_private_keys = export_menu.addAction(_("&Private keys"))
416 ex_private_keys.triggered.connect(self.do_export_privkeys)
418 ex_history = export_menu.addAction(_("&History"))
419 ex_history.triggered.connect(self.do_export_history)
421 ex_labels = export_menu.addAction(_("&Labels"))
422 ex_labels.triggered.connect(self.do_export_labels)
424 help_menu = menubar.addMenu(_("&Help"))
425 doc_open = help_menu.addAction(_("&Documentation"))
426 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
427 web_open = help_menu.addAction(_("&Official website"))
428 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
430 self.setMenuBar(menubar)
432 def load_wallet(self, filename):
435 config = electrum.SimpleConfig({'wallet_path': filename})
436 if not config.wallet_file_exists:
437 self.show_message("file not found "+ filename)
440 #self.wallet.verifier.stop()
441 interface = self.wallet.interface
442 verifier = self.wallet.verifier
443 self.wallet.synchronizer.stop()
446 self.wallet = electrum.Wallet(self.config)
447 self.wallet.interface = interface
448 self.wallet.verifier = verifier
450 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
455 def notify_transactions(self):
456 print_error("Notifying GUI")
457 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
458 # Combine the transactions if there are more then three
459 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
462 for tx in self.wallet.interface.pending_transactions_for_notifications:
463 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
467 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
468 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
470 self.wallet.interface.pending_transactions_for_notifications = []
472 for tx in self.wallet.interface.pending_transactions_for_notifications:
474 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
475 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
477 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
479 def notify(self, message):
480 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
483 def init_plugins(self):
484 import imp, pkgutil, __builtin__
485 if __builtin__.use_local_modules:
486 fp, pathname, description = imp.find_module('plugins')
487 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
488 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
489 imp.load_module('electrum_plugins', fp, pathname, description)
490 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
492 import electrum_plugins
493 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
494 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
499 self.plugins.append( p.Plugin(self) )
501 print_msg("Error:cannot initialize plugin",p)
502 traceback.print_exc(file=sys.stdout)
505 def run_hook(self, name, *args):
506 for p in self.plugins:
507 if not p.is_enabled():
516 print_error("Plugin error")
517 traceback.print_exc(file=sys.stdout)
522 def set_label(self, name, text = None):
524 old_text = self.wallet.labels.get(name)
527 self.wallet.labels[name] = text
528 self.wallet.config.set_key('labels', self.wallet.labels)
532 self.wallet.labels.pop(name)
534 self.run_hook('set_label', name, text, changed)
538 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
539 def getOpenFileName(self, title, filter = None):
540 directory = self.config.get('io_dir', os.path.expanduser('~'))
541 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
542 if fileName and directory != os.path.dirname(fileName):
543 self.config.set_key('io_dir', os.path.dirname(fileName), True)
546 def getSaveFileName(self, title, filename, filter = None):
547 directory = self.config.get('io_dir', os.path.expanduser('~'))
548 path = os.path.join( directory, filename )
549 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
550 if fileName and directory != os.path.dirname(fileName):
551 self.config.set_key('io_dir', os.path.dirname(fileName), True)
557 QMainWindow.close(self)
558 self.run_hook('close_main_window')
560 def connect_slots(self, sender):
561 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
562 self.previous_payto_e=''
564 def timer_actions(self):
565 if self.need_update.is_set():
567 self.need_update.clear()
568 self.run_hook('timer_actions')
570 def format_amount(self, x, is_diff=False, whitespaces=False):
571 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
573 def read_amount(self, x):
574 if x in['.', '']: return None
575 p = pow(10, self.decimal_point)
576 return int( p * Decimal(x) )
579 assert self.decimal_point in [5,8]
580 return "BTC" if self.decimal_point == 8 else "mBTC"
582 def update_status(self):
583 if self.wallet.interface and self.wallet.interface.is_connected:
584 if not self.wallet.up_to_date:
585 text = _("Synchronizing...")
586 icon = QIcon(":icons/status_waiting.png")
588 c, u = self.wallet.get_account_balance(self.current_account)
589 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
590 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
591 text += self.create_quote_text(Decimal(c+u)/100000000)
592 self.tray.setToolTip(text)
593 icon = QIcon(":icons/status_connected.png")
595 text = _("Not connected")
596 icon = QIcon(":icons/status_disconnected.png")
598 self.balance_label.setText(text)
599 self.status_button.setIcon( icon )
601 def update_wallet(self):
603 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
604 self.update_history_tab()
605 self.update_receive_tab()
606 self.update_contacts_tab()
607 self.update_completions()
610 def create_quote_text(self, btc_balance):
611 quote_currency = self.config.get("currency", "None")
612 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
613 if quote_balance is None:
616 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
619 def create_history_tab(self):
620 self.history_list = l = MyTreeWidget(self)
622 for i,width in enumerate(self.column_widths['history']):
623 l.setColumnWidth(i, width)
624 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
625 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
626 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
628 l.setContextMenuPolicy(Qt.CustomContextMenu)
629 l.customContextMenuRequested.connect(self.create_history_menu)
633 def create_history_menu(self, position):
634 self.history_list.selectedIndexes()
635 item = self.history_list.currentItem()
637 tx_hash = str(item.data(0, Qt.UserRole).toString())
638 if not tx_hash: return
640 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
641 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
642 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
643 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
646 def show_tx_details(self, tx):
647 dialog = QDialog(self)
649 dialog.setWindowTitle(_("Transaction Details"))
651 dialog.setLayout(vbox)
652 dialog.setMinimumSize(600,300)
655 if tx_hash in self.wallet.transactions.keys():
656 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
657 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
659 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
665 vbox.addWidget(QLabel("Transaction ID:"))
666 e = QLineEdit(tx_hash)
670 vbox.addWidget(QLabel("Date: %s"%time_str))
671 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
674 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
675 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
677 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
678 vbox.addWidget(QLabel("Transaction fee: unknown"))
680 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
682 vbox.addWidget( self.generate_transaction_information_widget(tx) )
684 ok_button = QPushButton(_("Close"))
685 ok_button.setDefault(True)
686 ok_button.clicked.connect(dialog.accept)
690 hbox.addWidget(ok_button)
694 def tx_label_clicked(self, item, column):
695 if column==2 and item.isSelected():
697 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
698 self.history_list.editItem( item, column )
699 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
702 def tx_label_changed(self, item, column):
706 tx_hash = str(item.data(0, Qt.UserRole).toString())
707 tx = self.wallet.transactions.get(tx_hash)
708 text = unicode( item.text(2) )
709 self.set_label(tx_hash, text)
711 item.setForeground(2, QBrush(QColor('black')))
713 text = self.wallet.get_default_label(tx_hash)
714 item.setText(2, text)
715 item.setForeground(2, QBrush(QColor('gray')))
719 def edit_label(self, is_recv):
720 l = self.receive_list if is_recv else self.contacts_list
721 item = l.currentItem()
722 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
723 l.editItem( item, 1 )
724 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
728 def address_label_clicked(self, item, column, l, column_addr, column_label):
729 if column == column_label and item.isSelected():
730 is_editable = item.data(0, 32).toBool()
733 addr = unicode( item.text(column_addr) )
734 label = unicode( item.text(column_label) )
735 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
736 l.editItem( item, column )
737 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
740 def address_label_changed(self, item, column, l, column_addr, column_label):
741 if column == column_label:
742 addr = unicode( item.text(column_addr) )
743 text = unicode( item.text(column_label) )
744 is_editable = item.data(0, 32).toBool()
748 changed = self.set_label(addr, text)
750 self.update_history_tab()
751 self.update_completions()
753 self.current_item_changed(item)
755 self.run_hook('item_changed', item, column)
758 def current_item_changed(self, a):
759 self.run_hook('current_item_changed', a)
763 def update_history_tab(self):
765 self.history_list.clear()
766 for item in self.wallet.get_tx_history(self.current_account):
767 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
770 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
775 time_str = 'unverified'
776 icon = QIcon(":icons/unconfirmed.png")
779 icon = QIcon(":icons/unconfirmed.png")
781 icon = QIcon(":icons/clock%d.png"%conf)
783 icon = QIcon(":icons/confirmed.png")
785 if value is not None:
786 v_str = self.format_amount(value, True, whitespaces=True)
790 balance_str = self.format_amount(balance, whitespaces=True)
793 label, is_default_label = self.wallet.get_label(tx_hash)
795 label = _('Pruned transaction outputs')
796 is_default_label = False
798 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
799 item.setFont(2, QFont(MONOSPACE_FONT))
800 item.setFont(3, QFont(MONOSPACE_FONT))
801 item.setFont(4, QFont(MONOSPACE_FONT))
803 item.setForeground(3, QBrush(QColor("#BC1E1E")))
805 item.setData(0, Qt.UserRole, tx_hash)
806 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
808 item.setForeground(2, QBrush(QColor('grey')))
810 item.setIcon(0, icon)
811 self.history_list.insertTopLevelItem(0,item)
814 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
817 def create_send_tab(self):
822 grid.setColumnMinimumWidth(3,300)
823 grid.setColumnStretch(5,1)
826 self.payto_e = QLineEdit()
827 grid.addWidget(QLabel(_('Pay to')), 1, 0)
828 grid.addWidget(self.payto_e, 1, 1, 1, 3)
830 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)
832 completer = QCompleter()
833 completer.setCaseSensitivity(False)
834 self.payto_e.setCompleter(completer)
835 completer.setModel(self.completions)
837 self.message_e = QLineEdit()
838 grid.addWidget(QLabel(_('Description')), 2, 0)
839 grid.addWidget(self.message_e, 2, 1, 1, 3)
840 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)
842 self.amount_e = AmountEdit(self.base_unit)
843 grid.addWidget(QLabel(_('Amount')), 3, 0)
844 grid.addWidget(self.amount_e, 3, 1, 1, 2)
845 grid.addWidget(HelpButton(
846 _('Amount to be sent.') + '\n\n' \
847 + _('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.') \
848 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
850 self.fee_e = AmountEdit(self.base_unit)
851 grid.addWidget(QLabel(_('Fee')), 4, 0)
852 grid.addWidget(self.fee_e, 4, 1, 1, 2)
853 grid.addWidget(HelpButton(
854 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
855 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
856 + _('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)
859 b = EnterButton(_("Send"), self.do_send)
861 b = EnterButton(_("Create unsigned transaction"), self.do_send)
862 grid.addWidget(b, 6, 1)
864 b = EnterButton(_("Clear"),self.do_clear)
865 grid.addWidget(b, 6, 2)
867 self.payto_sig = QLabel('')
868 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
870 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
871 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
880 def entry_changed( is_fee ):
881 self.funds_error = False
883 if self.amount_e.is_shortcut:
884 self.amount_e.is_shortcut = False
885 c, u = self.wallet.get_account_balance(self.current_account)
886 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
887 fee = self.wallet.estimated_fee(inputs)
889 self.amount_e.setText( self.format_amount(amount) )
890 self.fee_e.setText( self.format_amount( fee ) )
893 amount = self.read_amount(str(self.amount_e.text()))
894 fee = self.read_amount(str(self.fee_e.text()))
896 if not is_fee: fee = None
899 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
901 self.fee_e.setText( self.format_amount( fee ) )
904 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
908 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
909 self.funds_error = True
910 text = _( "Not enough funds" )
911 c, u = self.wallet.get_frozen_balance()
912 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
914 self.statusBar().showMessage(text)
915 self.amount_e.setPalette(palette)
916 self.fee_e.setPalette(palette)
918 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
919 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
921 self.run_hook('create_send_tab', grid)
925 def update_completions(self):
927 for addr,label in self.wallet.labels.items():
928 if addr in self.wallet.addressbook:
929 l.append( label + ' <' + addr + '>')
931 self.run_hook('update_completions', l)
932 self.completions.setStringList(l)
936 return lambda s, *args: s.do_protect(func, args)
941 label = unicode( self.message_e.text() )
942 r = unicode( self.payto_e.text() )
945 # label or alias, with address in brackets
946 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
947 to_address = m.group(2) if m else r
949 if not is_valid(to_address):
950 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
954 amount = self.read_amount(unicode( self.amount_e.text()))
956 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
959 fee = self.read_amount(unicode( self.fee_e.text()))
961 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
964 confirm_amount = self.config.get('confirm_amount', 100000000)
965 if amount >= confirm_amount:
966 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
969 self.send_tx(to_address, amount, fee, label)
973 def send_tx(self, to_address, amount, fee, label, password):
976 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
977 except BaseException, e:
978 traceback.print_exc(file=sys.stdout)
979 self.show_message(str(e))
982 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
983 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
986 self.run_hook('send_tx', tx)
989 self.set_label(tx.hash(), label)
992 h = self.wallet.send_tx(tx)
993 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
994 status, msg = self.wallet.receive_tx( h )
996 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
998 self.update_contacts_tab()
1000 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1002 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1004 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1005 with open(fileName,'w') as f:
1006 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1007 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1009 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1014 def set_url(self, url):
1015 address, amount, label, message, signature, identity, url = util.parse_url(url)
1016 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1018 if label and self.wallet.labels.get(address) != label:
1019 if self.question('Give label "%s" to address %s ?'%(label,address)):
1020 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1021 self.wallet.addressbook.append(address)
1022 self.set_label(address, label)
1024 self.run_hook('set_url', url, self.show_message, self.question)
1026 self.tabs.setCurrentIndex(1)
1027 label = self.wallet.labels.get(address)
1028 m_addr = label + ' <'+ address +'>' if label else address
1029 self.payto_e.setText(m_addr)
1031 self.message_e.setText(message)
1032 self.amount_e.setText(amount)
1034 self.set_frozen(self.payto_e,True)
1035 self.set_frozen(self.amount_e,True)
1036 self.set_frozen(self.message_e,True)
1037 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1039 self.payto_sig.setVisible(False)
1042 self.payto_sig.setVisible(False)
1043 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1045 self.set_frozen(e,False)
1046 self.update_status()
1048 def set_frozen(self,entry,frozen):
1050 entry.setReadOnly(True)
1051 entry.setFrame(False)
1052 palette = QPalette()
1053 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1054 entry.setPalette(palette)
1056 entry.setReadOnly(False)
1057 entry.setFrame(True)
1058 palette = QPalette()
1059 palette.setColor(entry.backgroundRole(), QColor('white'))
1060 entry.setPalette(palette)
1063 def toggle_freeze(self,addr):
1065 if addr in self.wallet.frozen_addresses:
1066 self.wallet.unfreeze(addr)
1068 self.wallet.freeze(addr)
1069 self.update_receive_tab()
1071 def toggle_priority(self,addr):
1073 if addr in self.wallet.prioritized_addresses:
1074 self.wallet.unprioritize(addr)
1076 self.wallet.prioritize(addr)
1077 self.update_receive_tab()
1080 def create_list_tab(self, headers):
1081 "generic tab creation method"
1082 l = MyTreeWidget(self)
1083 l.setColumnCount( len(headers) )
1084 l.setHeaderLabels( headers )
1087 vbox = QVBoxLayout()
1094 vbox.addWidget(buttons)
1096 hbox = QHBoxLayout()
1099 buttons.setLayout(hbox)
1104 def create_receive_tab(self):
1105 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1106 l.setContextMenuPolicy(Qt.CustomContextMenu)
1107 l.customContextMenuRequested.connect(self.create_receive_menu)
1108 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1109 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1110 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1111 self.receive_list = l
1112 self.receive_buttons_hbox = hbox
1117 def receive_tab_set_mode(self, i):
1118 self.save_column_widths()
1119 self.expert_mode = (i == 1)
1120 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1121 self.update_receive_tab()
1124 def save_column_widths(self):
1125 if not self.expert_mode:
1126 widths = [ self.receive_list.columnWidth(0) ]
1129 for i in range(self.receive_list.columnCount() -1):
1130 widths.append(self.receive_list.columnWidth(i))
1131 self.column_widths["receive"][self.expert_mode] = widths
1133 self.column_widths["history"] = []
1134 for i in range(self.history_list.columnCount() - 1):
1135 self.column_widths["history"].append(self.history_list.columnWidth(i))
1137 self.column_widths["contacts"] = []
1138 for i in range(self.contacts_list.columnCount() - 1):
1139 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1141 self.config.set_key("column_widths", self.column_widths, True)
1144 def create_contacts_tab(self):
1145 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1146 l.setContextMenuPolicy(Qt.CustomContextMenu)
1147 l.customContextMenuRequested.connect(self.create_contact_menu)
1148 for i,width in enumerate(self.column_widths['contacts']):
1149 l.setColumnWidth(i, width)
1151 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1152 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1153 self.contacts_list = l
1154 self.contacts_buttons_hbox = hbox
1159 def delete_imported_key(self, addr):
1160 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1161 self.wallet.delete_imported_key(addr)
1162 self.update_receive_tab()
1163 self.update_history_tab()
1166 def create_receive_menu(self, position):
1167 # fixme: this function apparently has a side effect.
1168 # if it is not called the menu pops up several times
1169 #self.receive_list.selectedIndexes()
1171 item = self.receive_list.itemAt(position)
1173 addr = unicode(item.text(0))
1174 if not is_valid(addr):
1175 item.setExpanded(not item.isExpanded())
1178 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1179 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1180 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1181 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1182 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1183 if addr in self.wallet.imported_keys:
1184 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1186 if self.expert_mode:
1187 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1188 menu.addAction(t, lambda: self.toggle_freeze(addr))
1189 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1190 menu.addAction(t, lambda: self.toggle_priority(addr))
1192 self.run_hook('receive_menu', menu)
1193 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1196 def payto(self, addr):
1198 label = self.wallet.labels.get(addr)
1199 m_addr = label + ' <' + addr + '>' if label else addr
1200 self.tabs.setCurrentIndex(1)
1201 self.payto_e.setText(m_addr)
1202 self.amount_e.setFocus()
1205 def delete_contact(self, x):
1206 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1207 self.wallet.delete_contact(x)
1208 self.set_label(x, None)
1209 self.update_history_tab()
1210 self.update_contacts_tab()
1211 self.update_completions()
1214 def create_contact_menu(self, position):
1215 item = self.contacts_list.itemAt(position)
1217 addr = unicode(item.text(0))
1218 label = unicode(item.text(1))
1219 is_editable = item.data(0,32).toBool()
1220 payto_addr = item.data(0,33).toString()
1222 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1223 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1224 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1226 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1227 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1229 self.run_hook('create_contact_menu', menu, item)
1230 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1233 def update_receive_item(self, item):
1234 item.setFont(0, QFont(MONOSPACE_FONT))
1235 address = str(item.data(0,0).toString())
1236 label = self.wallet.labels.get(address,'')
1237 item.setData(1,0,label)
1238 item.setData(0,32, True) # is editable
1240 self.run_hook('update_receive_item', address, item)
1242 c, u = self.wallet.get_addr_balance(address)
1243 balance = self.format_amount(c + u)
1244 item.setData(2,0,balance)
1246 if self.expert_mode:
1247 if address in self.wallet.frozen_addresses:
1248 item.setBackgroundColor(0, QColor('lightblue'))
1249 elif address in self.wallet.prioritized_addresses:
1250 item.setBackgroundColor(0, QColor('lightgreen'))
1253 def update_receive_tab(self):
1254 l = self.receive_list
1257 l.setColumnHidden(2, not self.expert_mode)
1258 l.setColumnHidden(3, not self.expert_mode)
1259 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1260 l.setColumnWidth(i, width)
1262 if self.current_account is None:
1263 account_items = self.wallet.accounts.items()
1264 elif self.current_account != -1:
1265 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1269 for k, account in account_items:
1270 name = account.get_name()
1271 c,u = self.wallet.get_account_balance(k)
1272 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1273 l.addTopLevelItem(account_item)
1274 account_item.setExpanded(True)
1276 for is_change in ([0,1] if self.expert_mode else [0]):
1277 if self.expert_mode:
1278 name = "Receiving" if not is_change else "Change"
1279 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1280 account_item.addChild(seq_item)
1281 if not is_change: seq_item.setExpanded(True)
1283 seq_item = account_item
1287 for address in account.get_addresses(is_change):
1288 h = self.wallet.history.get(address,[])
1292 if gap > self.wallet.gap_limit:
1297 num_tx = '*' if h == ['*'] else "%d"%len(h)
1298 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1299 self.update_receive_item(item)
1301 item.setBackgroundColor(1, QColor('red'))
1302 seq_item.addChild(item)
1305 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1306 c,u = self.wallet.get_imported_balance()
1307 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1308 l.addTopLevelItem(account_item)
1309 account_item.setExpanded(True)
1310 for address in self.wallet.imported_keys.keys():
1311 item = QTreeWidgetItem( [ address, '', '', ''] )
1312 self.update_receive_item(item)
1313 account_item.addChild(item)
1316 # we use column 1 because column 0 may be hidden
1317 l.setCurrentItem(l.topLevelItem(0),1)
1320 def update_contacts_tab(self):
1321 l = self.contacts_list
1324 for address in self.wallet.addressbook:
1325 label = self.wallet.labels.get(address,'')
1326 n = self.wallet.get_num_tx(address)
1327 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1328 item.setFont(0, QFont(MONOSPACE_FONT))
1329 # 32 = label can be edited (bool)
1330 item.setData(0,32, True)
1332 item.setData(0,33, address)
1333 l.addTopLevelItem(item)
1335 self.run_hook('update_contacts_tab', l)
1336 l.setCurrentItem(l.topLevelItem(0))
1340 def create_console_tab(self):
1341 from qt_console import Console
1342 self.console = console = Console()
1343 self.console.history = self.config.get("console-history",[])
1344 self.console.history_index = len(self.console.history)
1346 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1347 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1349 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1351 def mkfunc(f, method):
1352 return lambda *args: apply( f, (method, args, self.password_dialog ))
1354 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1355 methods[m] = mkfunc(c._run, m)
1357 console.updateNamespace(methods)
1360 def change_account(self,s):
1361 if s == _("All accounts"):
1362 self.current_account = None
1364 accounts = self.wallet.get_accounts()
1365 for k, v in accounts.items():
1367 self.current_account = k
1368 self.update_history_tab()
1369 self.update_status()
1370 self.update_receive_tab()
1372 def create_status_bar(self):
1375 sb.setFixedHeight(35)
1376 qtVersion = qVersion()
1378 self.balance_label = QLabel("")
1379 sb.addWidget(self.balance_label)
1381 update_notification = UpdateLabel(self.config)
1382 if(update_notification.new_version):
1383 sb.addPermanentWidget(update_notification)
1385 accounts = self.wallet.get_accounts()
1386 if len(accounts) > 1:
1387 from_combo = QComboBox()
1388 from_combo.addItems([_("All accounts")] + accounts.values())
1389 from_combo.setCurrentIndex(0)
1390 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1391 sb.addPermanentWidget(from_combo)
1393 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1394 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1395 if self.wallet.seed:
1396 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1397 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1398 sb.addPermanentWidget( self.password_button )
1399 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1400 if self.wallet.seed:
1401 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1402 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1403 sb.addPermanentWidget( self.status_button )
1405 self.run_hook('create_status_bar', (sb,))
1407 self.setStatusBar(sb)
1411 self.config.set_key('gui', 'lite', True)
1414 self.lite.mini.show()
1416 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1417 self.lite.main(None)
1419 def new_contact_dialog(self):
1420 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1421 address = unicode(text)
1423 if is_valid(address):
1424 self.wallet.add_contact(address)
1425 self.update_contacts_tab()
1426 self.update_history_tab()
1427 self.update_completions()
1429 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1431 def new_account_dialog(self):
1432 text, ok = QInputDialog.getText(self, _('New Account'), _('Name') + ':')
1433 name = unicode(text)
1435 self.wallet.create_new_account(name)
1436 self.wallet.synchronize()
1437 self.update_contacts_tab()
1438 self.update_history_tab()
1439 self.update_completions()
1441 def show_master_public_key(self):
1442 dialog = QDialog(self)
1444 dialog.setWindowTitle(_("Master Public Key"))
1446 main_text = QTextEdit()
1447 main_text.setText(self.wallet.get_master_public_key())
1448 main_text.setReadOnly(True)
1449 main_text.setMaximumHeight(170)
1450 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1452 ok_button = QPushButton(_("OK"))
1453 ok_button.setDefault(True)
1454 ok_button.clicked.connect(dialog.accept)
1456 main_layout = QGridLayout()
1457 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1459 main_layout.addWidget(main_text, 1, 0)
1460 main_layout.addWidget(qrw, 1, 1 )
1462 vbox = QVBoxLayout()
1463 vbox.addLayout(main_layout)
1464 hbox = QHBoxLayout()
1466 hbox.addWidget(ok_button)
1467 vbox.addLayout(hbox)
1469 dialog.setLayout(vbox)
1474 def show_seed_dialog(self, password):
1475 if not self.wallet.seed:
1476 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1479 seed = self.wallet.decode_seed(password)
1481 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1483 self.show_seed(seed, self.wallet.imported_keys, self)
1487 def show_seed(self, seed, imported_keys, parent=None):
1488 dialog = QDialog(parent)
1490 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1492 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1494 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1496 seed_text = QTextEdit(brainwallet)
1497 seed_text.setReadOnly(True)
1498 seed_text.setMaximumHeight(130)
1500 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1501 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1502 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1503 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1505 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1506 label2 = QLabel(msg2)
1507 label2.setWordWrap(True)
1510 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1511 logo.setMaximumWidth(60)
1513 qrw = QRCodeWidget(seed)
1515 ok_button = QPushButton(_("OK"))
1516 ok_button.setDefault(True)
1517 ok_button.clicked.connect(dialog.accept)
1519 grid = QGridLayout()
1520 #main_layout.addWidget(logo, 0, 0)
1522 grid.addWidget(logo, 0, 0)
1523 grid.addWidget(label1, 0, 1)
1525 grid.addWidget(seed_text, 1, 0, 1, 2)
1527 grid.addWidget(qrw, 0, 2, 2, 1)
1529 vbox = QVBoxLayout()
1530 vbox.addLayout(grid)
1531 vbox.addWidget(label2)
1533 hbox = QHBoxLayout()
1535 hbox.addWidget(ok_button)
1536 vbox.addLayout(hbox)
1538 dialog.setLayout(vbox)
1541 def show_qrcode(self, data, title = "QR code"):
1545 d.setWindowTitle(title)
1546 d.setMinimumSize(270, 300)
1547 vbox = QVBoxLayout()
1548 qrw = QRCodeWidget(data)
1549 vbox.addWidget(qrw, 1)
1550 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1551 hbox = QHBoxLayout()
1555 filename = "qrcode.bmp"
1556 bmp.save_qrcode(qrw.qr, filename)
1557 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1559 b = QPushButton(_("Save"))
1561 b.clicked.connect(print_qr)
1563 b = QPushButton(_("Close"))
1565 b.clicked.connect(d.accept)
1568 vbox.addLayout(hbox)
1573 def do_protect(self, func, args):
1574 if self.wallet.use_encryption:
1575 password = self.password_dialog()
1581 if args != (False,):
1582 args = (self,) + args + (password,)
1584 args = (self,password)
1589 def show_private_key(self, address, password):
1590 if not address: return
1592 pk = self.wallet.get_private_key(address, password)
1593 except BaseException, e:
1594 self.show_message(str(e))
1596 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1600 def do_sign(self, address, message, signature, password):
1602 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1603 signature.setText(sig)
1604 except BaseException, e:
1605 self.show_message(str(e))
1607 def sign_message(self, address):
1608 if not address: return
1611 d.setWindowTitle(_('Sign Message'))
1612 d.setMinimumSize(410, 290)
1614 tab_widget = QTabWidget()
1616 layout = QGridLayout(tab)
1618 sign_address = QLineEdit()
1620 sign_address.setText(address)
1621 layout.addWidget(QLabel(_('Address')), 1, 0)
1622 layout.addWidget(sign_address, 1, 1)
1624 sign_message = QTextEdit()
1625 layout.addWidget(QLabel(_('Message')), 2, 0)
1626 layout.addWidget(sign_message, 2, 1)
1627 layout.setRowStretch(2,3)
1629 sign_signature = QTextEdit()
1630 layout.addWidget(QLabel(_('Signature')), 3, 0)
1631 layout.addWidget(sign_signature, 3, 1)
1632 layout.setRowStretch(3,1)
1635 hbox = QHBoxLayout()
1636 b = QPushButton(_("Sign"))
1638 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1639 b = QPushButton(_("Close"))
1640 b.clicked.connect(d.accept)
1642 layout.addLayout(hbox, 4, 1)
1643 tab_widget.addTab(tab, _("Sign"))
1647 layout = QGridLayout(tab)
1649 verify_address = QLineEdit()
1650 layout.addWidget(QLabel(_('Address')), 1, 0)
1651 layout.addWidget(verify_address, 1, 1)
1653 verify_message = QTextEdit()
1654 layout.addWidget(QLabel(_('Message')), 2, 0)
1655 layout.addWidget(verify_message, 2, 1)
1656 layout.setRowStretch(2,3)
1658 verify_signature = QTextEdit()
1659 layout.addWidget(QLabel(_('Signature')), 3, 0)
1660 layout.addWidget(verify_signature, 3, 1)
1661 layout.setRowStretch(3,1)
1664 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1665 self.show_message(_("Signature verified"))
1667 self.show_message(_("Error: wrong signature"))
1669 hbox = QHBoxLayout()
1670 b = QPushButton(_("Verify"))
1671 b.clicked.connect(do_verify)
1673 b = QPushButton(_("Close"))
1674 b.clicked.connect(d.accept)
1676 layout.addLayout(hbox, 4, 1)
1677 tab_widget.addTab(tab, _("Verify"))
1679 vbox = QVBoxLayout()
1680 vbox.addWidget(tab_widget)
1687 def question(self, msg):
1688 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1690 def show_message(self, msg):
1691 QMessageBox.information(self, _('Message'), msg, _('OK'))
1693 def password_dialog(self ):
1700 vbox = QVBoxLayout()
1701 msg = _('Please enter your password')
1702 vbox.addWidget(QLabel(msg))
1704 grid = QGridLayout()
1706 grid.addWidget(QLabel(_('Password')), 1, 0)
1707 grid.addWidget(pw, 1, 1)
1708 vbox.addLayout(grid)
1710 vbox.addLayout(ok_cancel_buttons(d))
1713 self.run_hook('password_dialog', pw, grid, 1)
1714 if not d.exec_(): return
1715 return unicode(pw.text())
1722 def change_password_dialog( wallet, parent=None ):
1725 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1733 new_pw = QLineEdit()
1734 new_pw.setEchoMode(2)
1735 conf_pw = QLineEdit()
1736 conf_pw.setEchoMode(2)
1738 vbox = QVBoxLayout()
1740 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1741 +_('To disable wallet encryption, enter an empty new password.')) \
1742 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1744 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1745 +_("Leave these fields empty if you want to disable encryption.")
1746 vbox.addWidget(QLabel(msg))
1748 grid = QGridLayout()
1751 if wallet.use_encryption:
1752 grid.addWidget(QLabel(_('Password')), 1, 0)
1753 grid.addWidget(pw, 1, 1)
1755 grid.addWidget(QLabel(_('New Password')), 2, 0)
1756 grid.addWidget(new_pw, 2, 1)
1758 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1759 grid.addWidget(conf_pw, 3, 1)
1760 vbox.addLayout(grid)
1762 vbox.addLayout(ok_cancel_buttons(d))
1765 if not d.exec_(): return
1767 password = unicode(pw.text()) if wallet.use_encryption else None
1768 new_password = unicode(new_pw.text())
1769 new_password2 = unicode(conf_pw.text())
1772 seed = wallet.decode_seed(password)
1774 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1777 if new_password != new_password2:
1778 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1779 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1782 wallet.update_password(seed, password, new_password)
1784 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1787 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1790 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1791 parent.password_button.setIcon( icon )
1795 def generate_transaction_information_widget(self, tx):
1796 tabs = QTabWidget(self)
1799 grid_ui = QGridLayout(tab1)
1800 grid_ui.setColumnStretch(0,1)
1801 tabs.addTab(tab1, _('Outputs') )
1803 tree_widget = MyTreeWidget(self)
1804 tree_widget.setColumnCount(2)
1805 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1806 tree_widget.setColumnWidth(0, 300)
1807 tree_widget.setColumnWidth(1, 50)
1809 for address, value in tx.outputs:
1810 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1811 tree_widget.addTopLevelItem(item)
1813 tree_widget.setMaximumHeight(100)
1815 grid_ui.addWidget(tree_widget)
1818 grid_ui = QGridLayout(tab2)
1819 grid_ui.setColumnStretch(0,1)
1820 tabs.addTab(tab2, _('Inputs') )
1822 tree_widget = MyTreeWidget(self)
1823 tree_widget.setColumnCount(2)
1824 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1826 for input_line in tx.inputs:
1827 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1828 tree_widget.addTopLevelItem(item)
1830 tree_widget.setMaximumHeight(100)
1832 grid_ui.addWidget(tree_widget)
1836 def tx_dict_from_text(self, txt):
1838 tx_dict = json.loads(str(txt))
1839 assert "hex" in tx_dict.keys()
1840 assert "complete" in tx_dict.keys()
1841 if not tx_dict["complete"]:
1842 assert "input_info" in tx_dict.keys()
1844 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1849 def read_tx_from_file(self):
1850 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1854 with open(fileName, "r") as f:
1855 file_content = f.read()
1856 except (ValueError, IOError, os.error), reason:
1857 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1859 return self.tx_dict_from_text(file_content)
1863 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1865 self.wallet.signrawtransaction(tx, input_info, [], password)
1867 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1869 with open(fileName, "w+") as f:
1870 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1871 self.show_message(_("Transaction saved successfully"))
1874 except BaseException, e:
1875 self.show_message(str(e))
1878 def send_raw_transaction(self, raw_tx, dialog = ""):
1879 result, result_message = self.wallet.sendtx( raw_tx )
1881 self.show_message("Transaction successfully sent: %s" % (result_message))
1885 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1887 def do_process_from_text(self):
1888 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1891 tx_dict = self.tx_dict_from_text(text)
1893 self.create_process_transaction_window(tx_dict)
1895 def do_process_from_file(self):
1896 tx_dict = self.read_tx_from_file()
1898 self.create_process_transaction_window(tx_dict)
1900 def create_process_transaction_window(self, tx_dict):
1901 tx = Transaction(tx_dict["hex"])
1903 dialog = QDialog(self)
1904 dialog.setMinimumWidth(500)
1905 dialog.setWindowTitle(_('Process raw transaction'))
1911 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1912 l.addWidget(QLabel(_("Actions")), 4,0)
1914 if tx_dict["complete"] == False:
1915 l.addWidget(QLabel(_("Unsigned")), 3,1)
1916 if self.wallet.seed :
1917 b = QPushButton("Sign transaction")
1918 input_info = json.loads(tx_dict["input_info"])
1919 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1920 l.addWidget(b, 4, 1)
1922 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1924 l.addWidget(QLabel(_("Signed")), 3,1)
1925 b = QPushButton("Broadcast transaction")
1926 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1929 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1930 cancelButton = QPushButton(_("Cancel"))
1931 cancelButton.clicked.connect(lambda: dialog.done(0))
1932 l.addWidget(cancelButton, 4,2)
1938 def do_export_privkeys(self, password):
1939 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.")))
1942 select_export = _('Select file to export your private keys to')
1943 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1945 with open(fileName, "w+") as csvfile:
1946 transaction = csv.writer(csvfile)
1947 transaction.writerow(["address", "private_key"])
1950 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1951 transaction.writerow(["%34s"%addr,pk])
1953 self.show_message(_("Private keys exported."))
1955 except (IOError, os.error), reason:
1956 export_error_label = _("Electrum was unable to produce a private key-export.")
1957 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1959 except BaseException, e:
1960 self.show_message(str(e))
1964 def do_import_labels(self):
1965 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1966 if not labelsFile: return
1968 f = open(labelsFile, 'r')
1971 for key, value in json.loads(data).items():
1972 self.wallet.labels[key] = value
1974 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1975 except (IOError, os.error), reason:
1976 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1979 def do_export_labels(self):
1980 labels = self.wallet.labels
1982 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1984 with open(fileName, 'w+') as f:
1985 json.dump(labels, f)
1986 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1987 except (IOError, os.error), reason:
1988 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1991 def do_export_history(self):
1992 from gui_lite import csv_transaction
1993 csv_transaction(self.wallet)
1997 def do_import_privkey(self, password):
1998 if not self.wallet.imported_keys:
1999 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2000 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2001 + _('Are you sure you understand what you are doing?'), 3, 4)
2004 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2007 text = str(text).split()
2012 addr = self.wallet.import_key(key, password)
2013 except BaseException as e:
2019 addrlist.append(addr)
2021 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2023 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2024 self.update_receive_tab()
2025 self.update_history_tab()
2028 def settings_dialog(self):
2030 d.setWindowTitle(_('Electrum Settings'))
2032 vbox = QVBoxLayout()
2034 tabs = QTabWidget(self)
2035 self.settings_tab = tabs
2036 vbox.addWidget(tabs)
2039 grid_ui = QGridLayout(tab1)
2040 grid_ui.setColumnStretch(0,1)
2041 tabs.addTab(tab1, _('Display') )
2043 nz_label = QLabel(_('Display zeros'))
2044 grid_ui.addWidget(nz_label, 0, 0)
2045 nz_e = AmountEdit(None,True)
2046 nz_e.setText("%d"% self.wallet.num_zeros)
2047 grid_ui.addWidget(nz_e, 0, 1)
2048 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2049 grid_ui.addWidget(HelpButton(msg), 0, 2)
2050 if not self.config.is_modifiable('num_zeros'):
2051 for w in [nz_e, nz_label]: w.setEnabled(False)
2053 lang_label=QLabel(_('Language') + ':')
2054 grid_ui.addWidget(lang_label, 1, 0)
2055 lang_combo = QComboBox()
2056 from i18n import languages
2057 lang_combo.addItems(languages.values())
2059 index = languages.keys().index(self.config.get("language",''))
2062 lang_combo.setCurrentIndex(index)
2063 grid_ui.addWidget(lang_combo, 1, 1)
2064 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2065 if not self.config.is_modifiable('language'):
2066 for w in [lang_combo, lang_label]: w.setEnabled(False)
2068 currencies = self.exchanger.get_currencies()
2069 currencies.insert(0, "None")
2071 cur_label=QLabel(_('Currency') + ':')
2072 grid_ui.addWidget(cur_label , 2, 0)
2073 cur_combo = QComboBox()
2074 cur_combo.addItems(currencies)
2076 index = currencies.index(self.config.get('currency', "None"))
2079 cur_combo.setCurrentIndex(index)
2080 grid_ui.addWidget(cur_combo, 2, 1)
2081 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2083 expert_cb = QCheckBox(_('Expert mode'))
2084 expert_cb.setChecked(self.expert_mode)
2085 grid_ui.addWidget(expert_cb, 3, 0)
2086 hh = _('In expert mode, your client will:') + '\n' \
2087 + _(' - Show change addresses in the Receive tab') + '\n' \
2088 + _(' - Display the balance of each address') + '\n' \
2089 + _(' - Add freeze/prioritize actions to addresses.')
2090 grid_ui.addWidget(HelpButton(hh), 3, 2)
2091 grid_ui.setRowStretch(4,1)
2095 grid_wallet = QGridLayout(tab2)
2096 grid_wallet.setColumnStretch(0,1)
2097 tabs.addTab(tab2, _('Wallet') )
2099 fee_label = QLabel(_('Transaction fee'))
2100 grid_wallet.addWidget(fee_label, 0, 0)
2101 fee_e = AmountEdit(self.base_unit)
2102 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2103 grid_wallet.addWidget(fee_e, 0, 2)
2104 msg = _('Fee per kilobyte of transaction.') + ' ' \
2105 + _('Recommended value') + ': ' + self.format_amount(50000)
2106 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2107 if not self.config.is_modifiable('fee_per_kb'):
2108 for w in [fee_e, fee_label]: w.setEnabled(False)
2110 usechange_cb = QCheckBox(_('Use change addresses'))
2111 usechange_cb.setChecked(self.wallet.use_change)
2112 grid_wallet.addWidget(usechange_cb, 1, 0)
2113 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2114 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2116 gap_label = QLabel(_('Gap limit'))
2117 grid_wallet.addWidget(gap_label, 2, 0)
2118 gap_e = AmountEdit(None,True)
2119 gap_e.setText("%d"% self.wallet.gap_limit)
2120 grid_wallet.addWidget(gap_e, 2, 2)
2121 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2122 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2123 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2124 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2125 + _('Warning') + ': ' \
2126 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2127 + _('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'
2128 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2129 if not self.config.is_modifiable('gap_limit'):
2130 for w in [gap_e, gap_label]: w.setEnabled(False)
2132 units = ['BTC', 'mBTC']
2133 unit_label = QLabel(_('Base unit'))
2134 grid_wallet.addWidget(unit_label, 3, 0)
2135 unit_combo = QComboBox()
2136 unit_combo.addItems(units)
2137 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2138 grid_wallet.addWidget(unit_combo, 3, 2)
2139 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2140 + '\n1BTC=1000mBTC.\n' \
2141 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2142 grid_wallet.setRowStretch(4,1)
2146 tab5 = QScrollArea()
2147 tab5.setEnabled(True)
2148 tab5.setWidgetResizable(True)
2150 grid_plugins = QGridLayout()
2151 grid_plugins.setColumnStretch(0,1)
2154 w.setLayout(grid_plugins)
2157 w.setMinimumHeight(len(self.plugins)*35)
2159 tabs.addTab(tab5, _('Plugins') )
2160 def mk_toggle(cb, p):
2161 return lambda: cb.setChecked(p.toggle())
2162 for i, p in enumerate(self.plugins):
2164 name, description = p.get_info()
2165 cb = QCheckBox(name)
2166 cb.setDisabled(not p.is_available())
2167 cb.setChecked(p.is_enabled())
2168 cb.clicked.connect(mk_toggle(cb,p))
2169 grid_plugins.addWidget(cb, i, 0)
2170 if p.requires_settings():
2171 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2172 grid_plugins.addWidget(HelpButton(description), i, 2)
2174 print_msg("Error: cannot display plugin", p)
2175 traceback.print_exc(file=sys.stdout)
2176 grid_plugins.setRowStretch(i+1,1)
2178 self.run_hook('create_settings_tab', tabs)
2180 vbox.addLayout(ok_cancel_buttons(d))
2184 if not d.exec_(): return
2186 fee = unicode(fee_e.text())
2188 fee = self.read_amount(fee)
2190 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2193 self.wallet.set_fee(fee)
2195 nz = unicode(nz_e.text())
2200 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2203 if self.wallet.num_zeros != nz:
2204 self.wallet.num_zeros = nz
2205 self.config.set_key('num_zeros', nz, True)
2206 self.update_history_tab()
2207 self.update_receive_tab()
2209 usechange_result = usechange_cb.isChecked()
2210 if self.wallet.use_change != usechange_result:
2211 self.wallet.use_change = usechange_result
2212 self.config.set_key('use_change', self.wallet.use_change, True)
2214 unit_result = units[unit_combo.currentIndex()]
2215 if self.base_unit() != unit_result:
2216 self.decimal_point = 8 if unit_result == 'BTC' else 5
2217 self.config.set_key('decimal_point', self.decimal_point, True)
2218 self.update_history_tab()
2219 self.update_status()
2222 n = int(gap_e.text())
2224 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2227 if self.wallet.gap_limit != n:
2228 r = self.wallet.change_gap_limit(n)
2230 self.update_receive_tab()
2231 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2233 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2235 need_restart = False
2237 lang_request = languages.keys()[lang_combo.currentIndex()]
2238 if lang_request != self.config.get('language'):
2239 self.config.set_key("language", lang_request, True)
2242 cur_request = str(currencies[cur_combo.currentIndex()])
2243 if cur_request != self.config.get('currency', "None"):
2244 self.config.set_key('currency', cur_request, True)
2245 self.update_wallet()
2247 self.run_hook('close_settings_dialog')
2250 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2252 self.receive_tab_set_mode(expert_cb.isChecked())
2254 def run_network_dialog(self):
2255 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2257 def closeEvent(self, event):
2259 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2260 self.save_column_widths()
2261 self.config.set_key("console-history",self.console.history[-50:])
2270 def __init__(self, wallet, config, app=None):
2271 self.wallet = wallet
2272 self.config = config
2274 self.app = QApplication(sys.argv)
2277 def restore_or_create(self):
2278 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2279 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2280 if r==2: return None
2281 return 'restore' if r==1 else 'create'
2284 def verify_seed(self):
2285 r = self.seed_dialog(False)
2286 if r != self.wallet.seed:
2287 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2294 def seed_dialog(self, is_restore=True):
2298 vbox = QVBoxLayout()
2300 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2302 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2304 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2307 label.setWordWrap(True)
2308 vbox.addWidget(label)
2310 seed_e = QTextEdit()
2311 seed_e.setMaximumHeight(100)
2312 vbox.addWidget(seed_e)
2315 grid = QGridLayout()
2317 gap_e = AmountEdit(None, True)
2319 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2320 grid.addWidget(gap_e, 2, 1)
2321 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2322 vbox.addLayout(grid)
2324 vbox.addLayout(ok_cancel_buttons(d))
2327 if not d.exec_(): return
2330 seed = str(seed_e.toPlainText())
2334 seed = mnemonic.mn_decode( seed.split() )
2336 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2340 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2347 gap = int(unicode(gap_e.text()))
2349 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2354 def network_dialog(self):
2355 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2358 def show_seed(self):
2359 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2361 def password_dialog(self):
2362 if self.wallet.seed:
2363 ElectrumWindow.change_password_dialog(self.wallet)
2366 def restore_wallet(self):
2367 wallet = self.wallet
2368 # wait until we are connected, because the user might have selected another server
2369 if not wallet.interface.is_connected:
2370 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2371 waiting_dialog(waiting)
2373 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2374 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2376 wallet.set_up_to_date(False)
2377 wallet.interface.poke('synchronizer')
2378 waiting_dialog(waiting)
2379 if wallet.is_found():
2380 print_error( "Recovery successful" )
2382 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2389 w = ElectrumWindow(self.wallet, self.config)
2390 if url: w.set_url(url)