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')
351 def select_wallet_file(self):
352 wallet_folder = self.wallet.config.path
353 re.sub("(\/\w*.dat)$", "", wallet_folder)
354 file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
358 self.load_wallet(file_name)
361 def init_menubar(self):
364 electrum_menu = menubar.addMenu(_("&File"))
365 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
366 open_wallet_action.triggered.connect(self.select_wallet_file)
368 preferences_name = _("Preferences")
369 if sys.platform == 'darwin':
370 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
372 preferences_menu = electrum_menu.addAction(preferences_name)
373 preferences_menu.triggered.connect(self.settings_dialog)
374 electrum_menu.addSeparator()
376 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
378 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
379 raw_transaction_file.triggered.connect(self.do_process_from_file)
381 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
382 raw_transaction_text.triggered.connect(self.do_process_from_text)
384 electrum_menu.addSeparator()
385 quit_item = electrum_menu.addAction(_("&Close"))
386 quit_item.triggered.connect(self.close)
388 wallet_menu = menubar.addMenu(_("&Wallet"))
389 wallet_backup = wallet_menu.addAction(_("&Create backup"))
390 wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
392 show_menu = wallet_menu.addMenu(_("Show"))
395 show_seed = show_menu.addAction(_("&Seed"))
396 show_seed.triggered.connect(self.show_seed_dialog)
398 show_mpk = show_menu.addAction(_("&Master Public Key"))
399 show_mpk.triggered.connect(self.show_master_public_key)
401 wallet_menu.addSeparator()
402 new_contact = wallet_menu.addAction(_("&New contact"))
403 new_contact.triggered.connect(self.new_contact_dialog)
405 new_account = wallet_menu.addAction(_("&New account"))
406 new_account.triggered.connect(self.new_account_dialog)
408 import_menu = menubar.addMenu(_("&Import"))
409 in_labels = import_menu.addAction(_("&Labels"))
410 in_labels.triggered.connect(self.do_import_labels)
412 in_private_keys = import_menu.addAction(_("&Private keys"))
413 in_private_keys.triggered.connect(self.do_import_privkey)
415 export_menu = menubar.addMenu(_("&Export"))
416 ex_private_keys = export_menu.addAction(_("&Private keys"))
417 ex_private_keys.triggered.connect(self.do_export_privkeys)
419 ex_history = export_menu.addAction(_("&History"))
420 ex_history.triggered.connect(self.do_export_history)
422 ex_labels = export_menu.addAction(_("&Labels"))
423 ex_labels.triggered.connect(self.do_export_labels)
425 help_menu = menubar.addMenu(_("&Help"))
426 doc_open = help_menu.addAction(_("&Documentation"))
427 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
428 web_open = help_menu.addAction(_("&Official website"))
429 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
431 self.setMenuBar(menubar)
433 def load_wallet(self, filename):
436 config = electrum.SimpleConfig({'wallet_path': filename})
437 if not config.wallet_file_exists:
438 self.show_message("file not found "+ filename)
441 #self.wallet.verifier.stop()
442 interface = self.wallet.interface
443 verifier = self.wallet.verifier
444 self.wallet.synchronizer.stop()
447 self.wallet = electrum.Wallet(self.config)
448 self.wallet.interface = interface
449 self.wallet.verifier = verifier
451 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
456 def notify_transactions(self):
457 print_error("Notifying GUI")
458 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
459 # Combine the transactions if there are more then three
460 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
463 for tx in self.wallet.interface.pending_transactions_for_notifications:
464 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
468 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
469 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
471 self.wallet.interface.pending_transactions_for_notifications = []
473 for tx in self.wallet.interface.pending_transactions_for_notifications:
475 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
476 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
478 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
480 def notify(self, message):
481 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
484 def init_plugins(self):
485 import imp, pkgutil, __builtin__
486 if __builtin__.use_local_modules:
487 fp, pathname, description = imp.find_module('plugins')
488 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
489 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
490 imp.load_module('electrum_plugins', fp, pathname, description)
491 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
493 import electrum_plugins
494 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
495 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
498 for name, p in zip(plugin_names, plugins):
500 self.plugins.append( p.Plugin(self, name) )
502 print_msg("Error:cannot initialize plugin",p)
503 traceback.print_exc(file=sys.stdout)
506 def run_hook(self, name, *args):
507 for p in self.plugins:
508 if not p.is_enabled():
517 print_error("Plugin error")
518 traceback.print_exc(file=sys.stdout)
523 def set_label(self, name, text = None):
525 old_text = self.wallet.labels.get(name)
528 self.wallet.labels[name] = text
529 self.wallet.config.set_key('labels', self.wallet.labels)
533 self.wallet.labels.pop(name)
535 self.run_hook('set_label', name, text, changed)
539 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
540 def getOpenFileName(self, title, filter = None):
541 directory = self.config.get('io_dir', os.path.expanduser('~'))
542 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
543 if fileName and directory != os.path.dirname(fileName):
544 self.config.set_key('io_dir', os.path.dirname(fileName), True)
547 def getSaveFileName(self, title, filename, filter = None):
548 directory = self.config.get('io_dir', os.path.expanduser('~'))
549 path = os.path.join( directory, filename )
550 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
551 if fileName and directory != os.path.dirname(fileName):
552 self.config.set_key('io_dir', os.path.dirname(fileName), True)
558 QMainWindow.close(self)
559 self.run_hook('close_main_window')
561 def connect_slots(self, sender):
562 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
563 self.previous_payto_e=''
565 def timer_actions(self):
566 if self.need_update.is_set():
568 self.need_update.clear()
569 self.run_hook('timer_actions')
571 def format_amount(self, x, is_diff=False, whitespaces=False):
572 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
574 def read_amount(self, x):
575 if x in['.', '']: return None
576 p = pow(10, self.decimal_point)
577 return int( p * Decimal(x) )
580 assert self.decimal_point in [5,8]
581 return "BTC" if self.decimal_point == 8 else "mBTC"
583 def update_status(self):
584 if self.wallet.interface and self.wallet.interface.is_connected:
585 if not self.wallet.up_to_date:
586 text = _("Synchronizing...")
587 icon = QIcon(":icons/status_waiting.png")
589 c, u = self.wallet.get_account_balance(self.current_account)
590 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
591 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
592 text += self.create_quote_text(Decimal(c+u)/100000000)
593 self.tray.setToolTip(text)
594 icon = QIcon(":icons/status_connected.png")
596 text = _("Not connected")
597 icon = QIcon(":icons/status_disconnected.png")
599 self.balance_label.setText(text)
600 self.status_button.setIcon( icon )
602 def update_wallet(self):
604 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
605 self.update_history_tab()
606 self.update_receive_tab()
607 self.update_contacts_tab()
608 self.update_completions()
611 def create_quote_text(self, btc_balance):
612 quote_currency = self.config.get("currency", "None")
613 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
614 if quote_balance is None:
617 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
620 def create_history_tab(self):
621 self.history_list = l = MyTreeWidget(self)
623 for i,width in enumerate(self.column_widths['history']):
624 l.setColumnWidth(i, width)
625 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
626 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
627 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
629 l.setContextMenuPolicy(Qt.CustomContextMenu)
630 l.customContextMenuRequested.connect(self.create_history_menu)
634 def create_history_menu(self, position):
635 self.history_list.selectedIndexes()
636 item = self.history_list.currentItem()
638 tx_hash = str(item.data(0, Qt.UserRole).toString())
639 if not tx_hash: return
641 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
642 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
643 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
644 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
647 def show_tx_details(self, tx):
648 dialog = QDialog(self)
650 dialog.setWindowTitle(_("Transaction Details"))
652 dialog.setLayout(vbox)
653 dialog.setMinimumSize(600,300)
656 if tx_hash in self.wallet.transactions.keys():
657 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
658 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
660 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
666 vbox.addWidget(QLabel("Transaction ID:"))
667 e = QLineEdit(tx_hash)
671 vbox.addWidget(QLabel("Date: %s"%time_str))
672 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
675 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
676 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
678 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
679 vbox.addWidget(QLabel("Transaction fee: unknown"))
681 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
683 vbox.addWidget( self.generate_transaction_information_widget(tx) )
685 ok_button = QPushButton(_("Close"))
686 ok_button.setDefault(True)
687 ok_button.clicked.connect(dialog.accept)
691 hbox.addWidget(ok_button)
695 def tx_label_clicked(self, item, column):
696 if column==2 and item.isSelected():
698 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
699 self.history_list.editItem( item, column )
700 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
703 def tx_label_changed(self, item, column):
707 tx_hash = str(item.data(0, Qt.UserRole).toString())
708 tx = self.wallet.transactions.get(tx_hash)
709 text = unicode( item.text(2) )
710 self.set_label(tx_hash, text)
712 item.setForeground(2, QBrush(QColor('black')))
714 text = self.wallet.get_default_label(tx_hash)
715 item.setText(2, text)
716 item.setForeground(2, QBrush(QColor('gray')))
720 def edit_label(self, is_recv):
721 l = self.receive_list if is_recv else self.contacts_list
722 item = l.currentItem()
723 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
724 l.editItem( item, 1 )
725 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
729 def address_label_clicked(self, item, column, l, column_addr, column_label):
730 if column == column_label and item.isSelected():
731 is_editable = item.data(0, 32).toBool()
734 addr = unicode( item.text(column_addr) )
735 label = unicode( item.text(column_label) )
736 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
737 l.editItem( item, column )
738 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
741 def address_label_changed(self, item, column, l, column_addr, column_label):
742 if column == column_label:
743 addr = unicode( item.text(column_addr) )
744 text = unicode( item.text(column_label) )
745 is_editable = item.data(0, 32).toBool()
749 changed = self.set_label(addr, text)
751 self.update_history_tab()
752 self.update_completions()
754 self.current_item_changed(item)
756 self.run_hook('item_changed', item, column)
759 def current_item_changed(self, a):
760 self.run_hook('current_item_changed', a)
764 def update_history_tab(self):
766 self.history_list.clear()
767 for item in self.wallet.get_tx_history(self.current_account):
768 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
771 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
776 time_str = 'unverified'
777 icon = QIcon(":icons/unconfirmed.png")
780 icon = QIcon(":icons/unconfirmed.png")
782 icon = QIcon(":icons/clock%d.png"%conf)
784 icon = QIcon(":icons/confirmed.png")
786 if value is not None:
787 v_str = self.format_amount(value, True, whitespaces=True)
791 balance_str = self.format_amount(balance, whitespaces=True)
794 label, is_default_label = self.wallet.get_label(tx_hash)
796 label = _('Pruned transaction outputs')
797 is_default_label = False
799 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
800 item.setFont(2, QFont(MONOSPACE_FONT))
801 item.setFont(3, QFont(MONOSPACE_FONT))
802 item.setFont(4, QFont(MONOSPACE_FONT))
804 item.setForeground(3, QBrush(QColor("#BC1E1E")))
806 item.setData(0, Qt.UserRole, tx_hash)
807 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
809 item.setForeground(2, QBrush(QColor('grey')))
811 item.setIcon(0, icon)
812 self.history_list.insertTopLevelItem(0,item)
815 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
818 def create_send_tab(self):
823 grid.setColumnMinimumWidth(3,300)
824 grid.setColumnStretch(5,1)
827 self.payto_e = QLineEdit()
828 grid.addWidget(QLabel(_('Pay to')), 1, 0)
829 grid.addWidget(self.payto_e, 1, 1, 1, 3)
831 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)
833 completer = QCompleter()
834 completer.setCaseSensitivity(False)
835 self.payto_e.setCompleter(completer)
836 completer.setModel(self.completions)
838 self.message_e = QLineEdit()
839 grid.addWidget(QLabel(_('Description')), 2, 0)
840 grid.addWidget(self.message_e, 2, 1, 1, 3)
841 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)
843 self.amount_e = AmountEdit(self.base_unit)
844 grid.addWidget(QLabel(_('Amount')), 3, 0)
845 grid.addWidget(self.amount_e, 3, 1, 1, 2)
846 grid.addWidget(HelpButton(
847 _('Amount to be sent.') + '\n\n' \
848 + _('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.') \
849 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
851 self.fee_e = AmountEdit(self.base_unit)
852 grid.addWidget(QLabel(_('Fee')), 4, 0)
853 grid.addWidget(self.fee_e, 4, 1, 1, 2)
854 grid.addWidget(HelpButton(
855 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
856 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
857 + _('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)
860 b = EnterButton(_("Send"), self.do_send)
862 b = EnterButton(_("Create unsigned transaction"), self.do_send)
863 grid.addWidget(b, 6, 1)
865 b = EnterButton(_("Clear"),self.do_clear)
866 grid.addWidget(b, 6, 2)
868 self.payto_sig = QLabel('')
869 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
871 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
872 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
881 def entry_changed( is_fee ):
882 self.funds_error = False
884 if self.amount_e.is_shortcut:
885 self.amount_e.is_shortcut = False
886 c, u = self.wallet.get_account_balance(self.current_account)
887 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
888 fee = self.wallet.estimated_fee(inputs)
890 self.amount_e.setText( self.format_amount(amount) )
891 self.fee_e.setText( self.format_amount( fee ) )
894 amount = self.read_amount(str(self.amount_e.text()))
895 fee = self.read_amount(str(self.fee_e.text()))
897 if not is_fee: fee = None
900 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
902 self.fee_e.setText( self.format_amount( fee ) )
905 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
909 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
910 self.funds_error = True
911 text = _( "Not enough funds" )
912 c, u = self.wallet.get_frozen_balance()
913 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
915 self.statusBar().showMessage(text)
916 self.amount_e.setPalette(palette)
917 self.fee_e.setPalette(palette)
919 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
920 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
922 self.run_hook('create_send_tab', grid)
926 def update_completions(self):
928 for addr,label in self.wallet.labels.items():
929 if addr in self.wallet.addressbook:
930 l.append( label + ' <' + addr + '>')
932 self.run_hook('update_completions', l)
933 self.completions.setStringList(l)
937 return lambda s, *args: s.do_protect(func, args)
942 label = unicode( self.message_e.text() )
943 r = unicode( self.payto_e.text() )
946 # label or alias, with address in brackets
947 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
948 to_address = m.group(2) if m else r
950 if not is_valid(to_address):
951 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
955 amount = self.read_amount(unicode( self.amount_e.text()))
957 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
960 fee = self.read_amount(unicode( self.fee_e.text()))
962 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
965 confirm_amount = self.config.get('confirm_amount', 100000000)
966 if amount >= confirm_amount:
967 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
970 self.send_tx(to_address, amount, fee, label)
974 def send_tx(self, to_address, amount, fee, label, password):
977 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
978 except BaseException, e:
979 traceback.print_exc(file=sys.stdout)
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 = self.wallet.labels.get(k, 'unnamed account')
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.get_addresses(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'))
1433 def new_account_dialog(self):
1434 text, ok = QInputDialog.getText(self, _('New Account'), _('Name') + ':')
1435 if not ok or not text:
1437 name = unicode(text)
1439 self.create_new_account(name)
1441 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1444 def create_new_account(self, name, password):
1445 self.wallet.create_new_account(name, password)
1446 self.wallet.synchronize()
1447 self.update_receive_tab()
1448 self.update_history_tab()
1449 self.update_completions()
1451 def show_master_public_key(self):
1452 dialog = QDialog(self)
1454 dialog.setWindowTitle(_("Master Public Key"))
1456 main_text = QTextEdit()
1457 main_text.setText(self.wallet.get_master_public_key())
1458 main_text.setReadOnly(True)
1459 main_text.setMaximumHeight(170)
1460 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1462 ok_button = QPushButton(_("OK"))
1463 ok_button.setDefault(True)
1464 ok_button.clicked.connect(dialog.accept)
1466 main_layout = QGridLayout()
1467 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1469 main_layout.addWidget(main_text, 1, 0)
1470 main_layout.addWidget(qrw, 1, 1 )
1472 vbox = QVBoxLayout()
1473 vbox.addLayout(main_layout)
1474 hbox = QHBoxLayout()
1476 hbox.addWidget(ok_button)
1477 vbox.addLayout(hbox)
1479 dialog.setLayout(vbox)
1484 def show_seed_dialog(self, password):
1485 if not self.wallet.seed:
1486 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1489 seed = self.wallet.decode_seed(password)
1491 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1493 self.show_seed(seed, self.wallet.imported_keys, self)
1497 def show_seed(self, seed, imported_keys, parent=None):
1498 dialog = QDialog(parent)
1500 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1502 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1504 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1506 seed_text = QTextEdit(brainwallet)
1507 seed_text.setReadOnly(True)
1508 seed_text.setMaximumHeight(130)
1510 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1511 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1512 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1513 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1515 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1516 label2 = QLabel(msg2)
1517 label2.setWordWrap(True)
1520 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1521 logo.setMaximumWidth(60)
1523 qrw = QRCodeWidget(seed)
1525 ok_button = QPushButton(_("OK"))
1526 ok_button.setDefault(True)
1527 ok_button.clicked.connect(dialog.accept)
1529 grid = QGridLayout()
1530 #main_layout.addWidget(logo, 0, 0)
1532 grid.addWidget(logo, 0, 0)
1533 grid.addWidget(label1, 0, 1)
1535 grid.addWidget(seed_text, 1, 0, 1, 2)
1537 grid.addWidget(qrw, 0, 2, 2, 1)
1539 vbox = QVBoxLayout()
1540 vbox.addLayout(grid)
1541 vbox.addWidget(label2)
1543 hbox = QHBoxLayout()
1545 hbox.addWidget(ok_button)
1546 vbox.addLayout(hbox)
1548 dialog.setLayout(vbox)
1551 def show_qrcode(self, data, title = "QR code"):
1555 d.setWindowTitle(title)
1556 d.setMinimumSize(270, 300)
1557 vbox = QVBoxLayout()
1558 qrw = QRCodeWidget(data)
1559 vbox.addWidget(qrw, 1)
1560 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1561 hbox = QHBoxLayout()
1565 filename = "qrcode.bmp"
1566 bmp.save_qrcode(qrw.qr, filename)
1567 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1569 b = QPushButton(_("Save"))
1571 b.clicked.connect(print_qr)
1573 b = QPushButton(_("Close"))
1575 b.clicked.connect(d.accept)
1578 vbox.addLayout(hbox)
1583 def do_protect(self, func, args):
1584 if self.wallet.use_encryption:
1585 password = self.password_dialog()
1591 if args != (False,):
1592 args = (self,) + args + (password,)
1594 args = (self,password)
1599 def show_private_key(self, address, password):
1600 if not address: return
1602 pk = self.wallet.get_private_key(address, password)
1603 except BaseException, e:
1604 self.show_message(str(e))
1606 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1610 def do_sign(self, address, message, signature, password):
1612 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1613 signature.setText(sig)
1614 except BaseException, e:
1615 self.show_message(str(e))
1617 def sign_message(self, address):
1618 if not address: return
1621 d.setWindowTitle(_('Sign Message'))
1622 d.setMinimumSize(410, 290)
1624 tab_widget = QTabWidget()
1626 layout = QGridLayout(tab)
1628 sign_address = QLineEdit()
1630 sign_address.setText(address)
1631 layout.addWidget(QLabel(_('Address')), 1, 0)
1632 layout.addWidget(sign_address, 1, 1)
1634 sign_message = QTextEdit()
1635 layout.addWidget(QLabel(_('Message')), 2, 0)
1636 layout.addWidget(sign_message, 2, 1)
1637 layout.setRowStretch(2,3)
1639 sign_signature = QTextEdit()
1640 layout.addWidget(QLabel(_('Signature')), 3, 0)
1641 layout.addWidget(sign_signature, 3, 1)
1642 layout.setRowStretch(3,1)
1645 hbox = QHBoxLayout()
1646 b = QPushButton(_("Sign"))
1648 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1649 b = QPushButton(_("Close"))
1650 b.clicked.connect(d.accept)
1652 layout.addLayout(hbox, 4, 1)
1653 tab_widget.addTab(tab, _("Sign"))
1657 layout = QGridLayout(tab)
1659 verify_address = QLineEdit()
1660 layout.addWidget(QLabel(_('Address')), 1, 0)
1661 layout.addWidget(verify_address, 1, 1)
1663 verify_message = QTextEdit()
1664 layout.addWidget(QLabel(_('Message')), 2, 0)
1665 layout.addWidget(verify_message, 2, 1)
1666 layout.setRowStretch(2,3)
1668 verify_signature = QTextEdit()
1669 layout.addWidget(QLabel(_('Signature')), 3, 0)
1670 layout.addWidget(verify_signature, 3, 1)
1671 layout.setRowStretch(3,1)
1674 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1675 self.show_message(_("Signature verified"))
1677 self.show_message(_("Error: wrong signature"))
1679 hbox = QHBoxLayout()
1680 b = QPushButton(_("Verify"))
1681 b.clicked.connect(do_verify)
1683 b = QPushButton(_("Close"))
1684 b.clicked.connect(d.accept)
1686 layout.addLayout(hbox, 4, 1)
1687 tab_widget.addTab(tab, _("Verify"))
1689 vbox = QVBoxLayout()
1690 vbox.addWidget(tab_widget)
1697 def question(self, msg):
1698 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1700 def show_message(self, msg):
1701 QMessageBox.information(self, _('Message'), msg, _('OK'))
1703 def password_dialog(self ):
1710 vbox = QVBoxLayout()
1711 msg = _('Please enter your password')
1712 vbox.addWidget(QLabel(msg))
1714 grid = QGridLayout()
1716 grid.addWidget(QLabel(_('Password')), 1, 0)
1717 grid.addWidget(pw, 1, 1)
1718 vbox.addLayout(grid)
1720 vbox.addLayout(ok_cancel_buttons(d))
1723 self.run_hook('password_dialog', pw, grid, 1)
1724 if not d.exec_(): return
1725 return unicode(pw.text())
1732 def change_password_dialog( wallet, parent=None ):
1735 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1743 new_pw = QLineEdit()
1744 new_pw.setEchoMode(2)
1745 conf_pw = QLineEdit()
1746 conf_pw.setEchoMode(2)
1748 vbox = QVBoxLayout()
1750 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1751 +_('To disable wallet encryption, enter an empty new password.')) \
1752 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1754 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1755 +_("Leave these fields empty if you want to disable encryption.")
1756 vbox.addWidget(QLabel(msg))
1758 grid = QGridLayout()
1761 if wallet.use_encryption:
1762 grid.addWidget(QLabel(_('Password')), 1, 0)
1763 grid.addWidget(pw, 1, 1)
1765 grid.addWidget(QLabel(_('New Password')), 2, 0)
1766 grid.addWidget(new_pw, 2, 1)
1768 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1769 grid.addWidget(conf_pw, 3, 1)
1770 vbox.addLayout(grid)
1772 vbox.addLayout(ok_cancel_buttons(d))
1775 if not d.exec_(): return
1777 password = unicode(pw.text()) if wallet.use_encryption else None
1778 new_password = unicode(new_pw.text())
1779 new_password2 = unicode(conf_pw.text())
1782 seed = wallet.decode_seed(password)
1784 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1787 if new_password != new_password2:
1788 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1789 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1792 wallet.update_password(seed, password, new_password)
1794 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1797 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1800 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1801 parent.password_button.setIcon( icon )
1805 def generate_transaction_information_widget(self, tx):
1806 tabs = QTabWidget(self)
1809 grid_ui = QGridLayout(tab1)
1810 grid_ui.setColumnStretch(0,1)
1811 tabs.addTab(tab1, _('Outputs') )
1813 tree_widget = MyTreeWidget(self)
1814 tree_widget.setColumnCount(2)
1815 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1816 tree_widget.setColumnWidth(0, 300)
1817 tree_widget.setColumnWidth(1, 50)
1819 for address, value in tx.outputs:
1820 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1821 tree_widget.addTopLevelItem(item)
1823 tree_widget.setMaximumHeight(100)
1825 grid_ui.addWidget(tree_widget)
1828 grid_ui = QGridLayout(tab2)
1829 grid_ui.setColumnStretch(0,1)
1830 tabs.addTab(tab2, _('Inputs') )
1832 tree_widget = MyTreeWidget(self)
1833 tree_widget.setColumnCount(2)
1834 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1836 for input_line in tx.inputs:
1837 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1838 tree_widget.addTopLevelItem(item)
1840 tree_widget.setMaximumHeight(100)
1842 grid_ui.addWidget(tree_widget)
1846 def tx_dict_from_text(self, txt):
1848 tx_dict = json.loads(str(txt))
1849 assert "hex" in tx_dict.keys()
1850 assert "complete" in tx_dict.keys()
1851 if not tx_dict["complete"]:
1852 assert "input_info" in tx_dict.keys()
1854 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1859 def read_tx_from_file(self):
1860 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1864 with open(fileName, "r") as f:
1865 file_content = f.read()
1866 except (ValueError, IOError, os.error), reason:
1867 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1869 return self.tx_dict_from_text(file_content)
1873 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1875 self.wallet.signrawtransaction(tx, input_info, [], password)
1877 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1879 with open(fileName, "w+") as f:
1880 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1881 self.show_message(_("Transaction saved successfully"))
1884 except BaseException, e:
1885 self.show_message(str(e))
1888 def send_raw_transaction(self, raw_tx, dialog = ""):
1889 result, result_message = self.wallet.sendtx( raw_tx )
1891 self.show_message("Transaction successfully sent: %s" % (result_message))
1895 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1897 def do_process_from_text(self):
1898 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1901 tx_dict = self.tx_dict_from_text(text)
1903 self.create_process_transaction_window(tx_dict)
1905 def do_process_from_file(self):
1906 tx_dict = self.read_tx_from_file()
1908 self.create_process_transaction_window(tx_dict)
1910 def create_process_transaction_window(self, tx_dict):
1911 tx = Transaction(tx_dict["hex"])
1913 dialog = QDialog(self)
1914 dialog.setMinimumWidth(500)
1915 dialog.setWindowTitle(_('Process raw transaction'))
1921 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1922 l.addWidget(QLabel(_("Actions")), 4,0)
1924 if tx_dict["complete"] == False:
1925 l.addWidget(QLabel(_("Unsigned")), 3,1)
1926 if self.wallet.seed :
1927 b = QPushButton("Sign transaction")
1928 input_info = json.loads(tx_dict["input_info"])
1929 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1930 l.addWidget(b, 4, 1)
1932 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1934 l.addWidget(QLabel(_("Signed")), 3,1)
1935 b = QPushButton("Broadcast transaction")
1936 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1939 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1940 cancelButton = QPushButton(_("Cancel"))
1941 cancelButton.clicked.connect(lambda: dialog.done(0))
1942 l.addWidget(cancelButton, 4,2)
1948 def do_export_privkeys(self, password):
1949 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.")))
1952 select_export = _('Select file to export your private keys to')
1953 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1955 with open(fileName, "w+") as csvfile:
1956 transaction = csv.writer(csvfile)
1957 transaction.writerow(["address", "private_key"])
1960 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1961 transaction.writerow(["%34s"%addr,pk])
1963 self.show_message(_("Private keys exported."))
1965 except (IOError, os.error), reason:
1966 export_error_label = _("Electrum was unable to produce a private key-export.")
1967 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1969 except BaseException, e:
1970 self.show_message(str(e))
1974 def do_import_labels(self):
1975 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1976 if not labelsFile: return
1978 f = open(labelsFile, 'r')
1981 for key, value in json.loads(data).items():
1982 self.wallet.labels[key] = value
1984 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1985 except (IOError, os.error), reason:
1986 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1989 def do_export_labels(self):
1990 labels = self.wallet.labels
1992 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1994 with open(fileName, 'w+') as f:
1995 json.dump(labels, f)
1996 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1997 except (IOError, os.error), reason:
1998 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
2001 def do_export_history(self):
2002 from gui_lite import csv_transaction
2003 csv_transaction(self.wallet)
2007 def do_import_privkey(self, password):
2008 if not self.wallet.imported_keys:
2009 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2010 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2011 + _('Are you sure you understand what you are doing?'), 3, 4)
2014 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2017 text = str(text).split()
2022 addr = self.wallet.import_key(key, password)
2023 except BaseException as e:
2029 addrlist.append(addr)
2031 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2033 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2034 self.update_receive_tab()
2035 self.update_history_tab()
2038 def settings_dialog(self):
2040 d.setWindowTitle(_('Electrum Settings'))
2042 vbox = QVBoxLayout()
2044 tabs = QTabWidget(self)
2045 self.settings_tab = tabs
2046 vbox.addWidget(tabs)
2049 grid_ui = QGridLayout(tab1)
2050 grid_ui.setColumnStretch(0,1)
2051 tabs.addTab(tab1, _('Display') )
2053 nz_label = QLabel(_('Display zeros'))
2054 grid_ui.addWidget(nz_label, 0, 0)
2055 nz_e = AmountEdit(None,True)
2056 nz_e.setText("%d"% self.wallet.num_zeros)
2057 grid_ui.addWidget(nz_e, 0, 1)
2058 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2059 grid_ui.addWidget(HelpButton(msg), 0, 2)
2060 if not self.config.is_modifiable('num_zeros'):
2061 for w in [nz_e, nz_label]: w.setEnabled(False)
2063 lang_label=QLabel(_('Language') + ':')
2064 grid_ui.addWidget(lang_label, 1, 0)
2065 lang_combo = QComboBox()
2066 from i18n import languages
2067 lang_combo.addItems(languages.values())
2069 index = languages.keys().index(self.config.get("language",''))
2072 lang_combo.setCurrentIndex(index)
2073 grid_ui.addWidget(lang_combo, 1, 1)
2074 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2075 if not self.config.is_modifiable('language'):
2076 for w in [lang_combo, lang_label]: w.setEnabled(False)
2078 currencies = self.exchanger.get_currencies()
2079 currencies.insert(0, "None")
2081 cur_label=QLabel(_('Currency') + ':')
2082 grid_ui.addWidget(cur_label , 2, 0)
2083 cur_combo = QComboBox()
2084 cur_combo.addItems(currencies)
2086 index = currencies.index(self.config.get('currency', "None"))
2089 cur_combo.setCurrentIndex(index)
2090 grid_ui.addWidget(cur_combo, 2, 1)
2091 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2093 expert_cb = QCheckBox(_('Expert mode'))
2094 expert_cb.setChecked(self.expert_mode)
2095 grid_ui.addWidget(expert_cb, 3, 0)
2096 hh = _('In expert mode, your client will:') + '\n' \
2097 + _(' - Show change addresses in the Receive tab') + '\n' \
2098 + _(' - Display the balance of each address') + '\n' \
2099 + _(' - Add freeze/prioritize actions to addresses.')
2100 grid_ui.addWidget(HelpButton(hh), 3, 2)
2101 grid_ui.setRowStretch(4,1)
2105 grid_wallet = QGridLayout(tab2)
2106 grid_wallet.setColumnStretch(0,1)
2107 tabs.addTab(tab2, _('Wallet') )
2109 fee_label = QLabel(_('Transaction fee'))
2110 grid_wallet.addWidget(fee_label, 0, 0)
2111 fee_e = AmountEdit(self.base_unit)
2112 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2113 grid_wallet.addWidget(fee_e, 0, 2)
2114 msg = _('Fee per kilobyte of transaction.') + ' ' \
2115 + _('Recommended value') + ': ' + self.format_amount(50000)
2116 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2117 if not self.config.is_modifiable('fee_per_kb'):
2118 for w in [fee_e, fee_label]: w.setEnabled(False)
2120 usechange_cb = QCheckBox(_('Use change addresses'))
2121 usechange_cb.setChecked(self.wallet.use_change)
2122 grid_wallet.addWidget(usechange_cb, 1, 0)
2123 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2124 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2126 gap_label = QLabel(_('Gap limit'))
2127 grid_wallet.addWidget(gap_label, 2, 0)
2128 gap_e = AmountEdit(None,True)
2129 gap_e.setText("%d"% self.wallet.gap_limit)
2130 grid_wallet.addWidget(gap_e, 2, 2)
2131 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2132 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2133 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2134 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2135 + _('Warning') + ': ' \
2136 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2137 + _('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'
2138 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2139 if not self.config.is_modifiable('gap_limit'):
2140 for w in [gap_e, gap_label]: w.setEnabled(False)
2142 units = ['BTC', 'mBTC']
2143 unit_label = QLabel(_('Base unit'))
2144 grid_wallet.addWidget(unit_label, 3, 0)
2145 unit_combo = QComboBox()
2146 unit_combo.addItems(units)
2147 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2148 grid_wallet.addWidget(unit_combo, 3, 2)
2149 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2150 + '\n1BTC=1000mBTC.\n' \
2151 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2152 grid_wallet.setRowStretch(4,1)
2156 tab5 = QScrollArea()
2157 tab5.setEnabled(True)
2158 tab5.setWidgetResizable(True)
2160 grid_plugins = QGridLayout()
2161 grid_plugins.setColumnStretch(0,1)
2164 w.setLayout(grid_plugins)
2167 w.setMinimumHeight(len(self.plugins)*35)
2169 tabs.addTab(tab5, _('Plugins') )
2170 def mk_toggle(cb, p):
2171 return lambda: cb.setChecked(p.toggle())
2172 for i, p in enumerate(self.plugins):
2174 cb = QCheckBox(p.fullname())
2175 cb.setDisabled(not p.is_available())
2176 cb.setChecked(p.is_enabled())
2177 cb.clicked.connect(mk_toggle(cb,p))
2178 grid_plugins.addWidget(cb, i, 0)
2179 if p.requires_settings():
2180 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2181 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2183 print_msg("Error: cannot display plugin", p)
2184 traceback.print_exc(file=sys.stdout)
2185 grid_plugins.setRowStretch(i+1,1)
2187 self.run_hook('create_settings_tab', tabs)
2189 vbox.addLayout(ok_cancel_buttons(d))
2193 if not d.exec_(): return
2195 fee = unicode(fee_e.text())
2197 fee = self.read_amount(fee)
2199 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2202 self.wallet.set_fee(fee)
2204 nz = unicode(nz_e.text())
2209 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2212 if self.wallet.num_zeros != nz:
2213 self.wallet.num_zeros = nz
2214 self.config.set_key('num_zeros', nz, True)
2215 self.update_history_tab()
2216 self.update_receive_tab()
2218 usechange_result = usechange_cb.isChecked()
2219 if self.wallet.use_change != usechange_result:
2220 self.wallet.use_change = usechange_result
2221 self.config.set_key('use_change', self.wallet.use_change, True)
2223 unit_result = units[unit_combo.currentIndex()]
2224 if self.base_unit() != unit_result:
2225 self.decimal_point = 8 if unit_result == 'BTC' else 5
2226 self.config.set_key('decimal_point', self.decimal_point, True)
2227 self.update_history_tab()
2228 self.update_status()
2231 n = int(gap_e.text())
2233 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2236 if self.wallet.gap_limit != n:
2237 r = self.wallet.change_gap_limit(n)
2239 self.update_receive_tab()
2240 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2242 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2244 need_restart = False
2246 lang_request = languages.keys()[lang_combo.currentIndex()]
2247 if lang_request != self.config.get('language'):
2248 self.config.set_key("language", lang_request, True)
2251 cur_request = str(currencies[cur_combo.currentIndex()])
2252 if cur_request != self.config.get('currency', "None"):
2253 self.config.set_key('currency', cur_request, True)
2254 self.update_wallet()
2256 self.run_hook('close_settings_dialog')
2259 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2261 self.receive_tab_set_mode(expert_cb.isChecked())
2263 def run_network_dialog(self):
2264 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2266 def closeEvent(self, event):
2268 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2269 self.save_column_widths()
2270 self.config.set_key("console-history",self.console.history[-50:])
2273 class OpenFileEventFilter(QObject):
2274 def __init__(self, windows):
2275 self.windows = windows
2276 super(OpenFileEventFilter, self).__init__()
2278 def eventFilter(self, obj, event):
2279 if event.type() == QtCore.QEvent.FileOpen:
2280 if len(self.windows) >= 1:
2281 self.windows[0].set_url(event.url().toString())
2287 def __init__(self, wallet, config, app=None):
2288 self.wallet = wallet
2289 self.config = config
2291 self.efilter = OpenFileEventFilter(self.windows)
2293 self.app = QApplication(sys.argv)
2294 self.app.installEventFilter(self.efilter)
2296 def restore_or_create(self):
2297 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2298 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2299 if r==2: return None
2300 return 'restore' if r==1 else 'create'
2303 def verify_seed(self):
2304 r = self.seed_dialog(False)
2305 if r != self.wallet.seed:
2306 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2313 def seed_dialog(self, is_restore=True):
2317 vbox = QVBoxLayout()
2319 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2321 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2323 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2326 label.setWordWrap(True)
2327 vbox.addWidget(label)
2329 seed_e = QTextEdit()
2330 seed_e.setMaximumHeight(100)
2331 vbox.addWidget(seed_e)
2334 grid = QGridLayout()
2336 gap_e = AmountEdit(None, True)
2338 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2339 grid.addWidget(gap_e, 2, 1)
2340 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2341 vbox.addLayout(grid)
2343 vbox.addLayout(ok_cancel_buttons(d))
2346 if not d.exec_(): return
2349 seed = str(seed_e.toPlainText())
2353 seed = mnemonic.mn_decode( seed.split() )
2355 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2359 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2366 gap = int(unicode(gap_e.text()))
2368 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2373 def network_dialog(self):
2374 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2377 def show_seed(self):
2378 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2380 def password_dialog(self):
2381 if self.wallet.seed:
2382 ElectrumWindow.change_password_dialog(self.wallet)
2385 def restore_wallet(self):
2386 wallet = self.wallet
2387 # wait until we are connected, because the user might have selected another server
2388 if not wallet.interface.is_connected:
2389 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2390 waiting_dialog(waiting)
2392 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2393 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2395 wallet.set_up_to_date(False)
2396 wallet.interface.poke('synchronizer')
2397 waiting_dialog(waiting)
2398 if wallet.is_found():
2399 print_error( "Recovery successful" )
2401 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2408 w = ElectrumWindow(self.wallet, self.config)
2409 self.windows.append(w)
2410 if url: w.set_url(url)