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)
342 if self.wallet.fee < 50000:
343 self.wallet.set_fee(50000)
344 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
346 # set initial message
347 self.console.showMessage(self.wallet.interface.banner)
349 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
350 self.notify_transactions()
352 # plugins that need to change the GUI do it here
353 self.run_hook('init_gui')
355 def select_wallet_file(self):
356 wallet_folder = self.wallet.config.path
357 re.sub("(\/\w*.dat)$", "", wallet_folder)
358 file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
362 self.load_wallet(file_name)
365 def init_menubar(self):
368 electrum_menu = menubar.addMenu(_("&File"))
369 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
370 open_wallet_action.triggered.connect(self.select_wallet_file)
372 preferences_name = _("Preferences")
373 if sys.platform == 'darwin':
374 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
376 preferences_menu = electrum_menu.addAction(preferences_name)
377 preferences_menu.triggered.connect(self.settings_dialog)
378 electrum_menu.addSeparator()
380 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
382 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
383 raw_transaction_file.triggered.connect(self.do_process_from_file)
385 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
386 raw_transaction_text.triggered.connect(self.do_process_from_text)
388 electrum_menu.addSeparator()
389 quit_item = electrum_menu.addAction(_("&Close"))
390 quit_item.triggered.connect(self.close)
392 wallet_menu = menubar.addMenu(_("&Wallet"))
393 wallet_backup = wallet_menu.addAction(_("&Create backup"))
394 wallet_backup.triggered.connect(backup_wallet)
396 show_menu = wallet_menu.addMenu(_("Show"))
399 show_seed = show_menu.addAction(_("&Seed"))
400 show_seed.triggered.connect(self.show_seed_dialog)
402 show_mpk = show_menu.addAction(_("&Master Public Key"))
403 show_mpk.triggered.connect(self.show_master_public_key)
405 wallet_menu.addSeparator()
406 new_contact = wallet_menu.addAction(_("&New contact"))
407 new_contact.triggered.connect(self.new_contact_dialog)
409 import_menu = menubar.addMenu(_("&Import"))
410 in_labels = import_menu.addAction(_("&Labels"))
411 in_labels.triggered.connect(self.do_import_labels)
413 in_private_keys = import_menu.addAction(_("&Private keys"))
414 in_private_keys.triggered.connect(self.do_import_privkey)
416 export_menu = menubar.addMenu(_("&Export"))
417 ex_private_keys = export_menu.addAction(_("&Private keys"))
418 ex_private_keys.triggered.connect(self.do_export_privkeys)
420 ex_history = export_menu.addAction(_("&History"))
421 ex_history.triggered.connect(self.do_export_history)
423 ex_labels = export_menu.addAction(_("&Labels"))
424 ex_labels.triggered.connect(self.do_export_labels)
426 help_menu = menubar.addMenu(_("&Help"))
427 doc_open = help_menu.addAction(_("&Documentation"))
428 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
429 web_open = help_menu.addAction(_("&Official website"))
430 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
432 self.setMenuBar(menubar)
434 def load_wallet(self, filename):
437 config = electrum.SimpleConfig({'wallet_path': filename})
438 if not config.wallet_file_exists:
439 self.show_message("file not found "+ filename)
442 #self.wallet.verifier.stop()
443 interface = self.wallet.interface
444 verifier = self.wallet.verifier
445 self.wallet.synchronizer.stop()
448 self.wallet = electrum.Wallet(self.config)
449 self.wallet.interface = interface
450 self.wallet.verifier = verifier
452 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
457 def notify_transactions(self):
458 print_error("Notifying GUI")
459 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
460 # Combine the transactions if there are more then three
461 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
464 for tx in self.wallet.interface.pending_transactions_for_notifications:
465 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
469 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
470 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
472 self.wallet.interface.pending_transactions_for_notifications = []
474 for tx in self.wallet.interface.pending_transactions_for_notifications:
476 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
477 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
479 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
481 def notify(self, message):
482 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
485 def init_plugins(self):
486 import imp, pkgutil, __builtin__
487 if __builtin__.use_local_modules:
488 fp, pathname, description = imp.find_module('plugins')
489 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
490 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
491 imp.load_module('electrum_plugins', fp, pathname, description)
492 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
494 import electrum_plugins
495 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
496 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
501 self.plugins.append( p.Plugin(self) )
503 print_msg("Error:cannot initialize plugin",p)
504 traceback.print_exc(file=sys.stdout)
507 def run_hook(self, name, *args):
508 for p in self.plugins:
509 if not p.is_enabled():
518 print_error("Plugin error")
519 traceback.print_exc(file=sys.stdout)
524 def set_label(self, name, text = None):
526 old_text = self.wallet.labels.get(name)
529 self.wallet.labels[name] = text
530 self.wallet.config.set_key('labels', self.wallet.labels)
534 self.wallet.labels.pop(name)
536 self.run_hook('set_label', name, text, changed)
540 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
541 def getOpenFileName(self, title, filter = None):
542 directory = self.config.get('io_dir', os.path.expanduser('~'))
543 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
544 if fileName and directory != os.path.dirname(fileName):
545 self.config.set_key('io_dir', os.path.dirname(fileName), True)
548 def getSaveFileName(self, title, filename, filter = None):
549 directory = self.config.get('io_dir', os.path.expanduser('~'))
550 path = os.path.join( directory, filename )
551 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
552 if fileName and directory != os.path.dirname(fileName):
553 self.config.set_key('io_dir', os.path.dirname(fileName), True)
559 QMainWindow.close(self)
560 self.run_hook('close_main_window')
562 def connect_slots(self, sender):
563 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
564 self.previous_payto_e=''
566 def timer_actions(self):
567 if self.need_update.is_set():
569 self.need_update.clear()
570 self.run_hook('timer_actions')
572 def format_amount(self, x, is_diff=False, whitespaces=False):
573 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
575 def read_amount(self, x):
576 if x in['.', '']: return None
577 p = pow(10, self.decimal_point)
578 return int( p * Decimal(x) )
581 assert self.decimal_point in [5,8]
582 return "BTC" if self.decimal_point == 8 else "mBTC"
584 def update_status(self):
585 if self.wallet.interface and self.wallet.interface.is_connected:
586 if not self.wallet.up_to_date:
587 text = _("Synchronizing...")
588 icon = QIcon(":icons/status_waiting.png")
590 c, u = self.wallet.get_account_balance(self.current_account)
591 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
592 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
593 text += self.create_quote_text(Decimal(c+u)/100000000)
594 self.tray.setToolTip(text)
595 icon = QIcon(":icons/status_connected.png")
597 text = _("Not connected")
598 icon = QIcon(":icons/status_disconnected.png")
600 self.balance_label.setText(text)
601 self.status_button.setIcon( icon )
603 def update_wallet(self):
605 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
606 self.update_history_tab()
607 self.update_receive_tab()
608 self.update_contacts_tab()
609 self.update_completions()
612 def create_quote_text(self, btc_balance):
613 quote_currency = self.config.get("currency", "None")
614 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
615 if quote_balance is None:
618 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
621 def create_history_tab(self):
622 self.history_list = l = MyTreeWidget(self)
624 for i,width in enumerate(self.column_widths['history']):
625 l.setColumnWidth(i, width)
626 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
627 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
628 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
630 l.setContextMenuPolicy(Qt.CustomContextMenu)
631 l.customContextMenuRequested.connect(self.create_history_menu)
635 def create_history_menu(self, position):
636 self.history_list.selectedIndexes()
637 item = self.history_list.currentItem()
639 tx_hash = str(item.data(0, Qt.UserRole).toString())
640 if not tx_hash: return
642 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
643 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
644 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
645 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
648 def show_tx_details(self, tx):
649 dialog = QDialog(self)
651 dialog.setWindowTitle(_("Transaction Details"))
653 dialog.setLayout(vbox)
654 dialog.setMinimumSize(600,300)
657 if tx_hash in self.wallet.transactions.keys():
658 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
659 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
661 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
667 vbox.addWidget(QLabel("Transaction ID:"))
668 e = QLineEdit(tx_hash)
672 vbox.addWidget(QLabel("Date: %s"%time_str))
673 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
676 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
677 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
679 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
680 vbox.addWidget(QLabel("Transaction fee: unknown"))
682 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
684 vbox.addWidget( self.generate_transaction_information_widget(tx) )
686 ok_button = QPushButton(_("Close"))
687 ok_button.setDefault(True)
688 ok_button.clicked.connect(dialog.accept)
692 hbox.addWidget(ok_button)
696 def tx_label_clicked(self, item, column):
697 if column==2 and item.isSelected():
699 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
700 self.history_list.editItem( item, column )
701 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
704 def tx_label_changed(self, item, column):
708 tx_hash = str(item.data(0, Qt.UserRole).toString())
709 tx = self.wallet.transactions.get(tx_hash)
710 text = unicode( item.text(2) )
711 self.set_label(tx_hash, text)
713 item.setForeground(2, QBrush(QColor('black')))
715 text = self.wallet.get_default_label(tx_hash)
716 item.setText(2, text)
717 item.setForeground(2, QBrush(QColor('gray')))
721 def edit_label(self, is_recv):
722 l = self.receive_list if is_recv else self.contacts_list
723 item = l.currentItem()
724 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
725 l.editItem( item, 1 )
726 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
730 def address_label_clicked(self, item, column, l, column_addr, column_label):
731 if column == column_label and item.isSelected():
732 is_editable = item.data(0, 32).toBool()
735 addr = unicode( item.text(column_addr) )
736 label = unicode( item.text(column_label) )
737 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
738 l.editItem( item, column )
739 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
742 def address_label_changed(self, item, column, l, column_addr, column_label):
743 if column == column_label:
744 addr = unicode( item.text(column_addr) )
745 text = unicode( item.text(column_label) )
746 is_editable = item.data(0, 32).toBool()
750 changed = self.set_label(addr, text)
752 self.update_history_tab()
753 self.update_completions()
755 self.current_item_changed(item)
757 self.run_hook('item_changed', item, column)
760 def current_item_changed(self, a):
761 self.run_hook('current_item_changed', a)
765 def update_history_tab(self):
767 self.history_list.clear()
768 for item in self.wallet.get_tx_history(self.current_account):
769 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
772 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
777 time_str = 'unverified'
778 icon = QIcon(":icons/unconfirmed.png")
781 icon = QIcon(":icons/unconfirmed.png")
783 icon = QIcon(":icons/clock%d.png"%conf)
785 icon = QIcon(":icons/confirmed.png")
787 if value is not None:
788 v_str = self.format_amount(value, True, whitespaces=True)
792 balance_str = self.format_amount(balance, whitespaces=True)
795 label, is_default_label = self.wallet.get_label(tx_hash)
797 label = _('Pruned transaction outputs')
798 is_default_label = False
800 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
801 item.setFont(2, QFont(MONOSPACE_FONT))
802 item.setFont(3, QFont(MONOSPACE_FONT))
803 item.setFont(4, QFont(MONOSPACE_FONT))
805 item.setForeground(3, QBrush(QColor("#BC1E1E")))
807 item.setData(0, Qt.UserRole, tx_hash)
808 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
810 item.setForeground(2, QBrush(QColor('grey')))
812 item.setIcon(0, icon)
813 self.history_list.insertTopLevelItem(0,item)
816 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
819 def create_send_tab(self):
824 grid.setColumnMinimumWidth(3,300)
825 grid.setColumnStretch(5,1)
828 self.payto_e = QLineEdit()
829 grid.addWidget(QLabel(_('Pay to')), 1, 0)
830 grid.addWidget(self.payto_e, 1, 1, 1, 3)
832 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)
834 completer = QCompleter()
835 completer.setCaseSensitivity(False)
836 self.payto_e.setCompleter(completer)
837 completer.setModel(self.completions)
839 self.message_e = QLineEdit()
840 grid.addWidget(QLabel(_('Description')), 2, 0)
841 grid.addWidget(self.message_e, 2, 1, 1, 3)
842 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)
844 self.amount_e = AmountEdit(self.base_unit)
845 grid.addWidget(QLabel(_('Amount')), 3, 0)
846 grid.addWidget(self.amount_e, 3, 1, 1, 2)
847 grid.addWidget(HelpButton(
848 _('Amount to be sent.') + '\n\n' \
849 + _('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.') \
850 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
852 self.fee_e = AmountEdit(self.base_unit)
853 grid.addWidget(QLabel(_('Fee')), 4, 0)
854 grid.addWidget(self.fee_e, 4, 1, 1, 2)
855 grid.addWidget(HelpButton(
856 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
857 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
858 + _('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)
861 b = EnterButton(_("Send"), self.do_send)
863 b = EnterButton(_("Create unsigned transaction"), self.do_send)
864 grid.addWidget(b, 6, 1)
866 b = EnterButton(_("Clear"),self.do_clear)
867 grid.addWidget(b, 6, 2)
869 self.payto_sig = QLabel('')
870 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
872 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
873 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
882 def entry_changed( is_fee ):
883 self.funds_error = False
885 if self.amount_e.is_shortcut:
886 self.amount_e.is_shortcut = False
887 c, u = self.wallet.get_account_balance(self.current_account)
888 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
889 fee = self.wallet.estimated_fee(inputs)
891 self.amount_e.setText( self.format_amount(amount) )
892 self.fee_e.setText( self.format_amount( fee ) )
895 amount = self.read_amount(str(self.amount_e.text()))
896 fee = self.read_amount(str(self.fee_e.text()))
898 if not is_fee: fee = None
901 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
903 self.fee_e.setText( self.format_amount( fee ) )
906 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
910 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
911 self.funds_error = True
912 text = _( "Not enough funds" )
913 c, u = self.wallet.get_frozen_balance()
914 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
916 self.statusBar().showMessage(text)
917 self.amount_e.setPalette(palette)
918 self.fee_e.setPalette(palette)
920 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
921 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
923 self.run_hook('create_send_tab', grid)
927 def update_completions(self):
929 for addr,label in self.wallet.labels.items():
930 if addr in self.wallet.addressbook:
931 l.append( label + ' <' + addr + '>')
933 self.run_hook('update_completions', l)
934 self.completions.setStringList(l)
938 return lambda s, *args: s.do_protect(func, args)
943 label = unicode( self.message_e.text() )
944 r = unicode( self.payto_e.text() )
947 # label or alias, with address in brackets
948 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
949 to_address = m.group(2) if m else r
951 if not is_valid(to_address):
952 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
956 amount = self.read_amount(unicode( self.amount_e.text()))
958 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
961 fee = self.read_amount(unicode( self.fee_e.text()))
963 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
966 confirm_amount = self.config.get('confirm_amount', 100000000)
967 if amount >= confirm_amount:
968 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
971 self.send_tx(to_address, amount, fee, label)
975 def send_tx(self, to_address, amount, fee, label, password):
978 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
979 except BaseException, e:
980 self.show_message(str(e))
983 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
984 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
987 self.run_hook('send_tx', tx)
990 self.set_label(tx.hash(), label)
993 h = self.wallet.send_tx(tx)
994 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
995 status, msg = self.wallet.receive_tx( h )
997 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
999 self.update_contacts_tab()
1001 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1003 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1005 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1006 with open(fileName,'w') as f:
1007 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1008 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1010 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1015 def set_url(self, url):
1016 address, amount, label, message, signature, identity, url = util.parse_url(url)
1017 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1019 if label and self.wallet.labels.get(address) != label:
1020 if self.question('Give label "%s" to address %s ?'%(label,address)):
1021 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1022 self.wallet.addressbook.append(address)
1023 self.set_label(address, label)
1025 self.run_hook('set_url', url, self.show_message, self.question)
1027 self.tabs.setCurrentIndex(1)
1028 label = self.wallet.labels.get(address)
1029 m_addr = label + ' <'+ address +'>' if label else address
1030 self.payto_e.setText(m_addr)
1032 self.message_e.setText(message)
1033 self.amount_e.setText(amount)
1035 self.set_frozen(self.payto_e,True)
1036 self.set_frozen(self.amount_e,True)
1037 self.set_frozen(self.message_e,True)
1038 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1040 self.payto_sig.setVisible(False)
1043 self.payto_sig.setVisible(False)
1044 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1046 self.set_frozen(e,False)
1047 self.update_status()
1049 def set_frozen(self,entry,frozen):
1051 entry.setReadOnly(True)
1052 entry.setFrame(False)
1053 palette = QPalette()
1054 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1055 entry.setPalette(palette)
1057 entry.setReadOnly(False)
1058 entry.setFrame(True)
1059 palette = QPalette()
1060 palette.setColor(entry.backgroundRole(), QColor('white'))
1061 entry.setPalette(palette)
1064 def toggle_freeze(self,addr):
1066 if addr in self.wallet.frozen_addresses:
1067 self.wallet.unfreeze(addr)
1069 self.wallet.freeze(addr)
1070 self.update_receive_tab()
1072 def toggle_priority(self,addr):
1074 if addr in self.wallet.prioritized_addresses:
1075 self.wallet.unprioritize(addr)
1077 self.wallet.prioritize(addr)
1078 self.update_receive_tab()
1081 def create_list_tab(self, headers):
1082 "generic tab creation method"
1083 l = MyTreeWidget(self)
1084 l.setColumnCount( len(headers) )
1085 l.setHeaderLabels( headers )
1088 vbox = QVBoxLayout()
1095 vbox.addWidget(buttons)
1097 hbox = QHBoxLayout()
1100 buttons.setLayout(hbox)
1105 def create_receive_tab(self):
1106 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1107 l.setContextMenuPolicy(Qt.CustomContextMenu)
1108 l.customContextMenuRequested.connect(self.create_receive_menu)
1109 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1110 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1111 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1112 self.receive_list = l
1113 self.receive_buttons_hbox = hbox
1118 def receive_tab_set_mode(self, i):
1119 self.save_column_widths()
1120 self.expert_mode = (i == 1)
1121 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1122 self.update_receive_tab()
1125 def save_column_widths(self):
1126 if not self.expert_mode:
1127 widths = [ self.receive_list.columnWidth(0) ]
1130 for i in range(self.receive_list.columnCount() -1):
1131 widths.append(self.receive_list.columnWidth(i))
1132 self.column_widths["receive"][self.expert_mode] = widths
1134 self.column_widths["history"] = []
1135 for i in range(self.history_list.columnCount() - 1):
1136 self.column_widths["history"].append(self.history_list.columnWidth(i))
1138 self.column_widths["contacts"] = []
1139 for i in range(self.contacts_list.columnCount() - 1):
1140 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1142 self.config.set_key("column_widths", self.column_widths, True)
1145 def create_contacts_tab(self):
1146 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1147 l.setContextMenuPolicy(Qt.CustomContextMenu)
1148 l.customContextMenuRequested.connect(self.create_contact_menu)
1149 for i,width in enumerate(self.column_widths['contacts']):
1150 l.setColumnWidth(i, width)
1152 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1153 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1154 self.contacts_list = l
1155 self.contacts_buttons_hbox = hbox
1160 def delete_imported_key(self, addr):
1161 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1162 self.wallet.delete_imported_key(addr)
1163 self.update_receive_tab()
1164 self.update_history_tab()
1167 def create_receive_menu(self, position):
1168 # fixme: this function apparently has a side effect.
1169 # if it is not called the menu pops up several times
1170 #self.receive_list.selectedIndexes()
1172 item = self.receive_list.itemAt(position)
1174 addr = unicode(item.text(0))
1175 if not is_valid(addr):
1176 item.setExpanded(not item.isExpanded())
1179 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1180 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1181 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1182 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1183 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1184 if addr in self.wallet.imported_keys:
1185 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1187 if self.expert_mode:
1188 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1189 menu.addAction(t, lambda: self.toggle_freeze(addr))
1190 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1191 menu.addAction(t, lambda: self.toggle_priority(addr))
1193 self.run_hook('receive_menu', menu)
1194 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1197 def payto(self, addr):
1199 label = self.wallet.labels.get(addr)
1200 m_addr = label + ' <' + addr + '>' if label else addr
1201 self.tabs.setCurrentIndex(1)
1202 self.payto_e.setText(m_addr)
1203 self.amount_e.setFocus()
1206 def delete_contact(self, x):
1207 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1208 self.wallet.delete_contact(x)
1209 self.set_label(x, None)
1210 self.update_history_tab()
1211 self.update_contacts_tab()
1212 self.update_completions()
1215 def create_contact_menu(self, position):
1216 item = self.contacts_list.itemAt(position)
1218 addr = unicode(item.text(0))
1219 label = unicode(item.text(1))
1220 is_editable = item.data(0,32).toBool()
1221 payto_addr = item.data(0,33).toString()
1223 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1224 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1225 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1227 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1228 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1230 self.run_hook('create_contact_menu', menu, item)
1231 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1234 def update_receive_item(self, item):
1235 item.setFont(0, QFont(MONOSPACE_FONT))
1236 address = str(item.data(0,0).toString())
1237 label = self.wallet.labels.get(address,'')
1238 item.setData(1,0,label)
1239 item.setData(0,32, True) # is editable
1241 self.run_hook('update_receive_item', address, item)
1243 c, u = self.wallet.get_addr_balance(address)
1244 balance = self.format_amount(c + u)
1245 item.setData(2,0,balance)
1247 if self.expert_mode:
1248 if address in self.wallet.frozen_addresses:
1249 item.setBackgroundColor(0, QColor('lightblue'))
1250 elif address in self.wallet.prioritized_addresses:
1251 item.setBackgroundColor(0, QColor('lightgreen'))
1254 def update_receive_tab(self):
1255 l = self.receive_list
1258 l.setColumnHidden(2, not self.expert_mode)
1259 l.setColumnHidden(3, not self.expert_mode)
1260 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1261 l.setColumnWidth(i, width)
1263 if self.current_account is None:
1264 account_items = self.wallet.accounts.items()
1265 elif self.current_account != -1:
1266 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1270 for k, account in account_items:
1271 name = account.get('name',str(k))
1272 c,u = self.wallet.get_account_balance(k)
1273 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1274 l.addTopLevelItem(account_item)
1275 account_item.setExpanded(True)
1277 for is_change in ([0,1] if self.expert_mode else [0]):
1278 if self.expert_mode:
1279 name = "Receiving" if not is_change else "Change"
1280 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1281 account_item.addChild(seq_item)
1282 if not is_change: seq_item.setExpanded(True)
1284 seq_item = account_item
1288 for address in account[is_change]:
1289 h = self.wallet.history.get(address,[])
1293 if gap > self.wallet.gap_limit:
1298 num_tx = '*' if h == ['*'] else "%d"%len(h)
1299 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1300 self.update_receive_item(item)
1302 item.setBackgroundColor(1, QColor('red'))
1303 seq_item.addChild(item)
1306 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1307 c,u = self.wallet.get_imported_balance()
1308 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1309 l.addTopLevelItem(account_item)
1310 account_item.setExpanded(True)
1311 for address in self.wallet.imported_keys.keys():
1312 item = QTreeWidgetItem( [ address, '', '', ''] )
1313 self.update_receive_item(item)
1314 account_item.addChild(item)
1317 # we use column 1 because column 0 may be hidden
1318 l.setCurrentItem(l.topLevelItem(0),1)
1321 def update_contacts_tab(self):
1322 l = self.contacts_list
1325 for address in self.wallet.addressbook:
1326 label = self.wallet.labels.get(address,'')
1327 n = self.wallet.get_num_tx(address)
1328 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1329 item.setFont(0, QFont(MONOSPACE_FONT))
1330 # 32 = label can be edited (bool)
1331 item.setData(0,32, True)
1333 item.setData(0,33, address)
1334 l.addTopLevelItem(item)
1336 self.run_hook('update_contacts_tab', l)
1337 l.setCurrentItem(l.topLevelItem(0))
1341 def create_console_tab(self):
1342 from qt_console import Console
1343 self.console = console = Console()
1344 self.console.history = self.config.get("console-history",[])
1345 self.console.history_index = len(self.console.history)
1347 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1348 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1350 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1352 def mkfunc(f, method):
1353 return lambda *args: apply( f, (method, args, self.password_dialog ))
1355 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1356 methods[m] = mkfunc(c._run, m)
1358 console.updateNamespace(methods)
1361 def change_account(self,s):
1362 if s == _("All accounts"):
1363 self.current_account = None
1365 accounts = self.wallet.get_accounts()
1366 for k, v in accounts.items():
1368 self.current_account = k
1369 self.update_history_tab()
1370 self.update_status()
1371 self.update_receive_tab()
1373 def create_status_bar(self):
1376 sb.setFixedHeight(35)
1377 qtVersion = qVersion()
1379 self.balance_label = QLabel("")
1380 sb.addWidget(self.balance_label)
1382 update_notification = UpdateLabel(self.config)
1383 if(update_notification.new_version):
1384 sb.addPermanentWidget(update_notification)
1386 accounts = self.wallet.get_accounts()
1387 if len(accounts) > 1:
1388 from_combo = QComboBox()
1389 from_combo.addItems([_("All accounts")] + accounts.values())
1390 from_combo.setCurrentIndex(0)
1391 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1392 sb.addPermanentWidget(from_combo)
1394 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1395 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1396 if self.wallet.seed:
1397 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1398 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1399 sb.addPermanentWidget( self.password_button )
1400 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1401 if self.wallet.seed:
1402 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1403 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1404 sb.addPermanentWidget( self.status_button )
1406 self.run_hook('create_status_bar', (sb,))
1408 self.setStatusBar(sb)
1412 self.config.set_key('gui', 'lite', True)
1415 self.lite.mini.show()
1417 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1418 self.lite.main(None)
1420 def new_contact_dialog(self):
1421 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1422 address = unicode(text)
1424 if is_valid(address):
1425 self.wallet.add_contact(address)
1426 self.update_contacts_tab()
1427 self.update_history_tab()
1428 self.update_completions()
1430 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1432 def show_master_public_key(self):
1433 dialog = QDialog(self)
1435 dialog.setWindowTitle(_("Master Public Key"))
1437 main_text = QTextEdit()
1438 main_text.setText(self.wallet.get_master_public_key())
1439 main_text.setReadOnly(True)
1440 main_text.setMaximumHeight(170)
1441 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1443 ok_button = QPushButton(_("OK"))
1444 ok_button.setDefault(True)
1445 ok_button.clicked.connect(dialog.accept)
1447 main_layout = QGridLayout()
1448 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1450 main_layout.addWidget(main_text, 1, 0)
1451 main_layout.addWidget(qrw, 1, 1 )
1453 vbox = QVBoxLayout()
1454 vbox.addLayout(main_layout)
1455 hbox = QHBoxLayout()
1457 hbox.addWidget(ok_button)
1458 vbox.addLayout(hbox)
1460 dialog.setLayout(vbox)
1465 def show_seed_dialog(self, password):
1466 if not self.wallet.seed:
1467 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1470 seed = self.wallet.decode_seed(password)
1472 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1474 self.show_seed(seed, self.wallet.imported_keys, self)
1478 def show_seed(self, seed, imported_keys, parent=None):
1479 dialog = QDialog(parent)
1481 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1483 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1485 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1487 seed_text = QTextEdit(brainwallet)
1488 seed_text.setReadOnly(True)
1489 seed_text.setMaximumHeight(130)
1491 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1492 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1493 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1494 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1496 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1497 label2 = QLabel(msg2)
1498 label2.setWordWrap(True)
1501 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1502 logo.setMaximumWidth(60)
1504 qrw = QRCodeWidget(seed)
1506 ok_button = QPushButton(_("OK"))
1507 ok_button.setDefault(True)
1508 ok_button.clicked.connect(dialog.accept)
1510 grid = QGridLayout()
1511 #main_layout.addWidget(logo, 0, 0)
1513 grid.addWidget(logo, 0, 0)
1514 grid.addWidget(label1, 0, 1)
1516 grid.addWidget(seed_text, 1, 0, 1, 2)
1518 grid.addWidget(qrw, 0, 2, 2, 1)
1520 vbox = QVBoxLayout()
1521 vbox.addLayout(grid)
1522 vbox.addWidget(label2)
1524 hbox = QHBoxLayout()
1526 hbox.addWidget(ok_button)
1527 vbox.addLayout(hbox)
1529 dialog.setLayout(vbox)
1532 def show_qrcode(self, data, title = "QR code"):
1536 d.setWindowTitle(title)
1537 d.setMinimumSize(270, 300)
1538 vbox = QVBoxLayout()
1539 qrw = QRCodeWidget(data)
1540 vbox.addWidget(qrw, 1)
1541 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1542 hbox = QHBoxLayout()
1546 filename = "qrcode.bmp"
1547 bmp.save_qrcode(qrw.qr, filename)
1548 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1550 b = QPushButton(_("Save"))
1552 b.clicked.connect(print_qr)
1554 b = QPushButton(_("Close"))
1556 b.clicked.connect(d.accept)
1559 vbox.addLayout(hbox)
1564 def do_protect(self, func, args):
1565 if self.wallet.use_encryption:
1566 password = self.password_dialog()
1572 if args != (False,):
1573 args = (self,) + args + (password,)
1575 args = (self,password)
1580 def show_private_key(self, address, password):
1581 if not address: return
1583 pk = self.wallet.get_private_key(address, password)
1584 except BaseException, e:
1585 self.show_message(str(e))
1587 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1591 def do_sign(self, address, message, signature, password):
1593 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1594 signature.setText(sig)
1595 except BaseException, e:
1596 self.show_message(str(e))
1598 def sign_message(self, address):
1599 if not address: return
1602 d.setWindowTitle(_('Sign Message'))
1603 d.setMinimumSize(410, 290)
1605 tab_widget = QTabWidget()
1607 layout = QGridLayout(tab)
1609 sign_address = QLineEdit()
1611 sign_address.setText(address)
1612 layout.addWidget(QLabel(_('Address')), 1, 0)
1613 layout.addWidget(sign_address, 1, 1)
1615 sign_message = QTextEdit()
1616 layout.addWidget(QLabel(_('Message')), 2, 0)
1617 layout.addWidget(sign_message, 2, 1)
1618 layout.setRowStretch(2,3)
1620 sign_signature = QTextEdit()
1621 layout.addWidget(QLabel(_('Signature')), 3, 0)
1622 layout.addWidget(sign_signature, 3, 1)
1623 layout.setRowStretch(3,1)
1626 hbox = QHBoxLayout()
1627 b = QPushButton(_("Sign"))
1629 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1630 b = QPushButton(_("Close"))
1631 b.clicked.connect(d.accept)
1633 layout.addLayout(hbox, 4, 1)
1634 tab_widget.addTab(tab, _("Sign"))
1638 layout = QGridLayout(tab)
1640 verify_address = QLineEdit()
1641 layout.addWidget(QLabel(_('Address')), 1, 0)
1642 layout.addWidget(verify_address, 1, 1)
1644 verify_message = QTextEdit()
1645 layout.addWidget(QLabel(_('Message')), 2, 0)
1646 layout.addWidget(verify_message, 2, 1)
1647 layout.setRowStretch(2,3)
1649 verify_signature = QTextEdit()
1650 layout.addWidget(QLabel(_('Signature')), 3, 0)
1651 layout.addWidget(verify_signature, 3, 1)
1652 layout.setRowStretch(3,1)
1655 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1656 self.show_message(_("Signature verified"))
1658 self.show_message(_("Error: wrong signature"))
1660 hbox = QHBoxLayout()
1661 b = QPushButton(_("Verify"))
1662 b.clicked.connect(do_verify)
1664 b = QPushButton(_("Close"))
1665 b.clicked.connect(d.accept)
1667 layout.addLayout(hbox, 4, 1)
1668 tab_widget.addTab(tab, _("Verify"))
1670 vbox = QVBoxLayout()
1671 vbox.addWidget(tab_widget)
1678 def question(self, msg):
1679 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1681 def show_message(self, msg):
1682 QMessageBox.information(self, _('Message'), msg, _('OK'))
1684 def password_dialog(self ):
1691 vbox = QVBoxLayout()
1692 msg = _('Please enter your password')
1693 vbox.addWidget(QLabel(msg))
1695 grid = QGridLayout()
1697 grid.addWidget(QLabel(_('Password')), 1, 0)
1698 grid.addWidget(pw, 1, 1)
1699 vbox.addLayout(grid)
1701 vbox.addLayout(ok_cancel_buttons(d))
1704 self.run_hook('password_dialog', pw, grid, 1)
1705 if not d.exec_(): return
1706 return unicode(pw.text())
1713 def change_password_dialog( wallet, parent=None ):
1716 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1724 new_pw = QLineEdit()
1725 new_pw.setEchoMode(2)
1726 conf_pw = QLineEdit()
1727 conf_pw.setEchoMode(2)
1729 vbox = QVBoxLayout()
1731 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1732 +_('To disable wallet encryption, enter an empty new password.')) \
1733 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1735 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1736 +_("Leave these fields empty if you want to disable encryption.")
1737 vbox.addWidget(QLabel(msg))
1739 grid = QGridLayout()
1742 if wallet.use_encryption:
1743 grid.addWidget(QLabel(_('Password')), 1, 0)
1744 grid.addWidget(pw, 1, 1)
1746 grid.addWidget(QLabel(_('New Password')), 2, 0)
1747 grid.addWidget(new_pw, 2, 1)
1749 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1750 grid.addWidget(conf_pw, 3, 1)
1751 vbox.addLayout(grid)
1753 vbox.addLayout(ok_cancel_buttons(d))
1756 if not d.exec_(): return
1758 password = unicode(pw.text()) if wallet.use_encryption else None
1759 new_password = unicode(new_pw.text())
1760 new_password2 = unicode(conf_pw.text())
1763 seed = wallet.decode_seed(password)
1765 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1768 if new_password != new_password2:
1769 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1770 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1773 wallet.update_password(seed, password, new_password)
1775 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1778 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1781 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1782 parent.password_button.setIcon( icon )
1786 def generate_transaction_information_widget(self, tx):
1787 tabs = QTabWidget(self)
1790 grid_ui = QGridLayout(tab1)
1791 grid_ui.setColumnStretch(0,1)
1792 tabs.addTab(tab1, _('Outputs') )
1794 tree_widget = MyTreeWidget(self)
1795 tree_widget.setColumnCount(2)
1796 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1797 tree_widget.setColumnWidth(0, 300)
1798 tree_widget.setColumnWidth(1, 50)
1800 for address, value in tx.outputs:
1801 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1802 tree_widget.addTopLevelItem(item)
1804 tree_widget.setMaximumHeight(100)
1806 grid_ui.addWidget(tree_widget)
1809 grid_ui = QGridLayout(tab2)
1810 grid_ui.setColumnStretch(0,1)
1811 tabs.addTab(tab2, _('Inputs') )
1813 tree_widget = MyTreeWidget(self)
1814 tree_widget.setColumnCount(2)
1815 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1817 for input_line in tx.inputs:
1818 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1819 tree_widget.addTopLevelItem(item)
1821 tree_widget.setMaximumHeight(100)
1823 grid_ui.addWidget(tree_widget)
1827 def tx_dict_from_text(self, txt):
1829 tx_dict = json.loads(str(txt))
1830 assert "hex" in tx_dict.keys()
1831 assert "complete" in tx_dict.keys()
1832 if not tx_dict["complete"]:
1833 assert "input_info" in tx_dict.keys()
1835 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1840 def read_tx_from_file(self):
1841 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1845 with open(fileName, "r") as f:
1846 file_content = f.read()
1847 except (ValueError, IOError, os.error), reason:
1848 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1850 return self.tx_dict_from_text(file_content)
1854 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1856 self.wallet.signrawtransaction(tx, input_info, [], password)
1858 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1860 with open(fileName, "w+") as f:
1861 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1862 self.show_message(_("Transaction saved successfully"))
1865 except BaseException, e:
1866 self.show_message(str(e))
1869 def send_raw_transaction(self, raw_tx, dialog = ""):
1870 result, result_message = self.wallet.sendtx( raw_tx )
1872 self.show_message("Transaction successfully sent: %s" % (result_message))
1876 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1878 def do_process_from_text(self):
1879 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1882 tx_dict = self.tx_dict_from_text(text)
1884 self.create_process_transaction_window(tx_dict)
1886 def do_process_from_file(self):
1887 tx_dict = self.read_tx_from_file()
1889 self.create_process_transaction_window(tx_dict)
1891 def create_process_transaction_window(self, tx_dict):
1892 tx = Transaction(tx_dict["hex"])
1894 dialog = QDialog(self)
1895 dialog.setMinimumWidth(500)
1896 dialog.setWindowTitle(_('Process raw transaction'))
1902 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1903 l.addWidget(QLabel(_("Actions")), 4,0)
1905 if tx_dict["complete"] == False:
1906 l.addWidget(QLabel(_("Unsigned")), 3,1)
1907 if self.wallet.seed :
1908 b = QPushButton("Sign transaction")
1909 input_info = json.loads(tx_dict["input_info"])
1910 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1911 l.addWidget(b, 4, 1)
1913 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1915 l.addWidget(QLabel(_("Signed")), 3,1)
1916 b = QPushButton("Broadcast transaction")
1917 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1920 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1921 cancelButton = QPushButton(_("Cancel"))
1922 cancelButton.clicked.connect(lambda: dialog.done(0))
1923 l.addWidget(cancelButton, 4,2)
1929 def do_export_privkeys(self, password):
1930 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.")))
1933 select_export = _('Select file to export your private keys to')
1934 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1936 with open(fileName, "w+") as csvfile:
1937 transaction = csv.writer(csvfile)
1938 transaction.writerow(["address", "private_key"])
1941 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1942 transaction.writerow(["%34s"%addr,pk])
1944 self.show_message(_("Private keys exported."))
1946 except (IOError, os.error), reason:
1947 export_error_label = _("Electrum was unable to produce a private key-export.")
1948 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1950 except BaseException, e:
1951 self.show_message(str(e))
1955 def do_import_labels(self):
1956 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1957 if not labelsFile: return
1959 f = open(labelsFile, 'r')
1962 for key, value in json.loads(data).items():
1963 self.wallet.labels[key] = value
1965 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1966 except (IOError, os.error), reason:
1967 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1970 def do_export_labels(self):
1971 labels = self.wallet.labels
1973 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1975 with open(fileName, 'w+') as f:
1976 json.dump(labels, f)
1977 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1978 except (IOError, os.error), reason:
1979 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1982 def do_export_history(self):
1983 from gui_lite import csv_transaction
1984 csv_transaction(self.wallet)
1988 def do_import_privkey(self, password):
1989 if not self.wallet.imported_keys:
1990 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1991 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1992 + _('Are you sure you understand what you are doing?'), 3, 4)
1995 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1998 text = str(text).split()
2003 addr = self.wallet.import_key(key, password)
2004 except BaseException as e:
2010 addrlist.append(addr)
2012 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2014 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2015 self.update_receive_tab()
2016 self.update_history_tab()
2019 def settings_dialog(self):
2021 d.setWindowTitle(_('Electrum Settings'))
2023 vbox = QVBoxLayout()
2025 tabs = QTabWidget(self)
2026 self.settings_tab = tabs
2027 vbox.addWidget(tabs)
2030 grid_ui = QGridLayout(tab1)
2031 grid_ui.setColumnStretch(0,1)
2032 tabs.addTab(tab1, _('Display') )
2034 nz_label = QLabel(_('Display zeros'))
2035 grid_ui.addWidget(nz_label, 0, 0)
2036 nz_e = AmountEdit(None,True)
2037 nz_e.setText("%d"% self.wallet.num_zeros)
2038 grid_ui.addWidget(nz_e, 0, 1)
2039 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2040 grid_ui.addWidget(HelpButton(msg), 0, 2)
2041 if not self.config.is_modifiable('num_zeros'):
2042 for w in [nz_e, nz_label]: w.setEnabled(False)
2044 lang_label=QLabel(_('Language') + ':')
2045 grid_ui.addWidget(lang_label, 1, 0)
2046 lang_combo = QComboBox()
2047 from i18n import languages
2048 lang_combo.addItems(languages.values())
2050 index = languages.keys().index(self.config.get("language",''))
2053 lang_combo.setCurrentIndex(index)
2054 grid_ui.addWidget(lang_combo, 1, 1)
2055 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2056 if not self.config.is_modifiable('language'):
2057 for w in [lang_combo, lang_label]: w.setEnabled(False)
2059 currencies = self.exchanger.get_currencies()
2060 currencies.insert(0, "None")
2062 cur_label=QLabel(_('Currency') + ':')
2063 grid_ui.addWidget(cur_label , 2, 0)
2064 cur_combo = QComboBox()
2065 cur_combo.addItems(currencies)
2067 index = currencies.index(self.config.get('currency', "None"))
2070 cur_combo.setCurrentIndex(index)
2071 grid_ui.addWidget(cur_combo, 2, 1)
2072 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2074 expert_cb = QCheckBox(_('Expert mode'))
2075 expert_cb.setChecked(self.expert_mode)
2076 grid_ui.addWidget(expert_cb, 3, 0)
2077 hh = _('In expert mode, your client will:') + '\n' \
2078 + _(' - Show change addresses in the Receive tab') + '\n' \
2079 + _(' - Display the balance of each address') + '\n' \
2080 + _(' - Add freeze/prioritize actions to addresses.')
2081 grid_ui.addWidget(HelpButton(hh), 3, 2)
2082 grid_ui.setRowStretch(4,1)
2086 grid_wallet = QGridLayout(tab2)
2087 grid_wallet.setColumnStretch(0,1)
2088 tabs.addTab(tab2, _('Wallet') )
2090 fee_label = QLabel(_('Transaction fee'))
2091 grid_wallet.addWidget(fee_label, 0, 0)
2092 fee_e = AmountEdit(self.base_unit)
2093 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2094 grid_wallet.addWidget(fee_e, 0, 2)
2095 msg = _('Fee per kilobyte of transaction.') + ' ' \
2096 + _('Recommended value') + ': ' + self.format_amount(50000)
2097 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2098 if not self.config.is_modifiable('fee_per_kb'):
2099 for w in [fee_e, fee_label]: w.setEnabled(False)
2101 usechange_cb = QCheckBox(_('Use change addresses'))
2102 usechange_cb.setChecked(self.wallet.use_change)
2103 grid_wallet.addWidget(usechange_cb, 1, 0)
2104 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2105 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2107 gap_label = QLabel(_('Gap limit'))
2108 grid_wallet.addWidget(gap_label, 2, 0)
2109 gap_e = AmountEdit(None,True)
2110 gap_e.setText("%d"% self.wallet.gap_limit)
2111 grid_wallet.addWidget(gap_e, 2, 2)
2112 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2113 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2114 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2115 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2116 + _('Warning') + ': ' \
2117 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2118 + _('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'
2119 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2120 if not self.config.is_modifiable('gap_limit'):
2121 for w in [gap_e, gap_label]: w.setEnabled(False)
2123 units = ['BTC', 'mBTC']
2124 unit_label = QLabel(_('Base unit'))
2125 grid_wallet.addWidget(unit_label, 3, 0)
2126 unit_combo = QComboBox()
2127 unit_combo.addItems(units)
2128 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2129 grid_wallet.addWidget(unit_combo, 3, 2)
2130 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2131 + '\n1BTC=1000mBTC.\n' \
2132 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2133 grid_wallet.setRowStretch(4,1)
2137 tab5 = QScrollArea()
2138 tab5.setEnabled(True)
2139 tab5.setWidgetResizable(True)
2141 grid_plugins = QGridLayout()
2142 grid_plugins.setColumnStretch(0,1)
2145 w.setLayout(grid_plugins)
2148 w.setMinimumHeight(len(self.plugins)*35)
2150 tabs.addTab(tab5, _('Plugins') )
2151 def mk_toggle(cb, p):
2152 return lambda: cb.setChecked(p.toggle())
2153 for i, p in enumerate(self.plugins):
2155 name, description = p.get_info()
2156 cb = QCheckBox(name)
2157 cb.setDisabled(not p.is_available())
2158 cb.setChecked(p.is_enabled())
2159 cb.clicked.connect(mk_toggle(cb,p))
2160 grid_plugins.addWidget(cb, i, 0)
2161 if p.requires_settings():
2162 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2163 grid_plugins.addWidget(HelpButton(description), i, 2)
2165 print_msg("Error: cannot display plugin", p)
2166 traceback.print_exc(file=sys.stdout)
2167 grid_plugins.setRowStretch(i+1,1)
2169 self.run_hook('create_settings_tab', tabs)
2171 vbox.addLayout(ok_cancel_buttons(d))
2175 if not d.exec_(): return
2177 fee = unicode(fee_e.text())
2179 fee = self.read_amount(fee)
2181 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2184 self.wallet.set_fee(fee)
2186 nz = unicode(nz_e.text())
2191 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2194 if self.wallet.num_zeros != nz:
2195 self.wallet.num_zeros = nz
2196 self.config.set_key('num_zeros', nz, True)
2197 self.update_history_tab()
2198 self.update_receive_tab()
2200 usechange_result = usechange_cb.isChecked()
2201 if self.wallet.use_change != usechange_result:
2202 self.wallet.use_change = usechange_result
2203 self.config.set_key('use_change', self.wallet.use_change, True)
2205 unit_result = units[unit_combo.currentIndex()]
2206 if self.base_unit() != unit_result:
2207 self.decimal_point = 8 if unit_result == 'BTC' else 5
2208 self.config.set_key('decimal_point', self.decimal_point, True)
2209 self.update_history_tab()
2210 self.update_status()
2213 n = int(gap_e.text())
2215 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2218 if self.wallet.gap_limit != n:
2219 r = self.wallet.change_gap_limit(n)
2221 self.update_receive_tab()
2222 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2224 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2226 need_restart = False
2228 lang_request = languages.keys()[lang_combo.currentIndex()]
2229 if lang_request != self.config.get('language'):
2230 self.config.set_key("language", lang_request, True)
2233 cur_request = str(currencies[cur_combo.currentIndex()])
2234 if cur_request != self.config.get('currency', "None"):
2235 self.config.set_key('currency', cur_request, True)
2236 self.update_wallet()
2238 self.run_hook('close_settings_dialog')
2241 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2243 self.receive_tab_set_mode(expert_cb.isChecked())
2245 def run_network_dialog(self):
2246 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2248 def closeEvent(self, event):
2250 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2251 self.save_column_widths()
2252 self.config.set_key("console-history",self.console.history[-50:])
2261 def __init__(self, wallet, config, app=None):
2262 self.wallet = wallet
2263 self.config = config
2265 self.app = QApplication(sys.argv)
2268 def restore_or_create(self):
2269 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2270 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2271 if r==2: return None
2272 return 'restore' if r==1 else 'create'
2275 def verify_seed(self):
2276 r = self.seed_dialog(False)
2277 if r != self.wallet.seed:
2278 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2285 def seed_dialog(self, is_restore=True):
2289 vbox = QVBoxLayout()
2291 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2293 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2295 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2298 label.setWordWrap(True)
2299 vbox.addWidget(label)
2301 seed_e = QTextEdit()
2302 seed_e.setMaximumHeight(100)
2303 vbox.addWidget(seed_e)
2306 grid = QGridLayout()
2308 gap_e = AmountEdit(None, True)
2310 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2311 grid.addWidget(gap_e, 2, 1)
2312 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2313 vbox.addLayout(grid)
2315 vbox.addLayout(ok_cancel_buttons(d))
2318 if not d.exec_(): return
2321 seed = str(seed_e.toPlainText())
2325 seed = mnemonic.mn_decode( seed.split() )
2327 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2331 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2338 gap = int(unicode(gap_e.text()))
2340 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2345 def network_dialog(self):
2346 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2349 def show_seed(self):
2350 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2352 def password_dialog(self):
2353 if self.wallet.seed:
2354 ElectrumWindow.change_password_dialog(self.wallet)
2357 def restore_wallet(self):
2358 wallet = self.wallet
2359 # wait until we are connected, because the user might have selected another server
2360 if not wallet.interface.is_connected:
2361 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2362 waiting_dialog(waiting)
2364 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2365 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2367 wallet.set_up_to_date(False)
2368 wallet.interface.poke('synchronizer')
2369 waiting_dialog(waiting)
2370 if wallet.is_found():
2371 print_error( "Recovery successful" )
2373 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2380 w = ElectrumWindow(self.wallet, self.config)
2381 if url: w.set_url(url)