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
28 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
30 from PyQt4.QtGui import *
31 from PyQt4.QtCore import *
32 import PyQt4.QtCore as QtCore
34 from electrum.bitcoin import MIN_RELAY_TX_FEE
39 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41 from electrum.wallet import format_satoshis
42 from electrum.bitcoin import Transaction, is_valid
43 from electrum import mnemonic
44 from electrum import util, bitcoin, commands
46 import bmp, pyqrnative
49 from amountedit import AmountEdit
50 from network_dialog import NetworkDialog
51 from qrcodewidget import QRCodeWidget
53 from decimal import Decimal
61 if platform.system() == 'Windows':
62 MONOSPACE_FONT = 'Lucida Console'
63 elif platform.system() == 'Darwin':
64 MONOSPACE_FONT = 'Monaco'
66 MONOSPACE_FONT = 'monospace'
68 from electrum import ELECTRUM_VERSION
73 class UpdateLabel(QLabel):
74 def __init__(self, config, parent=None):
75 QLabel.__init__(self, parent)
76 self.new_version = False
79 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
80 con.request("GET", "/version")
81 res = con.getresponse()
82 except socket.error as msg:
83 print_error("Could not retrieve version information")
87 self.latest_version = res.read()
88 self.latest_version = self.latest_version.replace("\n","")
89 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
91 self.current_version = ELECTRUM_VERSION
92 if(self.compare_versions(self.latest_version, self.current_version) == 1):
93 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
94 if(self.compare_versions(self.latest_version, latest_seen) == 1):
95 self.new_version = True
96 self.setText(_("New version available") + ": " + self.latest_version)
99 def compare_versions(self, version1, version2):
101 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
102 return cmp(normalize(version1), normalize(version2))
104 def ignore_this_version(self):
106 self.config.set_key("last_seen_version", self.latest_version, True)
107 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
110 def ignore_all_version(self):
112 self.config.set_key("last_seen_version", "9.9.9", True)
113 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
116 def open_website(self):
117 webbrowser.open("http://electrum.org/download.html")
120 def mouseReleaseEvent(self, event):
121 dialog = QDialog(self)
122 dialog.setWindowTitle(_('Electrum update'))
125 main_layout = QGridLayout()
126 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
128 ignore_version = QPushButton(_("Ignore this version"))
129 ignore_version.clicked.connect(self.ignore_this_version)
131 ignore_all_versions = QPushButton(_("Ignore all versions"))
132 ignore_all_versions.clicked.connect(self.ignore_all_version)
134 open_website = QPushButton(_("Goto download page"))
135 open_website.clicked.connect(self.open_website)
137 main_layout.addWidget(ignore_version, 1, 0)
138 main_layout.addWidget(ignore_all_versions, 1, 1)
139 main_layout.addWidget(open_website, 1, 2)
141 dialog.setLayout(main_layout)
145 if not dialog.exec_(): return
149 class Timer(QtCore.QThread):
152 self.emit(QtCore.SIGNAL('timersignal'))
155 class HelpButton(QPushButton):
156 def __init__(self, text):
157 QPushButton.__init__(self, '?')
158 self.setFocusPolicy(Qt.NoFocus)
159 self.setFixedWidth(20)
160 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
163 class EnterButton(QPushButton):
164 def __init__(self, text, func):
165 QPushButton.__init__(self, text)
167 self.clicked.connect(func)
169 def keyPressEvent(self, e):
170 if e.key() == QtCore.Qt.Key_Return:
173 class MyTreeWidget(QTreeWidget):
174 def __init__(self, parent):
175 QTreeWidget.__init__(self, parent)
178 for i in range(0,self.viewport().height()/5):
179 if self.itemAt(QPoint(0,i*5)) == item:
183 for j in range(0,30):
184 if self.itemAt(QPoint(0,i*5 + j)) != item:
186 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
188 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
193 class StatusBarButton(QPushButton):
194 def __init__(self, icon, tooltip, func):
195 QPushButton.__init__(self, icon, '')
196 self.setToolTip(tooltip)
198 self.setMaximumWidth(25)
199 self.clicked.connect(func)
202 def keyPressEvent(self, e):
203 if e.key() == QtCore.Qt.Key_Return:
210 def waiting_dialog(f):
216 w.setWindowTitle('Electrum')
226 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
235 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
237 class ElectrumWindow(QMainWindow):
239 def __init__(self, wallet, config):
240 QMainWindow.__init__(self)
244 self.current_account = self.config.get("current_account", None)
247 self.create_status_bar()
249 self.need_update = threading.Event()
250 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
251 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
252 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
253 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
255 self.expert_mode = config.get('classic_expert_mode', False)
256 self.decimal_point = config.get('decimal_point', 8)
258 set_language(config.get('language'))
260 self.funds_error = False
261 self.completions = QStringListModel()
263 self.tabs = tabs = QTabWidget(self)
264 self.column_widths = self.config.get("column_widths", default_column_widths )
265 tabs.addTab(self.create_history_tab(), _('History') )
266 tabs.addTab(self.create_send_tab(), _('Send') )
267 tabs.addTab(self.create_receive_tab(), _('Receive') )
268 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
269 tabs.addTab(self.create_console_tab(), _('Console') )
270 tabs.setMinimumSize(600, 400)
271 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
272 self.setCentralWidget(tabs)
274 g = self.config.get("winpos-qt",[100, 100, 840, 400])
275 self.setGeometry(g[0], g[1], g[2], g[3])
276 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
277 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
278 self.setWindowTitle( title )
282 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
283 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
284 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
285 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
287 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
288 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
289 self.history_list.setFocus(True)
291 self.exchanger = exchange_rate.Exchanger(self)
292 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
294 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
295 if platform.system() == 'Windows':
296 n = 3 if self.wallet.seed else 2
297 tabs.setCurrentIndex (n)
298 tabs.setCurrentIndex (0)
301 if self.wallet.fee < 50000:
302 self.wallet.set_fee(50000)
303 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
305 # set initial message
306 self.console.showMessage(self.wallet.interface.banner)
308 # plugins that need to change the GUI do it here
309 self.run_hook('init_gui')
313 def init_menubar(self):
316 electrum_menu = menubar.addMenu(_("&File"))
317 preferences_name = _("Preferences")
318 if sys.platform == 'darwin':
319 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
321 preferences_menu = electrum_menu.addAction(preferences_name)
322 electrum_menu.addSeparator()
324 accounts_menu = electrum_menu.addMenu(_("&Accounts"))
325 accounts_menu.addAction(_("All accounts"))
326 accounts_menu.addAction(_("Main account"))
327 accounts_menu.addAction(_("Imported keys"))
329 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
330 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
331 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
333 electrum_menu.addSeparator()
334 quit_item = electrum_menu.addAction(_("&Close"))
336 wallet_menu = menubar.addMenu(_("&Wallet"))
337 wallet_backup = wallet_menu.addAction(_("&Create backup"))
338 show_seed = wallet_menu.addAction(_("&Show seed"))
339 chnage_password = wallet_menu.addAction(_("&Password"))
340 wallet_menu.addSeparator()
341 new_contact = wallet_menu.addAction(_("&New contact"))
343 import_menu = menubar.addMenu(_("&Import"))
344 in_labels = import_menu.addAction(_("&Labels"))
345 in_private_keys = import_menu.addAction(_("&Private key"))
347 export_menu = menubar.addMenu(_("&Export"))
348 ex_private_keys = export_menu.addAction(_("&Private keys"))
349 ex_history = export_menu.addAction(_("&History"))
350 ex_labels = export_menu.addAction(_("&Labels"))
352 help_menu = menubar.addMenu(_("&Help"))
353 help_menu.addAction(_("&About"))
354 help_menu.addAction(_("&Documentation")) # http://electrum.org/documentation.html
355 help_menu.addAction(_("&Official website"))
357 self.setMenuBar(menubar)
361 def load_wallet(self, filename):
364 config = electrum.SimpleConfig({'wallet_path': filename})
365 if not config.wallet_file_exists:
366 self.show_message("file not found "+ filename)
369 #self.wallet.verifier.stop()
370 interface = self.wallet.interface
371 verifier = self.wallet.verifier
372 self.wallet.synchronizer.stop()
375 self.wallet = electrum.Wallet(self.config)
376 self.wallet.interface = interface
377 self.wallet.verifier = verifier
379 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
387 def init_plugins(self):
388 import imp, pkgutil, __builtin__
389 if __builtin__.use_local_modules:
390 fp, pathname, description = imp.find_module('plugins')
391 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
392 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
393 imp.load_module('electrum_plugins', fp, pathname, description)
394 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
396 import electrum_plugins
397 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
398 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
403 self.plugins.append( p.Plugin(self) )
405 print_msg("Error:cannot initialize plugin",p)
406 traceback.print_exc(file=sys.stdout)
409 def run_hook(self, name, *args):
410 for p in self.plugins:
411 if not p.is_enabled():
420 print_error("Plugin error")
421 traceback.print_exc(file=sys.stdout)
426 def set_label(self, name, text = None):
428 old_text = self.wallet.labels.get(name)
431 self.wallet.labels[name] = text
435 self.wallet.labels.pop(name)
437 self.run_hook('set_label', name, text, changed)
441 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
442 def getOpenFileName(self, title, filter = None):
443 directory = self.config.get('io_dir', os.path.expanduser('~'))
444 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
445 if fileName and directory != os.path.dirname(fileName):
446 self.config.set_key('io_dir', os.path.dirname(fileName), True)
449 def getSaveFileName(self, title, filename, filter = None):
450 directory = self.config.get('io_dir', os.path.expanduser('~'))
451 path = os.path.join( directory, filename )
452 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
453 if fileName and directory != os.path.dirname(fileName):
454 self.config.set_key('io_dir', os.path.dirname(fileName), True)
460 QMainWindow.close(self)
461 self.run_hook('close_main_window')
463 def connect_slots(self, sender):
464 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
465 self.previous_payto_e=''
467 def timer_actions(self):
468 if self.need_update.is_set():
470 self.need_update.clear()
471 self.run_hook('timer_actions')
473 def format_amount(self, x, is_diff=False):
474 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
476 def read_amount(self, x):
477 if x in['.', '']: return None
478 p = pow(10, self.decimal_point)
479 return int( p * Decimal(x) )
482 assert self.decimal_point in [5,8]
483 return "BTC" if self.decimal_point == 8 else "mBTC"
485 def update_status(self):
486 if self.wallet.interface and self.wallet.interface.is_connected:
487 if not self.wallet.up_to_date:
488 text = _("Synchronizing...")
489 icon = QIcon(":icons/status_waiting.png")
491 c, u = self.wallet.get_account_balance(self.current_account)
492 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
493 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
494 text += self.create_quote_text(Decimal(c+u)/100000000)
495 icon = QIcon(":icons/status_connected.png")
497 text = _("Not connected")
498 icon = QIcon(":icons/status_disconnected.png")
500 self.balance_label.setText(text)
501 self.status_button.setIcon( icon )
503 def update_wallet(self):
505 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
506 self.update_history_tab()
507 self.update_receive_tab()
508 self.update_contacts_tab()
509 self.update_completions()
512 def create_quote_text(self, btc_balance):
513 quote_currency = self.config.get("currency", "None")
514 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
515 if quote_balance is None:
518 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
521 def create_history_tab(self):
522 self.history_list = l = MyTreeWidget(self)
524 for i,width in enumerate(self.column_widths['history']):
525 l.setColumnWidth(i, width)
526 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
527 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
528 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
530 l.setContextMenuPolicy(Qt.CustomContextMenu)
531 l.customContextMenuRequested.connect(self.create_history_menu)
535 def create_history_menu(self, position):
536 self.history_list.selectedIndexes()
537 item = self.history_list.currentItem()
539 tx_hash = str(item.data(0, Qt.UserRole).toString())
540 if not tx_hash: return
542 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
543 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
544 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
545 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
548 def show_tx_details(self, tx):
549 dialog = QDialog(self)
551 dialog.setWindowTitle(_("Transaction Details"))
553 dialog.setLayout(vbox)
554 dialog.setMinimumSize(600,300)
557 if tx_hash in self.wallet.transactions.keys():
558 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
559 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
561 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
567 vbox.addWidget(QLabel("Transaction ID:"))
568 e = QLineEdit(tx_hash)
572 vbox.addWidget(QLabel("Date: %s"%time_str))
573 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
576 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
577 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
579 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
580 vbox.addWidget(QLabel("Transaction fee: unknown"))
582 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
584 vbox.addWidget( self.generate_transaction_information_widget(tx) )
586 ok_button = QPushButton(_("Close"))
587 ok_button.setDefault(True)
588 ok_button.clicked.connect(dialog.accept)
592 hbox.addWidget(ok_button)
596 def tx_label_clicked(self, item, column):
597 if column==2 and item.isSelected():
599 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
600 self.history_list.editItem( item, column )
601 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
604 def tx_label_changed(self, item, column):
608 tx_hash = str(item.data(0, Qt.UserRole).toString())
609 tx = self.wallet.transactions.get(tx_hash)
610 text = unicode( item.text(2) )
611 self.set_label(tx_hash, text)
613 item.setForeground(2, QBrush(QColor('black')))
615 text = self.wallet.get_default_label(tx_hash)
616 item.setText(2, text)
617 item.setForeground(2, QBrush(QColor('gray')))
621 def edit_label(self, is_recv):
622 l = self.receive_list if is_recv else self.contacts_list
623 item = l.currentItem()
624 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
625 l.editItem( item, 1 )
626 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
630 def address_label_clicked(self, item, column, l, column_addr, column_label):
631 if column == column_label and item.isSelected():
632 is_editable = item.data(0, 32).toBool()
635 addr = unicode( item.text(column_addr) )
636 label = unicode( item.text(column_label) )
637 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
638 l.editItem( item, column )
639 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
642 def address_label_changed(self, item, column, l, column_addr, column_label):
643 if column == column_label:
644 addr = unicode( item.text(column_addr) )
645 text = unicode( item.text(column_label) )
646 is_editable = item.data(0, 32).toBool()
650 changed = self.set_label(addr, text)
652 self.update_history_tab()
653 self.update_completions()
655 self.current_item_changed(item)
657 self.run_hook('item_changed', item, column)
660 def current_item_changed(self, a):
661 self.run_hook('current_item_changed', a)
665 def update_history_tab(self):
667 self.history_list.clear()
668 for item in self.wallet.get_tx_history(self.current_account):
669 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
672 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
677 time_str = 'unverified'
678 icon = QIcon(":icons/unconfirmed.png")
681 icon = QIcon(":icons/unconfirmed.png")
683 icon = QIcon(":icons/clock%d.png"%conf)
685 icon = QIcon(":icons/confirmed.png")
687 if value is not None:
688 v_str = self.format_amount(value, True)
692 balance_str = self.format_amount(balance)
695 label, is_default_label = self.wallet.get_label(tx_hash)
697 label = _('Pruned transaction outputs')
698 is_default_label = False
700 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
701 item.setFont(2, QFont(MONOSPACE_FONT))
702 item.setFont(3, QFont(MONOSPACE_FONT))
703 item.setFont(4, QFont(MONOSPACE_FONT))
705 item.setForeground(3, QBrush(QColor("#BC1E1E")))
707 item.setData(0, Qt.UserRole, tx_hash)
708 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
710 item.setForeground(2, QBrush(QColor('grey')))
712 item.setIcon(0, icon)
713 self.history_list.insertTopLevelItem(0,item)
716 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
719 def create_send_tab(self):
724 grid.setColumnMinimumWidth(3,300)
725 grid.setColumnStretch(5,1)
728 self.payto_e = QLineEdit()
729 grid.addWidget(QLabel(_('Pay to')), 1, 0)
730 grid.addWidget(self.payto_e, 1, 1, 1, 3)
732 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)
734 completer = QCompleter()
735 completer.setCaseSensitivity(False)
736 self.payto_e.setCompleter(completer)
737 completer.setModel(self.completions)
739 self.message_e = QLineEdit()
740 grid.addWidget(QLabel(_('Description')), 2, 0)
741 grid.addWidget(self.message_e, 2, 1, 1, 3)
742 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)
744 self.amount_e = AmountEdit(self.base_unit)
745 grid.addWidget(QLabel(_('Amount')), 3, 0)
746 grid.addWidget(self.amount_e, 3, 1, 1, 2)
747 grid.addWidget(HelpButton(
748 _('Amount to be sent.') + '\n\n' \
749 + _('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.') \
750 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
752 self.fee_e = AmountEdit(self.base_unit)
753 grid.addWidget(QLabel(_('Fee')), 4, 0)
754 grid.addWidget(self.fee_e, 4, 1, 1, 2)
755 grid.addWidget(HelpButton(
756 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
757 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
758 + _('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)
761 b = EnterButton(_("Send"), self.do_send)
763 b = EnterButton(_("Create unsigned transaction"), self.do_send)
764 grid.addWidget(b, 6, 1)
766 b = EnterButton(_("Clear"),self.do_clear)
767 grid.addWidget(b, 6, 2)
769 self.payto_sig = QLabel('')
770 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
772 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
773 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
782 def entry_changed( is_fee ):
783 self.funds_error = False
785 if self.amount_e.is_shortcut:
786 self.amount_e.is_shortcut = False
787 c, u = self.wallet.get_account_balance(self.current_account)
788 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
789 fee = self.wallet.estimated_fee(inputs)
791 self.amount_e.setText( self.format_amount(amount) )
792 self.fee_e.setText( self.format_amount( fee ) )
795 amount = self.read_amount(str(self.amount_e.text()))
796 fee = self.read_amount(str(self.fee_e.text()))
798 if not is_fee: fee = None
801 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
803 self.fee_e.setText( self.format_amount( fee ) )
806 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
810 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
811 self.funds_error = True
812 text = _( "Not enough funds" )
813 c, u = self.wallet.get_frozen_balance()
814 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
816 self.statusBar().showMessage(text)
817 self.amount_e.setPalette(palette)
818 self.fee_e.setPalette(palette)
820 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
821 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
823 self.run_hook('create_send_tab', grid)
827 def update_completions(self):
829 for addr,label in self.wallet.labels.items():
830 if addr in self.wallet.addressbook:
831 l.append( label + ' <' + addr + '>')
833 self.run_hook('update_completions', l)
834 self.completions.setStringList(l)
838 return lambda s, *args: s.do_protect(func, args)
842 def do_send(self, password):
844 label = unicode( self.message_e.text() )
845 r = unicode( self.payto_e.text() )
848 # label or alias, with address in brackets
849 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
850 to_address = m.group(2) if m else r
852 if not is_valid(to_address):
853 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
857 amount = self.read_amount(unicode( self.amount_e.text()))
859 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
862 fee = self.read_amount(unicode( self.fee_e.text()))
864 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
868 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
869 except BaseException, e:
870 self.show_message(str(e))
873 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
874 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
877 self.run_hook('send_tx', tx)
880 self.set_label(tx.hash(), label)
883 h = self.wallet.send_tx(tx)
884 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
885 status, msg = self.wallet.receive_tx( h )
887 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
889 self.update_contacts_tab()
891 QMessageBox.warning(self, _('Error'), msg, _('OK'))
893 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
895 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
896 with open(fileName,'w') as f:
897 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
898 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
900 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
905 def set_url(self, url):
906 address, amount, label, message, signature, identity, url = util.parse_url(url)
907 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
909 if label and self.wallet.labels.get(address) != label:
910 if self.question('Give label "%s" to address %s ?'%(label,address)):
911 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
912 self.wallet.addressbook.append(address)
913 self.set_label(address, label)
915 self.run_hook('set_url', url, self.show_message, self.question)
917 self.tabs.setCurrentIndex(1)
918 label = self.wallet.labels.get(address)
919 m_addr = label + ' <'+ address +'>' if label else address
920 self.payto_e.setText(m_addr)
922 self.message_e.setText(message)
923 self.amount_e.setText(amount)
925 self.set_frozen(self.payto_e,True)
926 self.set_frozen(self.amount_e,True)
927 self.set_frozen(self.message_e,True)
928 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
930 self.payto_sig.setVisible(False)
933 self.payto_sig.setVisible(False)
934 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
936 self.set_frozen(e,False)
939 def set_frozen(self,entry,frozen):
941 entry.setReadOnly(True)
942 entry.setFrame(False)
944 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
945 entry.setPalette(palette)
947 entry.setReadOnly(False)
950 palette.setColor(entry.backgroundRole(), QColor('white'))
951 entry.setPalette(palette)
954 def toggle_freeze(self,addr):
956 if addr in self.wallet.frozen_addresses:
957 self.wallet.unfreeze(addr)
959 self.wallet.freeze(addr)
960 self.update_receive_tab()
962 def toggle_priority(self,addr):
964 if addr in self.wallet.prioritized_addresses:
965 self.wallet.unprioritize(addr)
967 self.wallet.prioritize(addr)
968 self.update_receive_tab()
971 def create_list_tab(self, headers):
972 "generic tab creation method"
973 l = MyTreeWidget(self)
974 l.setColumnCount( len(headers) )
975 l.setHeaderLabels( headers )
985 vbox.addWidget(buttons)
990 buttons.setLayout(hbox)
995 def create_receive_tab(self):
996 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
997 l.setContextMenuPolicy(Qt.CustomContextMenu)
998 l.customContextMenuRequested.connect(self.create_receive_menu)
999 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1000 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1001 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1002 self.receive_list = l
1003 self.receive_buttons_hbox = hbox
1008 def receive_tab_set_mode(self, i):
1009 self.save_column_widths()
1010 self.expert_mode = (i == 1)
1011 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1012 self.update_receive_tab()
1015 def save_column_widths(self):
1016 if not self.expert_mode:
1017 widths = [ self.receive_list.columnWidth(0) ]
1020 for i in range(self.receive_list.columnCount() -1):
1021 widths.append(self.receive_list.columnWidth(i))
1022 self.column_widths["receive"][self.expert_mode] = widths
1024 self.column_widths["history"] = []
1025 for i in range(self.history_list.columnCount() - 1):
1026 self.column_widths["history"].append(self.history_list.columnWidth(i))
1028 self.column_widths["contacts"] = []
1029 for i in range(self.contacts_list.columnCount() - 1):
1030 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1032 self.config.set_key("column_widths", self.column_widths, True)
1035 def create_contacts_tab(self):
1036 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1037 l.setContextMenuPolicy(Qt.CustomContextMenu)
1038 l.customContextMenuRequested.connect(self.create_contact_menu)
1039 for i,width in enumerate(self.column_widths['contacts']):
1040 l.setColumnWidth(i, width)
1042 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1043 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1044 self.contacts_list = l
1045 self.contacts_buttons_hbox = hbox
1046 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1051 def delete_imported_key(self, addr):
1052 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1053 self.wallet.delete_imported_key(addr)
1054 self.update_receive_tab()
1055 self.update_history_tab()
1058 def create_receive_menu(self, position):
1059 # fixme: this function apparently has a side effect.
1060 # if it is not called the menu pops up several times
1061 #self.receive_list.selectedIndexes()
1063 item = self.receive_list.itemAt(position)
1065 addr = unicode(item.text(0))
1066 if not is_valid(addr):
1067 item.setExpanded(not item.isExpanded())
1070 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1071 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1072 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1073 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1074 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1075 if addr in self.wallet.imported_keys:
1076 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1078 if self.expert_mode:
1079 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1080 menu.addAction(t, lambda: self.toggle_freeze(addr))
1081 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1082 menu.addAction(t, lambda: self.toggle_priority(addr))
1084 self.run_hook('receive_menu', menu)
1085 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1088 def payto(self, addr):
1090 label = self.wallet.labels.get(addr)
1091 m_addr = label + ' <' + addr + '>' if label else addr
1092 self.tabs.setCurrentIndex(1)
1093 self.payto_e.setText(m_addr)
1094 self.amount_e.setFocus()
1097 def delete_contact(self, x):
1098 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1099 self.wallet.delete_contact(x)
1100 self.set_label(x, None)
1101 self.update_history_tab()
1102 self.update_contacts_tab()
1103 self.update_completions()
1106 def create_contact_menu(self, position):
1107 item = self.contacts_list.itemAt(position)
1109 addr = unicode(item.text(0))
1110 label = unicode(item.text(1))
1111 is_editable = item.data(0,32).toBool()
1112 payto_addr = item.data(0,33).toString()
1114 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1115 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1116 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1118 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1119 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1121 self.run_hook('create_contact_menu', menu, item)
1122 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1125 def update_receive_item(self, item):
1126 item.setFont(0, QFont(MONOSPACE_FONT))
1127 address = str(item.data(0,0).toString())
1128 label = self.wallet.labels.get(address,'')
1129 item.setData(1,0,label)
1130 item.setData(0,32, True) # is editable
1132 self.run_hook('update_receive_item', address, item)
1134 c, u = self.wallet.get_addr_balance(address)
1135 balance = self.format_amount(c + u)
1136 item.setData(2,0,balance)
1138 if self.expert_mode:
1139 if address in self.wallet.frozen_addresses:
1140 item.setBackgroundColor(0, QColor('lightblue'))
1141 elif address in self.wallet.prioritized_addresses:
1142 item.setBackgroundColor(0, QColor('lightgreen'))
1145 def update_receive_tab(self):
1146 l = self.receive_list
1149 l.setColumnHidden(2, not self.expert_mode)
1150 l.setColumnHidden(3, not self.expert_mode)
1151 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1152 l.setColumnWidth(i, width)
1154 if self.current_account is None:
1155 account_items = self.wallet.accounts.items()
1156 elif self.current_account != -1:
1157 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1161 for k, account in account_items:
1162 name = account.get('name',str(k))
1163 c,u = self.wallet.get_account_balance(k)
1164 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1165 l.addTopLevelItem(account_item)
1166 account_item.setExpanded(True)
1168 for is_change in ([0,1] if self.expert_mode else [0]):
1169 if self.expert_mode:
1170 name = "Receiving" if not is_change else "Change"
1171 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1172 account_item.addChild(seq_item)
1173 if not is_change: seq_item.setExpanded(True)
1175 seq_item = account_item
1179 for address in account[is_change]:
1180 h = self.wallet.history.get(address,[])
1184 if gap > self.wallet.gap_limit:
1189 num_tx = '*' if h == ['*'] else "%d"%len(h)
1190 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1191 self.update_receive_item(item)
1193 item.setBackgroundColor(1, QColor('red'))
1194 seq_item.addChild(item)
1197 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1198 c,u = self.wallet.get_imported_balance()
1199 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1200 l.addTopLevelItem(account_item)
1201 account_item.setExpanded(True)
1202 for address in self.wallet.imported_keys.keys():
1203 item = QTreeWidgetItem( [ address, '', '', ''] )
1204 self.update_receive_item(item)
1205 account_item.addChild(item)
1208 # we use column 1 because column 0 may be hidden
1209 l.setCurrentItem(l.topLevelItem(0),1)
1212 def update_contacts_tab(self):
1214 l = self.contacts_list
1217 for address in self.wallet.addressbook:
1218 label = self.wallet.labels.get(address,'')
1219 n = self.wallet.get_num_tx(address)
1220 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1221 item.setFont(0, QFont(MONOSPACE_FONT))
1222 # 32 = label can be edited (bool)
1223 item.setData(0,32, True)
1225 item.setData(0,33, address)
1226 l.addTopLevelItem(item)
1228 self.run_hook('update_contacts_tab', l)
1229 l.setCurrentItem(l.topLevelItem(0))
1233 def create_console_tab(self):
1234 from qt_console import Console
1235 self.console = console = Console()
1236 self.console.history = self.config.get("console-history",[])
1237 self.console.history_index = len(self.console.history)
1239 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1240 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1242 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1244 def mkfunc(f, method):
1245 return lambda *args: apply( f, (method, args, self.password_dialog ))
1247 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1248 methods[m] = mkfunc(c._run, m)
1250 console.updateNamespace(methods)
1253 def change_account(self,s):
1254 if s == _("All accounts"):
1255 self.current_account = None
1257 accounts = self.wallet.get_accounts()
1258 for k, v in accounts.items():
1260 self.current_account = k
1261 self.update_history_tab()
1262 self.update_status()
1263 self.update_receive_tab()
1265 def create_status_bar(self):
1268 sb.setFixedHeight(35)
1269 qtVersion = qVersion()
1271 self.balance_label = QLabel("")
1272 sb.addWidget(self.balance_label)
1274 update_notification = UpdateLabel(self.config)
1275 if(update_notification.new_version):
1276 sb.addPermanentWidget(update_notification)
1278 accounts = self.wallet.get_accounts()
1279 if len(accounts) > 1:
1280 from_combo = QComboBox()
1281 from_combo.addItems([_("All accounts")] + accounts.values())
1282 from_combo.setCurrentIndex(0)
1283 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1284 sb.addPermanentWidget(from_combo)
1286 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1287 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1288 if self.wallet.seed:
1289 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1290 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1291 sb.addPermanentWidget( self.password_button )
1292 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1293 if self.wallet.seed:
1294 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1295 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1296 sb.addPermanentWidget( self.status_button )
1298 self.run_hook('create_status_bar', (sb,))
1300 self.setStatusBar(sb)
1304 self.config.set_key('gui', 'lite', True)
1307 self.lite.mini.show()
1309 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1310 self.lite.main(None)
1312 def new_contact_dialog(self):
1313 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1314 address = unicode(text)
1316 if is_valid(address):
1317 self.wallet.add_contact(address)
1318 self.update_contacts_tab()
1319 self.update_history_tab()
1320 self.update_completions()
1322 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1324 def show_master_public_key(self):
1325 dialog = QDialog(self)
1327 dialog.setWindowTitle(_("Master Public Key"))
1329 main_text = QTextEdit()
1330 main_text.setText(self.wallet.get_master_public_key())
1331 main_text.setReadOnly(True)
1332 main_text.setMaximumHeight(170)
1333 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1335 ok_button = QPushButton(_("OK"))
1336 ok_button.setDefault(True)
1337 ok_button.clicked.connect(dialog.accept)
1339 main_layout = QGridLayout()
1340 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1342 main_layout.addWidget(main_text, 1, 0)
1343 main_layout.addWidget(qrw, 1, 1 )
1345 vbox = QVBoxLayout()
1346 vbox.addLayout(main_layout)
1347 hbox = QHBoxLayout()
1349 hbox.addWidget(ok_button)
1350 vbox.addLayout(hbox)
1352 dialog.setLayout(vbox)
1357 def show_seed_dialog(self, password):
1358 if not self.wallet.seed:
1359 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1362 seed = self.wallet.decode_seed(password)
1364 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1366 self.show_seed(seed, self.wallet.imported_keys, self)
1370 def show_seed(self, seed, imported_keys, parent=None):
1371 dialog = QDialog(parent)
1373 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1375 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1377 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1379 seed_text = QTextEdit(brainwallet)
1380 seed_text.setReadOnly(True)
1381 seed_text.setMaximumHeight(130)
1383 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1384 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1385 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1386 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1388 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1389 label2 = QLabel(msg2)
1390 label2.setWordWrap(True)
1393 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1394 logo.setMaximumWidth(60)
1396 qrw = QRCodeWidget(seed)
1398 ok_button = QPushButton(_("OK"))
1399 ok_button.setDefault(True)
1400 ok_button.clicked.connect(dialog.accept)
1402 grid = QGridLayout()
1403 #main_layout.addWidget(logo, 0, 0)
1405 grid.addWidget(logo, 0, 0)
1406 grid.addWidget(label1, 0, 1)
1408 grid.addWidget(seed_text, 1, 0, 1, 2)
1410 grid.addWidget(qrw, 0, 2, 2, 1)
1412 vbox = QVBoxLayout()
1413 vbox.addLayout(grid)
1414 vbox.addWidget(label2)
1416 hbox = QHBoxLayout()
1418 hbox.addWidget(ok_button)
1419 vbox.addLayout(hbox)
1421 dialog.setLayout(vbox)
1424 def show_qrcode(self, data, title = "QR code"):
1428 d.setWindowTitle(title)
1429 d.setMinimumSize(270, 300)
1430 vbox = QVBoxLayout()
1431 qrw = QRCodeWidget(data)
1432 vbox.addWidget(qrw, 1)
1433 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1434 hbox = QHBoxLayout()
1438 filename = "qrcode.bmp"
1439 bmp.save_qrcode(qrw.qr, filename)
1440 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1442 b = QPushButton(_("Save"))
1444 b.clicked.connect(print_qr)
1446 b = QPushButton(_("Close"))
1448 b.clicked.connect(d.accept)
1451 vbox.addLayout(hbox)
1456 def do_protect(self, func, args):
1457 if self.wallet.use_encryption:
1458 password = self.password_dialog()
1464 if args != (False,):
1465 args = (self,) + args + (password,)
1467 args = (self,password)
1472 def show_private_key(self, address, password):
1473 if not address: return
1475 pk = self.wallet.get_private_key(address, password)
1476 except BaseException, e:
1477 self.show_message(str(e))
1479 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1483 def do_sign(self, address, message, signature, password):
1485 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1486 signature.setText(sig)
1487 except BaseException, e:
1488 self.show_message(str(e))
1490 def sign_message(self, address):
1491 if not address: return
1494 d.setWindowTitle(_('Sign Message'))
1495 d.setMinimumSize(410, 290)
1497 tab_widget = QTabWidget()
1499 layout = QGridLayout(tab)
1501 sign_address = QLineEdit()
1503 sign_address.setText(address)
1504 layout.addWidget(QLabel(_('Address')), 1, 0)
1505 layout.addWidget(sign_address, 1, 1)
1507 sign_message = QTextEdit()
1508 layout.addWidget(QLabel(_('Message')), 2, 0)
1509 layout.addWidget(sign_message, 2, 1)
1510 layout.setRowStretch(2,3)
1512 sign_signature = QTextEdit()
1513 layout.addWidget(QLabel(_('Signature')), 3, 0)
1514 layout.addWidget(sign_signature, 3, 1)
1515 layout.setRowStretch(3,1)
1518 hbox = QHBoxLayout()
1519 b = QPushButton(_("Sign"))
1521 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1522 b = QPushButton(_("Close"))
1523 b.clicked.connect(d.accept)
1525 layout.addLayout(hbox, 4, 1)
1526 tab_widget.addTab(tab, _("Sign"))
1530 layout = QGridLayout(tab)
1532 verify_address = QLineEdit()
1533 layout.addWidget(QLabel(_('Address')), 1, 0)
1534 layout.addWidget(verify_address, 1, 1)
1536 verify_message = QTextEdit()
1537 layout.addWidget(QLabel(_('Message')), 2, 0)
1538 layout.addWidget(verify_message, 2, 1)
1539 layout.setRowStretch(2,3)
1541 verify_signature = QTextEdit()
1542 layout.addWidget(QLabel(_('Signature')), 3, 0)
1543 layout.addWidget(verify_signature, 3, 1)
1544 layout.setRowStretch(3,1)
1547 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1548 self.show_message(_("Signature verified"))
1550 self.show_message(_("Error: wrong signature"))
1552 hbox = QHBoxLayout()
1553 b = QPushButton(_("Verify"))
1554 b.clicked.connect(do_verify)
1556 b = QPushButton(_("Close"))
1557 b.clicked.connect(d.accept)
1559 layout.addLayout(hbox, 4, 1)
1560 tab_widget.addTab(tab, _("Verify"))
1562 vbox = QVBoxLayout()
1563 vbox.addWidget(tab_widget)
1570 def question(self, msg):
1571 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1573 def show_message(self, msg):
1574 QMessageBox.information(self, _('Message'), msg, _('OK'))
1576 def password_dialog(self ):
1583 vbox = QVBoxLayout()
1584 msg = _('Please enter your password')
1585 vbox.addWidget(QLabel(msg))
1587 grid = QGridLayout()
1589 grid.addWidget(QLabel(_('Password')), 1, 0)
1590 grid.addWidget(pw, 1, 1)
1591 vbox.addLayout(grid)
1593 vbox.addLayout(ok_cancel_buttons(d))
1596 self.run_hook('password_dialog', pw, grid, 1)
1597 if not d.exec_(): return
1598 return unicode(pw.text())
1605 def change_password_dialog( wallet, parent=None ):
1608 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1616 new_pw = QLineEdit()
1617 new_pw.setEchoMode(2)
1618 conf_pw = QLineEdit()
1619 conf_pw.setEchoMode(2)
1621 vbox = QVBoxLayout()
1623 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1624 +_('To disable wallet encryption, enter an empty new password.')) \
1625 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1627 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1628 +_("Leave these fields empty if you want to disable encryption.")
1629 vbox.addWidget(QLabel(msg))
1631 grid = QGridLayout()
1634 if wallet.use_encryption:
1635 grid.addWidget(QLabel(_('Password')), 1, 0)
1636 grid.addWidget(pw, 1, 1)
1638 grid.addWidget(QLabel(_('New Password')), 2, 0)
1639 grid.addWidget(new_pw, 2, 1)
1641 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1642 grid.addWidget(conf_pw, 3, 1)
1643 vbox.addLayout(grid)
1645 vbox.addLayout(ok_cancel_buttons(d))
1648 if not d.exec_(): return
1650 password = unicode(pw.text()) if wallet.use_encryption else None
1651 new_password = unicode(new_pw.text())
1652 new_password2 = unicode(conf_pw.text())
1655 seed = wallet.decode_seed(password)
1657 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1660 if new_password != new_password2:
1661 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1662 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1665 wallet.update_password(seed, password, new_password)
1667 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1670 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1673 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1674 parent.password_button.setIcon( icon )
1678 def generate_transaction_information_widget(self, tx):
1679 tabs = QTabWidget(self)
1682 grid_ui = QGridLayout(tab1)
1683 grid_ui.setColumnStretch(0,1)
1684 tabs.addTab(tab1, _('Outputs') )
1686 tree_widget = MyTreeWidget(self)
1687 tree_widget.setColumnCount(2)
1688 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1689 tree_widget.setColumnWidth(0, 300)
1690 tree_widget.setColumnWidth(1, 50)
1692 for address, value in tx.outputs:
1693 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1694 tree_widget.addTopLevelItem(item)
1696 tree_widget.setMaximumHeight(100)
1698 grid_ui.addWidget(tree_widget)
1701 grid_ui = QGridLayout(tab2)
1702 grid_ui.setColumnStretch(0,1)
1703 tabs.addTab(tab2, _('Inputs') )
1705 tree_widget = MyTreeWidget(self)
1706 tree_widget.setColumnCount(2)
1707 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1709 for input_line in tx.inputs:
1710 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1711 tree_widget.addTopLevelItem(item)
1713 tree_widget.setMaximumHeight(100)
1715 grid_ui.addWidget(tree_widget)
1719 def tx_dict_from_text(self, txt):
1721 tx_dict = json.loads(str(txt))
1722 assert "hex" in tx_dict.keys()
1723 assert "complete" in tx_dict.keys()
1724 if not tx_dict["complete"]:
1725 assert "input_info" in tx_dict.keys()
1727 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1732 def read_tx_from_file(self):
1733 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1737 with open(fileName, "r") as f:
1738 file_content = f.read()
1739 except (ValueError, IOError, os.error), reason:
1740 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1742 return self.tx_dict_from_text(file_content)
1746 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1748 self.wallet.signrawtransaction(tx, input_info, [], password)
1750 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1752 with open(fileName, "w+") as f:
1753 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1754 self.show_message(_("Transaction saved successfully"))
1757 except BaseException, e:
1758 self.show_message(str(e))
1761 def send_raw_transaction(self, raw_tx, dialog = ""):
1762 result, result_message = self.wallet.sendtx( raw_tx )
1764 self.show_message("Transaction successfully sent: %s" % (result_message))
1768 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1770 def do_process_from_text(self):
1771 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1774 tx_dict = self.tx_dict_from_text(text)
1776 self.create_process_transaction_window(tx_dict)
1778 def do_process_from_file(self):
1779 tx_dict = self.read_tx_from_file()
1781 self.create_process_transaction_window(tx_dict)
1783 def create_process_transaction_window(self, tx_dict):
1784 tx = Transaction(tx_dict["hex"])
1786 dialog = QDialog(self)
1787 dialog.setMinimumWidth(500)
1788 dialog.setWindowTitle(_('Process raw transaction'))
1794 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1795 l.addWidget(QLabel(_("Actions")), 4,0)
1797 if tx_dict["complete"] == False:
1798 l.addWidget(QLabel(_("Unsigned")), 3,1)
1799 if self.wallet.seed :
1800 b = QPushButton("Sign transaction")
1801 input_info = json.loads(tx_dict["input_info"])
1802 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1803 l.addWidget(b, 4, 1)
1805 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1807 l.addWidget(QLabel(_("Signed")), 3,1)
1808 b = QPushButton("Broadcast transaction")
1809 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1812 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1813 cancelButton = QPushButton(_("Cancel"))
1814 cancelButton.clicked.connect(lambda: dialog.done(0))
1815 l.addWidget(cancelButton, 4,2)
1821 def do_export_privkeys(self, password):
1822 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.")))
1825 select_export = _('Select file to export your private keys to')
1826 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1828 with open(fileName, "w+") as csvfile:
1829 transaction = csv.writer(csvfile)
1830 transaction.writerow(["address", "private_key"])
1833 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1834 transaction.writerow(["%34s"%addr,pk])
1836 self.show_message(_("Private keys exported."))
1838 except (IOError, os.error), reason:
1839 export_error_label = _("Electrum was unable to produce a private key-export.")
1840 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1842 except BaseException, e:
1843 self.show_message(str(e))
1847 def do_import_labels(self):
1848 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1849 if not labelsFile: return
1851 f = open(labelsFile, 'r')
1854 for key, value in json.loads(data).items():
1855 self.wallet.labels[key] = value
1857 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1858 except (IOError, os.error), reason:
1859 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1862 def do_export_labels(self):
1863 labels = self.wallet.labels
1865 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1867 with open(fileName, 'w+') as f:
1868 json.dump(labels, f)
1869 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1870 except (IOError, os.error), reason:
1871 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1874 def do_export_history(self):
1875 from gui_lite import csv_transaction
1876 csv_transaction(self.wallet)
1880 def do_import_privkey(self, password):
1881 if not self.wallet.imported_keys:
1882 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1883 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1884 + _('Are you sure you understand what you are doing?'), 3, 4)
1887 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1890 text = str(text).split()
1895 addr = self.wallet.import_key(key, password)
1896 except BaseException as e:
1902 addrlist.append(addr)
1904 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1906 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1907 self.update_receive_tab()
1908 self.update_history_tab()
1911 def settings_dialog(self):
1913 d.setWindowTitle(_('Electrum Settings'))
1915 vbox = QVBoxLayout()
1917 tabs = QTabWidget(self)
1918 self.settings_tab = tabs
1919 vbox.addWidget(tabs)
1922 grid_ui = QGridLayout(tab1)
1923 grid_ui.setColumnStretch(0,1)
1924 tabs.addTab(tab1, _('Display') )
1926 nz_label = QLabel(_('Display zeros'))
1927 grid_ui.addWidget(nz_label, 0, 0)
1928 nz_e = AmountEdit(None,True)
1929 nz_e.setText("%d"% self.wallet.num_zeros)
1930 grid_ui.addWidget(nz_e, 0, 1)
1931 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1932 grid_ui.addWidget(HelpButton(msg), 0, 2)
1933 if not self.config.is_modifiable('num_zeros'):
1934 for w in [nz_e, nz_label]: w.setEnabled(False)
1936 lang_label=QLabel(_('Language') + ':')
1937 grid_ui.addWidget(lang_label, 1, 0)
1938 lang_combo = QComboBox()
1939 from i18n import languages
1940 lang_combo.addItems(languages.values())
1942 index = languages.keys().index(self.config.get("language",''))
1945 lang_combo.setCurrentIndex(index)
1946 grid_ui.addWidget(lang_combo, 1, 1)
1947 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1948 if not self.config.is_modifiable('language'):
1949 for w in [lang_combo, lang_label]: w.setEnabled(False)
1951 currencies = self.exchanger.get_currencies()
1952 currencies.insert(0, "None")
1954 cur_label=QLabel(_('Currency') + ':')
1955 grid_ui.addWidget(cur_label , 2, 0)
1956 cur_combo = QComboBox()
1957 cur_combo.addItems(currencies)
1959 index = currencies.index(self.config.get('currency', "None"))
1962 cur_combo.setCurrentIndex(index)
1963 grid_ui.addWidget(cur_combo, 2, 1)
1964 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1966 expert_cb = QCheckBox(_('Expert mode'))
1967 expert_cb.setChecked(self.expert_mode)
1968 grid_ui.addWidget(expert_cb, 3, 0)
1969 hh = _('In expert mode, your client will:') + '\n' \
1970 + _(' - Show change addresses in the Receive tab') + '\n' \
1971 + _(' - Display the balance of each address') + '\n' \
1972 + _(' - Add freeze/prioritize actions to addresses.')
1973 grid_ui.addWidget(HelpButton(hh), 3, 2)
1974 grid_ui.setRowStretch(4,1)
1978 grid_wallet = QGridLayout(tab2)
1979 grid_wallet.setColumnStretch(0,1)
1980 tabs.addTab(tab2, _('Wallet') )
1982 fee_label = QLabel(_('Transaction fee'))
1983 grid_wallet.addWidget(fee_label, 0, 0)
1984 fee_e = AmountEdit(self.base_unit)
1985 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1986 grid_wallet.addWidget(fee_e, 0, 2)
1987 msg = _('Fee per kilobyte of transaction.') + ' ' \
1988 + _('Recommended value') + ': ' + self.format_amount(50000)
1989 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1990 if not self.config.is_modifiable('fee_per_kb'):
1991 for w in [fee_e, fee_label]: w.setEnabled(False)
1993 usechange_cb = QCheckBox(_('Use change addresses'))
1994 usechange_cb.setChecked(self.wallet.use_change)
1995 grid_wallet.addWidget(usechange_cb, 1, 0)
1996 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1997 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1999 gap_label = QLabel(_('Gap limit'))
2000 grid_wallet.addWidget(gap_label, 2, 0)
2001 gap_e = AmountEdit(None,True)
2002 gap_e.setText("%d"% self.wallet.gap_limit)
2003 grid_wallet.addWidget(gap_e, 2, 2)
2004 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2005 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2006 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2007 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2008 + _('Warning') + ': ' \
2009 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2010 + _('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'
2011 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2012 if not self.config.is_modifiable('gap_limit'):
2013 for w in [gap_e, gap_label]: w.setEnabled(False)
2015 units = ['BTC', 'mBTC']
2016 unit_label = QLabel(_('Base unit'))
2017 grid_wallet.addWidget(unit_label, 3, 0)
2018 unit_combo = QComboBox()
2019 unit_combo.addItems(units)
2020 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2021 grid_wallet.addWidget(unit_combo, 3, 2)
2022 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2023 + '\n1BTC=1000mBTC.\n' \
2024 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2025 grid_wallet.setRowStretch(4,1)
2030 grid_io = QGridLayout(tab3)
2031 grid_io.setColumnStretch(0,1)
2032 tabs.addTab(tab3, _('Import/Export') )
2034 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2035 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2036 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2037 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2039 grid_io.addWidget(QLabel(_('History')), 2, 0)
2040 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2041 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2043 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2045 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2046 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2047 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2049 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2050 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2051 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2052 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2053 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2056 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2057 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2058 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2059 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2061 grid_io.setRowStretch(6,1)
2066 tab5 = QScrollArea()
2067 tab5.setEnabled(True)
2068 tab5.setWidgetResizable(True)
2070 grid_plugins = QGridLayout()
2071 grid_plugins.setColumnStretch(0,1)
2074 w.setLayout(grid_plugins)
2076 tab5.setMaximumSize(tab3.size()) # optional
2078 w.setMinimumHeight(len(self.plugins)*35)
2080 tabs.addTab(tab5, _('Plugins') )
2081 def mk_toggle(cb, p):
2082 return lambda: cb.setChecked(p.toggle())
2083 for i, p in enumerate(self.plugins):
2085 name, description = p.get_info()
2086 cb = QCheckBox(name)
2087 cb.setDisabled(not p.is_available())
2088 cb.setChecked(p.is_enabled())
2089 cb.clicked.connect(mk_toggle(cb,p))
2090 grid_plugins.addWidget(cb, i, 0)
2091 if p.requires_settings():
2092 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2093 grid_plugins.addWidget(HelpButton(description), i, 2)
2095 print_msg("Error: cannot display plugin", p)
2096 traceback.print_exc(file=sys.stdout)
2097 grid_plugins.setRowStretch(i+1,1)
2099 self.run_hook('create_settings_tab', tabs)
2101 vbox.addLayout(ok_cancel_buttons(d))
2105 if not d.exec_(): return
2107 fee = unicode(fee_e.text())
2109 fee = self.read_amount(fee)
2111 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2114 self.wallet.set_fee(fee)
2116 nz = unicode(nz_e.text())
2121 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2124 if self.wallet.num_zeros != nz:
2125 self.wallet.num_zeros = nz
2126 self.config.set_key('num_zeros', nz, True)
2127 self.update_history_tab()
2128 self.update_receive_tab()
2130 usechange_result = usechange_cb.isChecked()
2131 if self.wallet.use_change != usechange_result:
2132 self.wallet.use_change = usechange_result
2133 self.config.set_key('use_change', self.wallet.use_change, True)
2135 unit_result = units[unit_combo.currentIndex()]
2136 if self.base_unit() != unit_result:
2137 self.decimal_point = 8 if unit_result == 'BTC' else 5
2138 self.config.set_key('decimal_point', self.decimal_point, True)
2139 self.update_history_tab()
2140 self.update_status()
2143 n = int(gap_e.text())
2145 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2148 if self.wallet.gap_limit != n:
2149 r = self.wallet.change_gap_limit(n)
2151 self.update_receive_tab()
2152 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2154 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2156 need_restart = False
2158 lang_request = languages.keys()[lang_combo.currentIndex()]
2159 if lang_request != self.config.get('language'):
2160 self.config.set_key("language", lang_request, True)
2163 cur_request = str(currencies[cur_combo.currentIndex()])
2164 if cur_request != self.config.get('currency', "None"):
2165 self.config.set_key('currency', cur_request, True)
2166 self.update_wallet()
2168 self.run_hook('close_settings_dialog')
2171 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2173 self.receive_tab_set_mode(expert_cb.isChecked())
2175 def run_network_dialog(self):
2176 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2178 def closeEvent(self, event):
2180 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2181 self.save_column_widths()
2182 self.config.set_key("console-history",self.console.history[-50:])
2191 def __init__(self, wallet, config, app=None):
2192 self.wallet = wallet
2193 self.config = config
2195 self.app = QApplication(sys.argv)
2198 def restore_or_create(self):
2199 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2200 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2201 if r==2: return None
2202 return 'restore' if r==1 else 'create'
2205 def verify_seed(self):
2206 r = self.seed_dialog(False)
2207 if r != self.wallet.seed:
2208 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2215 def seed_dialog(self, is_restore=True):
2219 vbox = QVBoxLayout()
2221 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2223 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2225 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2228 label.setWordWrap(True)
2229 vbox.addWidget(label)
2231 seed_e = QTextEdit()
2232 seed_e.setMaximumHeight(100)
2233 vbox.addWidget(seed_e)
2236 grid = QGridLayout()
2238 gap_e = AmountEdit(None, True)
2240 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2241 grid.addWidget(gap_e, 2, 1)
2242 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2243 vbox.addLayout(grid)
2245 vbox.addLayout(ok_cancel_buttons(d))
2248 if not d.exec_(): return
2251 seed = str(seed_e.toPlainText())
2255 seed = mnemonic.mn_decode( seed.split() )
2257 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2261 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2268 gap = int(unicode(gap_e.text()))
2270 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2275 def network_dialog(self):
2276 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2279 def show_seed(self):
2280 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2282 def password_dialog(self):
2283 if self.wallet.seed:
2284 ElectrumWindow.change_password_dialog(self.wallet)
2287 def restore_wallet(self):
2288 wallet = self.wallet
2289 # wait until we are connected, because the user might have selected another server
2290 if not wallet.interface.is_connected:
2291 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2292 waiting_dialog(waiting)
2294 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2295 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2297 wallet.set_up_to_date(False)
2298 wallet.interface.poke('synchronizer')
2299 waiting_dialog(waiting)
2300 if wallet.is_found():
2301 print_error( "Recovery successful" )
2303 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2310 w = ElectrumWindow(self.wallet, self.config)
2311 if url: w.set_url(url)