3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
31 from PyQt4.QtGui import *
32 from PyQt4.QtCore import *
33 import PyQt4.QtCore as QtCore
35 from electrum.bitcoin import MIN_RELAY_TX_FEE
40 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
42 from electrum.wallet import format_satoshis
43 from electrum.bitcoin import Transaction, is_valid
44 from electrum import mnemonic
45 from electrum import util, bitcoin, commands
47 import bmp, pyqrnative
50 from amountedit import AmountEdit
51 from network_dialog import NetworkDialog
52 from qrcodewidget import QRCodeWidget
54 from decimal import Decimal
62 if platform.system() == 'Windows':
63 MONOSPACE_FONT = 'Lucida Console'
64 elif platform.system() == 'Darwin':
65 MONOSPACE_FONT = 'Monaco'
67 MONOSPACE_FONT = 'monospace'
69 from electrum import ELECTRUM_VERSION
74 class UpdateLabel(QLabel):
75 def __init__(self, config, parent=None):
76 QLabel.__init__(self, parent)
77 self.new_version = False
80 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
81 con.request("GET", "/version")
82 res = con.getresponse()
83 except socket.error as msg:
84 print_error("Could not retrieve version information")
88 self.latest_version = res.read()
89 self.latest_version = self.latest_version.replace("\n","")
90 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
92 self.current_version = ELECTRUM_VERSION
93 if(self.compare_versions(self.latest_version, self.current_version) == 1):
94 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
95 if(self.compare_versions(self.latest_version, latest_seen) == 1):
96 self.new_version = True
97 self.setText(_("New version available") + ": " + self.latest_version)
100 def compare_versions(self, version1, version2):
102 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
103 return cmp(normalize(version1), normalize(version2))
105 def ignore_this_version(self):
107 self.config.set_key("last_seen_version", self.latest_version, True)
108 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
111 def ignore_all_version(self):
113 self.config.set_key("last_seen_version", "9.9.9", True)
114 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
117 def open_website(self):
118 webbrowser.open("http://electrum.org/download.html")
121 def mouseReleaseEvent(self, event):
122 dialog = QDialog(self)
123 dialog.setWindowTitle(_('Electrum update'))
126 main_layout = QGridLayout()
127 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
129 ignore_version = QPushButton(_("Ignore this version"))
130 ignore_version.clicked.connect(self.ignore_this_version)
132 ignore_all_versions = QPushButton(_("Ignore all versions"))
133 ignore_all_versions.clicked.connect(self.ignore_all_version)
135 open_website = QPushButton(_("Goto download page"))
136 open_website.clicked.connect(self.open_website)
138 main_layout.addWidget(ignore_version, 1, 0)
139 main_layout.addWidget(ignore_all_versions, 1, 1)
140 main_layout.addWidget(open_website, 1, 2)
142 dialog.setLayout(main_layout)
146 if not dialog.exec_(): return
150 class Timer(QtCore.QThread):
153 self.emit(QtCore.SIGNAL('timersignal'))
156 class HelpButton(QPushButton):
157 def __init__(self, text):
158 QPushButton.__init__(self, '?')
159 self.setFocusPolicy(Qt.NoFocus)
160 self.setFixedWidth(20)
161 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
164 class EnterButton(QPushButton):
165 def __init__(self, text, func):
166 QPushButton.__init__(self, text)
168 self.clicked.connect(func)
170 def keyPressEvent(self, e):
171 if e.key() == QtCore.Qt.Key_Return:
174 class MyTreeWidget(QTreeWidget):
175 def __init__(self, parent):
176 QTreeWidget.__init__(self, parent)
179 for i in range(0,self.viewport().height()/5):
180 if self.itemAt(QPoint(0,i*5)) == item:
184 for j in range(0,30):
185 if self.itemAt(QPoint(0,i*5 + j)) != item:
187 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
189 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
194 class StatusBarButton(QPushButton):
195 def __init__(self, icon, tooltip, func):
196 QPushButton.__init__(self, icon, '')
197 self.setToolTip(tooltip)
199 self.setMaximumWidth(25)
200 self.clicked.connect(func)
203 def keyPressEvent(self, e):
204 if e.key() == QtCore.Qt.Key_Return:
211 def waiting_dialog(f):
217 w.setWindowTitle('Electrum')
227 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
236 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
238 class ElectrumWindow(QMainWindow):
240 def __init__(self, wallet, config):
241 QMainWindow.__init__(self)
245 self.current_account = self.config.get("current_account", None)
248 self.create_status_bar()
250 self.need_update = threading.Event()
251 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
252 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
253 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
254 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
256 self.expert_mode = config.get('classic_expert_mode', False)
257 self.decimal_point = config.get('decimal_point', 8)
259 set_language(config.get('language'))
261 self.funds_error = False
262 self.completions = QStringListModel()
264 self.tabs = tabs = QTabWidget(self)
265 self.column_widths = self.config.get("column_widths", default_column_widths )
266 tabs.addTab(self.create_history_tab(), _('History') )
267 tabs.addTab(self.create_send_tab(), _('Send') )
268 tabs.addTab(self.create_receive_tab(), _('Receive') )
269 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
270 tabs.addTab(self.create_console_tab(), _('Console') )
271 tabs.setMinimumSize(600, 400)
272 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
273 self.setCentralWidget(tabs)
275 g = self.config.get("winpos-qt",[100, 100, 840, 400])
276 self.setGeometry(g[0], g[1], g[2], g[3])
277 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
278 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
279 self.setWindowTitle( title )
283 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
284 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
285 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
286 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
287 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
289 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
290 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
291 self.history_list.setFocus(True)
293 self.exchanger = exchange_rate.Exchanger(self)
294 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
296 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
297 if platform.system() == 'Windows':
298 n = 3 if self.wallet.seed else 2
299 tabs.setCurrentIndex (n)
300 tabs.setCurrentIndex (0)
303 if self.wallet.fee < 50000:
304 self.wallet.set_fee(50000)
305 self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
307 # set initial message
308 self.console.showMessage(self.wallet.interface.banner)
310 # plugins that need to change the GUI do it here
311 self.run_hook('init_gui')
314 def select_wallet_file(self):
315 file_name = self.getOpenFileName("Select your wallet file", "*.dat")
319 self.load_wallet(file_name)
321 # TODO: I rather import this from the lite gui, is that possible?
322 def backup_wallet(self):
324 folderName = QFileDialog.getExistingDirectory(QWidget(), _('Select folder to save a copy of your wallet to'), os.path.expanduser('~/'))
326 # TODO: Can we get the current wallet file instead of bruteforcing the default one?
327 sourceFile = util.user_dir() + '/electrum.dat'
328 shutil.copy2(sourceFile, str(folderName))
329 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(folderName))
330 except (IOError, os.error), reason:
331 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
333 def init_menubar(self):
336 electrum_menu = menubar.addMenu(_("&File"))
337 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
338 open_wallet_action.triggered.connect(self.select_wallet_file)
340 preferences_name = _("Preferences")
341 if sys.platform == 'darwin':
342 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
344 preferences_menu = electrum_menu.addAction(preferences_name)
345 preferences_menu.triggered.connect(self.settings_dialog)
346 electrum_menu.addSeparator()
348 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
350 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
351 raw_transaction_file.triggered.connect(self.do_process_from_file)
353 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
354 raw_transaction_text.triggered.connect(self.do_process_from_text)
356 electrum_menu.addSeparator()
357 quit_item = electrum_menu.addAction(_("&Close"))
358 quit_item.triggered.connect(self.close)
360 wallet_menu = menubar.addMenu(_("&Wallet"))
361 wallet_backup = wallet_menu.addAction(_("&Create backup"))
362 wallet_backup.triggered.connect(self.backup_wallet)
364 show_menu = wallet_menu.addMenu(_("Show"))
367 show_seed = show_menu.addAction(_("&Seed"))
368 show_seed.triggered.connect(self.show_seed_dialog)
370 show_mpk = show_menu.addAction(_("&Master Public Key"))
371 show_mpk.triggered.connect(self.show_master_public_key)
373 wallet_menu.addSeparator()
374 new_contact = wallet_menu.addAction(_("&New contact"))
375 new_contact.triggered.connect(self.new_contact_dialog)
377 import_menu = menubar.addMenu(_("&Import"))
378 in_labels = import_menu.addAction(_("&Labels"))
379 in_labels.triggered.connect(self.do_import_labels)
381 in_private_keys = import_menu.addAction(_("&Private keys"))
382 in_private_keys.triggered.connect(self.do_import_privkey)
384 export_menu = menubar.addMenu(_("&Export"))
385 ex_private_keys = export_menu.addAction(_("&Private keys"))
386 ex_private_keys.triggered.connect(self.do_export_privkeys)
388 ex_history = export_menu.addAction(_("&History"))
389 ex_history.triggered.connect(self.do_export_history)
391 ex_labels = export_menu.addAction(_("&Labels"))
392 ex_labels.triggered.connect(self.do_export_labels)
394 help_menu = menubar.addMenu(_("&Help"))
395 doc_open = help_menu.addAction(_("&Documentation"))
396 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
397 web_open = help_menu.addAction(_("&Official website"))
398 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
400 self.setMenuBar(menubar)
404 def load_wallet(self, filename):
407 config = electrum.SimpleConfig({'wallet_path': filename})
408 if not config.wallet_file_exists:
409 self.show_message("file not found "+ filename)
412 #self.wallet.verifier.stop()
413 interface = self.wallet.interface
414 verifier = self.wallet.verifier
415 self.wallet.synchronizer.stop()
418 self.wallet = electrum.Wallet(self.config)
419 self.wallet.interface = interface
420 self.wallet.verifier = verifier
422 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
430 def init_plugins(self):
431 import imp, pkgutil, __builtin__
432 if __builtin__.use_local_modules:
433 fp, pathname, description = imp.find_module('plugins')
434 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
435 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
436 imp.load_module('electrum_plugins', fp, pathname, description)
437 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
439 import electrum_plugins
440 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
441 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
446 self.plugins.append( p.Plugin(self) )
448 print_msg("Error:cannot initialize plugin",p)
449 traceback.print_exc(file=sys.stdout)
452 def run_hook(self, name, *args):
453 for p in self.plugins:
454 if not p.is_enabled():
463 print_error("Plugin error")
464 traceback.print_exc(file=sys.stdout)
469 def set_label(self, name, text = None):
471 old_text = self.wallet.labels.get(name)
474 self.wallet.labels[name] = text
478 self.wallet.labels.pop(name)
480 self.run_hook('set_label', name, text, changed)
484 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
485 def getOpenFileName(self, title, filter = None):
486 directory = self.config.get('io_dir', os.path.expanduser('~'))
487 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
488 if fileName and directory != os.path.dirname(fileName):
489 self.config.set_key('io_dir', os.path.dirname(fileName), True)
492 def getSaveFileName(self, title, filename, filter = None):
493 directory = self.config.get('io_dir', os.path.expanduser('~'))
494 path = os.path.join( directory, filename )
495 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
496 if fileName and directory != os.path.dirname(fileName):
497 self.config.set_key('io_dir', os.path.dirname(fileName), True)
503 QMainWindow.close(self)
504 self.run_hook('close_main_window')
506 def connect_slots(self, sender):
507 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
508 self.previous_payto_e=''
510 def timer_actions(self):
511 if self.need_update.is_set():
513 self.need_update.clear()
514 self.run_hook('timer_actions')
516 def format_amount(self, x, is_diff=False):
517 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
519 def read_amount(self, x):
520 if x in['.', '']: return None
521 p = pow(10, self.decimal_point)
522 return int( p * Decimal(x) )
525 assert self.decimal_point in [5,8]
526 return "BTC" if self.decimal_point == 8 else "mBTC"
528 def update_status(self):
529 if self.wallet.interface and self.wallet.interface.is_connected:
530 if not self.wallet.up_to_date:
531 text = _("Synchronizing...")
532 icon = QIcon(":icons/status_waiting.png")
534 c, u = self.wallet.get_account_balance(self.current_account)
535 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
536 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
537 text += self.create_quote_text(Decimal(c+u)/100000000)
538 icon = QIcon(":icons/status_connected.png")
540 text = _("Not connected")
541 icon = QIcon(":icons/status_disconnected.png")
543 self.balance_label.setText(text)
544 self.status_button.setIcon( icon )
546 def update_wallet(self):
548 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
549 self.update_history_tab()
550 self.update_receive_tab()
551 self.update_contacts_tab()
552 self.update_completions()
555 def create_quote_text(self, btc_balance):
556 quote_currency = self.config.get("currency", "None")
557 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
558 if quote_balance is None:
561 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
564 def create_history_tab(self):
565 self.history_list = l = MyTreeWidget(self)
567 for i,width in enumerate(self.column_widths['history']):
568 l.setColumnWidth(i, width)
569 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
570 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
571 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
573 l.setContextMenuPolicy(Qt.CustomContextMenu)
574 l.customContextMenuRequested.connect(self.create_history_menu)
578 def create_history_menu(self, position):
579 self.history_list.selectedIndexes()
580 item = self.history_list.currentItem()
582 tx_hash = str(item.data(0, Qt.UserRole).toString())
583 if not tx_hash: return
585 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
586 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
587 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
588 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
591 def show_tx_details(self, tx):
592 dialog = QDialog(self)
594 dialog.setWindowTitle(_("Transaction Details"))
596 dialog.setLayout(vbox)
597 dialog.setMinimumSize(600,300)
600 if tx_hash in self.wallet.transactions.keys():
601 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
602 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
604 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
610 vbox.addWidget(QLabel("Transaction ID:"))
611 e = QLineEdit(tx_hash)
615 vbox.addWidget(QLabel("Date: %s"%time_str))
616 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
619 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
620 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
622 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
623 vbox.addWidget(QLabel("Transaction fee: unknown"))
625 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
627 vbox.addWidget( self.generate_transaction_information_widget(tx) )
629 ok_button = QPushButton(_("Close"))
630 ok_button.setDefault(True)
631 ok_button.clicked.connect(dialog.accept)
635 hbox.addWidget(ok_button)
639 def tx_label_clicked(self, item, column):
640 if column==2 and item.isSelected():
642 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
643 self.history_list.editItem( item, column )
644 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
647 def tx_label_changed(self, item, column):
651 tx_hash = str(item.data(0, Qt.UserRole).toString())
652 tx = self.wallet.transactions.get(tx_hash)
653 text = unicode( item.text(2) )
654 self.set_label(tx_hash, text)
656 item.setForeground(2, QBrush(QColor('black')))
658 text = self.wallet.get_default_label(tx_hash)
659 item.setText(2, text)
660 item.setForeground(2, QBrush(QColor('gray')))
664 def edit_label(self, is_recv):
665 l = self.receive_list if is_recv else self.contacts_list
666 item = l.currentItem()
667 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
668 l.editItem( item, 1 )
669 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
673 def address_label_clicked(self, item, column, l, column_addr, column_label):
674 if column == column_label and item.isSelected():
675 is_editable = item.data(0, 32).toBool()
678 addr = unicode( item.text(column_addr) )
679 label = unicode( item.text(column_label) )
680 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
681 l.editItem( item, column )
682 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
685 def address_label_changed(self, item, column, l, column_addr, column_label):
686 if column == column_label:
687 addr = unicode( item.text(column_addr) )
688 text = unicode( item.text(column_label) )
689 is_editable = item.data(0, 32).toBool()
693 changed = self.set_label(addr, text)
695 self.update_history_tab()
696 self.update_completions()
698 self.current_item_changed(item)
700 self.run_hook('item_changed', item, column)
703 def current_item_changed(self, a):
704 self.run_hook('current_item_changed', a)
708 def update_history_tab(self):
710 self.history_list.clear()
711 for item in self.wallet.get_tx_history(self.current_account):
712 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
715 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
720 time_str = 'unverified'
721 icon = QIcon(":icons/unconfirmed.png")
724 icon = QIcon(":icons/unconfirmed.png")
726 icon = QIcon(":icons/clock%d.png"%conf)
728 icon = QIcon(":icons/confirmed.png")
730 if value is not None:
731 v_str = self.format_amount(value, True)
735 balance_str = self.format_amount(balance)
738 label, is_default_label = self.wallet.get_label(tx_hash)
740 label = _('Pruned transaction outputs')
741 is_default_label = False
743 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
744 item.setFont(2, QFont(MONOSPACE_FONT))
745 item.setFont(3, QFont(MONOSPACE_FONT))
746 item.setFont(4, QFont(MONOSPACE_FONT))
748 item.setForeground(3, QBrush(QColor("#BC1E1E")))
750 item.setData(0, Qt.UserRole, tx_hash)
751 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
753 item.setForeground(2, QBrush(QColor('grey')))
755 item.setIcon(0, icon)
756 self.history_list.insertTopLevelItem(0,item)
759 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
762 def create_send_tab(self):
767 grid.setColumnMinimumWidth(3,300)
768 grid.setColumnStretch(5,1)
771 self.payto_e = QLineEdit()
772 grid.addWidget(QLabel(_('Pay to')), 1, 0)
773 grid.addWidget(self.payto_e, 1, 1, 1, 3)
775 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)
777 completer = QCompleter()
778 completer.setCaseSensitivity(False)
779 self.payto_e.setCompleter(completer)
780 completer.setModel(self.completions)
782 self.message_e = QLineEdit()
783 grid.addWidget(QLabel(_('Description')), 2, 0)
784 grid.addWidget(self.message_e, 2, 1, 1, 3)
785 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)
787 self.amount_e = AmountEdit(self.base_unit)
788 grid.addWidget(QLabel(_('Amount')), 3, 0)
789 grid.addWidget(self.amount_e, 3, 1, 1, 2)
790 grid.addWidget(HelpButton(
791 _('Amount to be sent.') + '\n\n' \
792 + _('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.') \
793 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
795 self.fee_e = AmountEdit(self.base_unit)
796 grid.addWidget(QLabel(_('Fee')), 4, 0)
797 grid.addWidget(self.fee_e, 4, 1, 1, 2)
798 grid.addWidget(HelpButton(
799 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
800 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
801 + _('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)
804 b = EnterButton(_("Send"), self.do_send)
806 b = EnterButton(_("Create unsigned transaction"), self.do_send)
807 grid.addWidget(b, 6, 1)
809 b = EnterButton(_("Clear"),self.do_clear)
810 grid.addWidget(b, 6, 2)
812 self.payto_sig = QLabel('')
813 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
815 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
816 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
825 def entry_changed( is_fee ):
826 self.funds_error = False
828 if self.amount_e.is_shortcut:
829 self.amount_e.is_shortcut = False
830 c, u = self.wallet.get_account_balance(self.current_account)
831 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
832 fee = self.wallet.estimated_fee(inputs)
834 self.amount_e.setText( self.format_amount(amount) )
835 self.fee_e.setText( self.format_amount( fee ) )
838 amount = self.read_amount(str(self.amount_e.text()))
839 fee = self.read_amount(str(self.fee_e.text()))
841 if not is_fee: fee = None
844 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
846 self.fee_e.setText( self.format_amount( fee ) )
849 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
853 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
854 self.funds_error = True
855 text = _( "Not enough funds" )
856 c, u = self.wallet.get_frozen_balance()
857 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
859 self.statusBar().showMessage(text)
860 self.amount_e.setPalette(palette)
861 self.fee_e.setPalette(palette)
863 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
864 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
866 self.run_hook('create_send_tab', grid)
870 def update_completions(self):
872 for addr,label in self.wallet.labels.items():
873 if addr in self.wallet.addressbook:
874 l.append( label + ' <' + addr + '>')
876 self.run_hook('update_completions', l)
877 self.completions.setStringList(l)
881 return lambda s, *args: s.do_protect(func, args)
885 def do_send(self, password):
887 label = unicode( self.message_e.text() )
888 r = unicode( self.payto_e.text() )
891 # label or alias, with address in brackets
892 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
893 to_address = m.group(2) if m else r
895 if not is_valid(to_address):
896 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
900 amount = self.read_amount(unicode( self.amount_e.text()))
902 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
905 fee = self.read_amount(unicode( self.fee_e.text()))
907 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
911 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
912 except BaseException, e:
913 self.show_message(str(e))
916 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
917 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
920 self.run_hook('send_tx', tx)
923 self.set_label(tx.hash(), label)
926 h = self.wallet.send_tx(tx)
927 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
928 status, msg = self.wallet.receive_tx( h )
930 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
932 self.update_contacts_tab()
934 QMessageBox.warning(self, _('Error'), msg, _('OK'))
936 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
938 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
939 with open(fileName,'w') as f:
940 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
941 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
943 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
948 def set_url(self, url):
949 address, amount, label, message, signature, identity, url = util.parse_url(url)
950 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
952 if label and self.wallet.labels.get(address) != label:
953 if self.question('Give label "%s" to address %s ?'%(label,address)):
954 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
955 self.wallet.addressbook.append(address)
956 self.set_label(address, label)
958 self.run_hook('set_url', url, self.show_message, self.question)
960 self.tabs.setCurrentIndex(1)
961 label = self.wallet.labels.get(address)
962 m_addr = label + ' <'+ address +'>' if label else address
963 self.payto_e.setText(m_addr)
965 self.message_e.setText(message)
966 self.amount_e.setText(amount)
968 self.set_frozen(self.payto_e,True)
969 self.set_frozen(self.amount_e,True)
970 self.set_frozen(self.message_e,True)
971 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
973 self.payto_sig.setVisible(False)
976 self.payto_sig.setVisible(False)
977 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
979 self.set_frozen(e,False)
982 def set_frozen(self,entry,frozen):
984 entry.setReadOnly(True)
985 entry.setFrame(False)
987 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
988 entry.setPalette(palette)
990 entry.setReadOnly(False)
993 palette.setColor(entry.backgroundRole(), QColor('white'))
994 entry.setPalette(palette)
997 def toggle_freeze(self,addr):
999 if addr in self.wallet.frozen_addresses:
1000 self.wallet.unfreeze(addr)
1002 self.wallet.freeze(addr)
1003 self.update_receive_tab()
1005 def toggle_priority(self,addr):
1007 if addr in self.wallet.prioritized_addresses:
1008 self.wallet.unprioritize(addr)
1010 self.wallet.prioritize(addr)
1011 self.update_receive_tab()
1014 def create_list_tab(self, headers):
1015 "generic tab creation method"
1016 l = MyTreeWidget(self)
1017 l.setColumnCount( len(headers) )
1018 l.setHeaderLabels( headers )
1021 vbox = QVBoxLayout()
1028 vbox.addWidget(buttons)
1030 hbox = QHBoxLayout()
1033 buttons.setLayout(hbox)
1038 def create_receive_tab(self):
1039 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1040 l.setContextMenuPolicy(Qt.CustomContextMenu)
1041 l.customContextMenuRequested.connect(self.create_receive_menu)
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.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1045 self.receive_list = l
1046 self.receive_buttons_hbox = hbox
1051 def receive_tab_set_mode(self, i):
1052 self.save_column_widths()
1053 self.expert_mode = (i == 1)
1054 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1055 self.update_receive_tab()
1058 def save_column_widths(self):
1059 if not self.expert_mode:
1060 widths = [ self.receive_list.columnWidth(0) ]
1063 for i in range(self.receive_list.columnCount() -1):
1064 widths.append(self.receive_list.columnWidth(i))
1065 self.column_widths["receive"][self.expert_mode] = widths
1067 self.column_widths["history"] = []
1068 for i in range(self.history_list.columnCount() - 1):
1069 self.column_widths["history"].append(self.history_list.columnWidth(i))
1071 self.column_widths["contacts"] = []
1072 for i in range(self.contacts_list.columnCount() - 1):
1073 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1075 self.config.set_key("column_widths", self.column_widths, True)
1078 def create_contacts_tab(self):
1079 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1080 l.setContextMenuPolicy(Qt.CustomContextMenu)
1081 l.customContextMenuRequested.connect(self.create_contact_menu)
1082 for i,width in enumerate(self.column_widths['contacts']):
1083 l.setColumnWidth(i, width)
1085 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1086 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1087 self.contacts_list = l
1088 self.contacts_buttons_hbox = hbox
1089 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1094 def delete_imported_key(self, addr):
1095 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1096 self.wallet.delete_imported_key(addr)
1097 self.update_receive_tab()
1098 self.update_history_tab()
1101 def create_receive_menu(self, position):
1102 # fixme: this function apparently has a side effect.
1103 # if it is not called the menu pops up several times
1104 #self.receive_list.selectedIndexes()
1106 item = self.receive_list.itemAt(position)
1108 addr = unicode(item.text(0))
1109 if not is_valid(addr):
1110 item.setExpanded(not item.isExpanded())
1113 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1114 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1115 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1116 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1117 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1118 if addr in self.wallet.imported_keys:
1119 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1121 if self.expert_mode:
1122 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1123 menu.addAction(t, lambda: self.toggle_freeze(addr))
1124 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1125 menu.addAction(t, lambda: self.toggle_priority(addr))
1127 self.run_hook('receive_menu', menu)
1128 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1131 def payto(self, addr):
1133 label = self.wallet.labels.get(addr)
1134 m_addr = label + ' <' + addr + '>' if label else addr
1135 self.tabs.setCurrentIndex(1)
1136 self.payto_e.setText(m_addr)
1137 self.amount_e.setFocus()
1140 def delete_contact(self, x):
1141 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1142 self.wallet.delete_contact(x)
1143 self.set_label(x, None)
1144 self.update_history_tab()
1145 self.update_contacts_tab()
1146 self.update_completions()
1149 def create_contact_menu(self, position):
1150 item = self.contacts_list.itemAt(position)
1152 addr = unicode(item.text(0))
1153 label = unicode(item.text(1))
1154 is_editable = item.data(0,32).toBool()
1155 payto_addr = item.data(0,33).toString()
1157 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1158 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1159 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1161 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1162 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1164 self.run_hook('create_contact_menu', menu, item)
1165 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1168 def update_receive_item(self, item):
1169 item.setFont(0, QFont(MONOSPACE_FONT))
1170 address = str(item.data(0,0).toString())
1171 label = self.wallet.labels.get(address,'')
1172 item.setData(1,0,label)
1173 item.setData(0,32, True) # is editable
1175 self.run_hook('update_receive_item', address, item)
1177 c, u = self.wallet.get_addr_balance(address)
1178 balance = self.format_amount(c + u)
1179 item.setData(2,0,balance)
1181 if self.expert_mode:
1182 if address in self.wallet.frozen_addresses:
1183 item.setBackgroundColor(0, QColor('lightblue'))
1184 elif address in self.wallet.prioritized_addresses:
1185 item.setBackgroundColor(0, QColor('lightgreen'))
1188 def update_receive_tab(self):
1189 l = self.receive_list
1192 l.setColumnHidden(2, not self.expert_mode)
1193 l.setColumnHidden(3, not self.expert_mode)
1194 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1195 l.setColumnWidth(i, width)
1197 if self.current_account is None:
1198 account_items = self.wallet.accounts.items()
1199 elif self.current_account != -1:
1200 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1204 for k, account in account_items:
1205 name = account.get('name',str(k))
1206 c,u = self.wallet.get_account_balance(k)
1207 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1208 l.addTopLevelItem(account_item)
1209 account_item.setExpanded(True)
1211 for is_change in ([0,1] if self.expert_mode else [0]):
1212 if self.expert_mode:
1213 name = "Receiving" if not is_change else "Change"
1214 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1215 account_item.addChild(seq_item)
1216 if not is_change: seq_item.setExpanded(True)
1218 seq_item = account_item
1222 for address in account[is_change]:
1223 h = self.wallet.history.get(address,[])
1227 if gap > self.wallet.gap_limit:
1232 num_tx = '*' if h == ['*'] else "%d"%len(h)
1233 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1234 self.update_receive_item(item)
1236 item.setBackgroundColor(1, QColor('red'))
1237 seq_item.addChild(item)
1240 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1241 c,u = self.wallet.get_imported_balance()
1242 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1243 l.addTopLevelItem(account_item)
1244 account_item.setExpanded(True)
1245 for address in self.wallet.imported_keys.keys():
1246 item = QTreeWidgetItem( [ address, '', '', ''] )
1247 self.update_receive_item(item)
1248 account_item.addChild(item)
1251 # we use column 1 because column 0 may be hidden
1252 l.setCurrentItem(l.topLevelItem(0),1)
1255 def update_contacts_tab(self):
1257 l = self.contacts_list
1260 for address in self.wallet.addressbook:
1261 label = self.wallet.labels.get(address,'')
1262 n = self.wallet.get_num_tx(address)
1263 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1264 item.setFont(0, QFont(MONOSPACE_FONT))
1265 # 32 = label can be edited (bool)
1266 item.setData(0,32, True)
1268 item.setData(0,33, address)
1269 l.addTopLevelItem(item)
1271 self.run_hook('update_contacts_tab', l)
1272 l.setCurrentItem(l.topLevelItem(0))
1276 def create_console_tab(self):
1277 from qt_console import Console
1278 self.console = console = Console()
1279 self.console.history = self.config.get("console-history",[])
1280 self.console.history_index = len(self.console.history)
1282 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1283 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1285 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1287 def mkfunc(f, method):
1288 return lambda *args: apply( f, (method, args, self.password_dialog ))
1290 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1291 methods[m] = mkfunc(c._run, m)
1293 console.updateNamespace(methods)
1296 def change_account(self,s):
1297 if s == _("All accounts"):
1298 self.current_account = None
1300 accounts = self.wallet.get_accounts()
1301 for k, v in accounts.items():
1303 self.current_account = k
1304 self.update_history_tab()
1305 self.update_status()
1306 self.update_receive_tab()
1308 def create_status_bar(self):
1311 sb.setFixedHeight(35)
1312 qtVersion = qVersion()
1314 self.balance_label = QLabel("")
1315 sb.addWidget(self.balance_label)
1317 update_notification = UpdateLabel(self.config)
1318 if(update_notification.new_version):
1319 sb.addPermanentWidget(update_notification)
1321 accounts = self.wallet.get_accounts()
1322 if len(accounts) > 1:
1323 from_combo = QComboBox()
1324 from_combo.addItems([_("All accounts")] + accounts.values())
1325 from_combo.setCurrentIndex(0)
1326 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1327 sb.addPermanentWidget(from_combo)
1329 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1330 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1331 if self.wallet.seed:
1332 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1333 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1334 sb.addPermanentWidget( self.password_button )
1335 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1336 if self.wallet.seed:
1337 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1338 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1339 sb.addPermanentWidget( self.status_button )
1341 self.run_hook('create_status_bar', (sb,))
1343 self.setStatusBar(sb)
1347 self.config.set_key('gui', 'lite', True)
1350 self.lite.mini.show()
1352 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1353 self.lite.main(None)
1355 def new_contact_dialog(self):
1356 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1357 address = unicode(text)
1359 if is_valid(address):
1360 self.wallet.add_contact(address)
1361 self.update_contacts_tab()
1362 self.update_history_tab()
1363 self.update_completions()
1365 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1367 def show_master_public_key(self):
1368 dialog = QDialog(self)
1370 dialog.setWindowTitle(_("Master Public Key"))
1372 main_text = QTextEdit()
1373 main_text.setText(self.wallet.get_master_public_key())
1374 main_text.setReadOnly(True)
1375 main_text.setMaximumHeight(170)
1376 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1378 ok_button = QPushButton(_("OK"))
1379 ok_button.setDefault(True)
1380 ok_button.clicked.connect(dialog.accept)
1382 main_layout = QGridLayout()
1383 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1385 main_layout.addWidget(main_text, 1, 0)
1386 main_layout.addWidget(qrw, 1, 1 )
1388 vbox = QVBoxLayout()
1389 vbox.addLayout(main_layout)
1390 hbox = QHBoxLayout()
1392 hbox.addWidget(ok_button)
1393 vbox.addLayout(hbox)
1395 dialog.setLayout(vbox)
1400 def show_seed_dialog(self, password):
1401 if not self.wallet.seed:
1402 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1405 seed = self.wallet.decode_seed(password)
1407 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1409 self.show_seed(seed, self.wallet.imported_keys, self)
1413 def show_seed(self, seed, imported_keys, parent=None):
1414 dialog = QDialog(parent)
1416 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1418 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1420 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1422 seed_text = QTextEdit(brainwallet)
1423 seed_text.setReadOnly(True)
1424 seed_text.setMaximumHeight(130)
1426 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1427 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1428 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1429 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1431 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1432 label2 = QLabel(msg2)
1433 label2.setWordWrap(True)
1436 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1437 logo.setMaximumWidth(60)
1439 qrw = QRCodeWidget(seed)
1441 ok_button = QPushButton(_("OK"))
1442 ok_button.setDefault(True)
1443 ok_button.clicked.connect(dialog.accept)
1445 grid = QGridLayout()
1446 #main_layout.addWidget(logo, 0, 0)
1448 grid.addWidget(logo, 0, 0)
1449 grid.addWidget(label1, 0, 1)
1451 grid.addWidget(seed_text, 1, 0, 1, 2)
1453 grid.addWidget(qrw, 0, 2, 2, 1)
1455 vbox = QVBoxLayout()
1456 vbox.addLayout(grid)
1457 vbox.addWidget(label2)
1459 hbox = QHBoxLayout()
1461 hbox.addWidget(ok_button)
1462 vbox.addLayout(hbox)
1464 dialog.setLayout(vbox)
1467 def show_qrcode(self, data, title = "QR code"):
1471 d.setWindowTitle(title)
1472 d.setMinimumSize(270, 300)
1473 vbox = QVBoxLayout()
1474 qrw = QRCodeWidget(data)
1475 vbox.addWidget(qrw, 1)
1476 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1477 hbox = QHBoxLayout()
1481 filename = "qrcode.bmp"
1482 bmp.save_qrcode(qrw.qr, filename)
1483 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1485 b = QPushButton(_("Save"))
1487 b.clicked.connect(print_qr)
1489 b = QPushButton(_("Close"))
1491 b.clicked.connect(d.accept)
1494 vbox.addLayout(hbox)
1499 def do_protect(self, func, args):
1500 if self.wallet.use_encryption:
1501 password = self.password_dialog()
1507 if args != (False,):
1508 args = (self,) + args + (password,)
1510 args = (self,password)
1515 def show_private_key(self, address, password):
1516 if not address: return
1518 pk = self.wallet.get_private_key(address, password)
1519 except BaseException, e:
1520 self.show_message(str(e))
1522 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1526 def do_sign(self, address, message, signature, password):
1528 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1529 signature.setText(sig)
1530 except BaseException, e:
1531 self.show_message(str(e))
1533 def sign_message(self, address):
1534 if not address: return
1537 d.setWindowTitle(_('Sign Message'))
1538 d.setMinimumSize(410, 290)
1540 tab_widget = QTabWidget()
1542 layout = QGridLayout(tab)
1544 sign_address = QLineEdit()
1546 sign_address.setText(address)
1547 layout.addWidget(QLabel(_('Address')), 1, 0)
1548 layout.addWidget(sign_address, 1, 1)
1550 sign_message = QTextEdit()
1551 layout.addWidget(QLabel(_('Message')), 2, 0)
1552 layout.addWidget(sign_message, 2, 1)
1553 layout.setRowStretch(2,3)
1555 sign_signature = QTextEdit()
1556 layout.addWidget(QLabel(_('Signature')), 3, 0)
1557 layout.addWidget(sign_signature, 3, 1)
1558 layout.setRowStretch(3,1)
1561 hbox = QHBoxLayout()
1562 b = QPushButton(_("Sign"))
1564 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1565 b = QPushButton(_("Close"))
1566 b.clicked.connect(d.accept)
1568 layout.addLayout(hbox, 4, 1)
1569 tab_widget.addTab(tab, _("Sign"))
1573 layout = QGridLayout(tab)
1575 verify_address = QLineEdit()
1576 layout.addWidget(QLabel(_('Address')), 1, 0)
1577 layout.addWidget(verify_address, 1, 1)
1579 verify_message = QTextEdit()
1580 layout.addWidget(QLabel(_('Message')), 2, 0)
1581 layout.addWidget(verify_message, 2, 1)
1582 layout.setRowStretch(2,3)
1584 verify_signature = QTextEdit()
1585 layout.addWidget(QLabel(_('Signature')), 3, 0)
1586 layout.addWidget(verify_signature, 3, 1)
1587 layout.setRowStretch(3,1)
1590 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1591 self.show_message(_("Signature verified"))
1593 self.show_message(_("Error: wrong signature"))
1595 hbox = QHBoxLayout()
1596 b = QPushButton(_("Verify"))
1597 b.clicked.connect(do_verify)
1599 b = QPushButton(_("Close"))
1600 b.clicked.connect(d.accept)
1602 layout.addLayout(hbox, 4, 1)
1603 tab_widget.addTab(tab, _("Verify"))
1605 vbox = QVBoxLayout()
1606 vbox.addWidget(tab_widget)
1613 def question(self, msg):
1614 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1616 def show_message(self, msg):
1617 QMessageBox.information(self, _('Message'), msg, _('OK'))
1619 def password_dialog(self ):
1626 vbox = QVBoxLayout()
1627 msg = _('Please enter your password')
1628 vbox.addWidget(QLabel(msg))
1630 grid = QGridLayout()
1632 grid.addWidget(QLabel(_('Password')), 1, 0)
1633 grid.addWidget(pw, 1, 1)
1634 vbox.addLayout(grid)
1636 vbox.addLayout(ok_cancel_buttons(d))
1639 self.run_hook('password_dialog', pw, grid, 1)
1640 if not d.exec_(): return
1641 return unicode(pw.text())
1648 def change_password_dialog( wallet, parent=None ):
1651 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1659 new_pw = QLineEdit()
1660 new_pw.setEchoMode(2)
1661 conf_pw = QLineEdit()
1662 conf_pw.setEchoMode(2)
1664 vbox = QVBoxLayout()
1666 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1667 +_('To disable wallet encryption, enter an empty new password.')) \
1668 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1670 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1671 +_("Leave these fields empty if you want to disable encryption.")
1672 vbox.addWidget(QLabel(msg))
1674 grid = QGridLayout()
1677 if wallet.use_encryption:
1678 grid.addWidget(QLabel(_('Password')), 1, 0)
1679 grid.addWidget(pw, 1, 1)
1681 grid.addWidget(QLabel(_('New Password')), 2, 0)
1682 grid.addWidget(new_pw, 2, 1)
1684 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1685 grid.addWidget(conf_pw, 3, 1)
1686 vbox.addLayout(grid)
1688 vbox.addLayout(ok_cancel_buttons(d))
1691 if not d.exec_(): return
1693 password = unicode(pw.text()) if wallet.use_encryption else None
1694 new_password = unicode(new_pw.text())
1695 new_password2 = unicode(conf_pw.text())
1698 seed = wallet.decode_seed(password)
1700 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1703 if new_password != new_password2:
1704 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1705 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1708 wallet.update_password(seed, password, new_password)
1710 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1713 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1716 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1717 parent.password_button.setIcon( icon )
1721 def generate_transaction_information_widget(self, tx):
1722 tabs = QTabWidget(self)
1725 grid_ui = QGridLayout(tab1)
1726 grid_ui.setColumnStretch(0,1)
1727 tabs.addTab(tab1, _('Outputs') )
1729 tree_widget = MyTreeWidget(self)
1730 tree_widget.setColumnCount(2)
1731 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1732 tree_widget.setColumnWidth(0, 300)
1733 tree_widget.setColumnWidth(1, 50)
1735 for address, value in tx.outputs:
1736 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1737 tree_widget.addTopLevelItem(item)
1739 tree_widget.setMaximumHeight(100)
1741 grid_ui.addWidget(tree_widget)
1744 grid_ui = QGridLayout(tab2)
1745 grid_ui.setColumnStretch(0,1)
1746 tabs.addTab(tab2, _('Inputs') )
1748 tree_widget = MyTreeWidget(self)
1749 tree_widget.setColumnCount(2)
1750 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1752 for input_line in tx.inputs:
1753 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1754 tree_widget.addTopLevelItem(item)
1756 tree_widget.setMaximumHeight(100)
1758 grid_ui.addWidget(tree_widget)
1762 def tx_dict_from_text(self, txt):
1764 tx_dict = json.loads(str(txt))
1765 assert "hex" in tx_dict.keys()
1766 assert "complete" in tx_dict.keys()
1767 if not tx_dict["complete"]:
1768 assert "input_info" in tx_dict.keys()
1770 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1775 def read_tx_from_file(self):
1776 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1780 with open(fileName, "r") as f:
1781 file_content = f.read()
1782 except (ValueError, IOError, os.error), reason:
1783 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1785 return self.tx_dict_from_text(file_content)
1789 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1791 self.wallet.signrawtransaction(tx, input_info, [], password)
1793 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1795 with open(fileName, "w+") as f:
1796 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1797 self.show_message(_("Transaction saved successfully"))
1800 except BaseException, e:
1801 self.show_message(str(e))
1804 def send_raw_transaction(self, raw_tx, dialog = ""):
1805 result, result_message = self.wallet.sendtx( raw_tx )
1807 self.show_message("Transaction successfully sent: %s" % (result_message))
1811 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1813 def do_process_from_text(self):
1814 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1817 tx_dict = self.tx_dict_from_text(text)
1819 self.create_process_transaction_window(tx_dict)
1821 def do_process_from_file(self):
1822 tx_dict = self.read_tx_from_file()
1824 self.create_process_transaction_window(tx_dict)
1826 def create_process_transaction_window(self, tx_dict):
1827 tx = Transaction(tx_dict["hex"])
1829 dialog = QDialog(self)
1830 dialog.setMinimumWidth(500)
1831 dialog.setWindowTitle(_('Process raw transaction'))
1837 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1838 l.addWidget(QLabel(_("Actions")), 4,0)
1840 if tx_dict["complete"] == False:
1841 l.addWidget(QLabel(_("Unsigned")), 3,1)
1842 if self.wallet.seed :
1843 b = QPushButton("Sign transaction")
1844 input_info = json.loads(tx_dict["input_info"])
1845 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1846 l.addWidget(b, 4, 1)
1848 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1850 l.addWidget(QLabel(_("Signed")), 3,1)
1851 b = QPushButton("Broadcast transaction")
1852 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1855 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1856 cancelButton = QPushButton(_("Cancel"))
1857 cancelButton.clicked.connect(lambda: dialog.done(0))
1858 l.addWidget(cancelButton, 4,2)
1864 def do_export_privkeys(self, password):
1865 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.")))
1868 select_export = _('Select file to export your private keys to')
1869 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1871 with open(fileName, "w+") as csvfile:
1872 transaction = csv.writer(csvfile)
1873 transaction.writerow(["address", "private_key"])
1876 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1877 transaction.writerow(["%34s"%addr,pk])
1879 self.show_message(_("Private keys exported."))
1881 except (IOError, os.error), reason:
1882 export_error_label = _("Electrum was unable to produce a private key-export.")
1883 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1885 except BaseException, e:
1886 self.show_message(str(e))
1890 def do_import_labels(self):
1891 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1892 if not labelsFile: return
1894 f = open(labelsFile, 'r')
1897 for key, value in json.loads(data).items():
1898 self.wallet.labels[key] = value
1900 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1901 except (IOError, os.error), reason:
1902 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1905 def do_export_labels(self):
1906 labels = self.wallet.labels
1908 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1910 with open(fileName, 'w+') as f:
1911 json.dump(labels, f)
1912 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1913 except (IOError, os.error), reason:
1914 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1917 def do_export_history(self):
1918 from gui_lite import csv_transaction
1919 csv_transaction(self.wallet)
1923 def do_import_privkey(self, password):
1924 if not self.wallet.imported_keys:
1925 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1926 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1927 + _('Are you sure you understand what you are doing?'), 3, 4)
1930 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1933 text = str(text).split()
1938 addr = self.wallet.import_key(key, password)
1939 except BaseException as e:
1945 addrlist.append(addr)
1947 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1949 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1950 self.update_receive_tab()
1951 self.update_history_tab()
1954 def settings_dialog(self):
1956 d.setWindowTitle(_('Electrum Settings'))
1958 vbox = QVBoxLayout()
1960 tabs = QTabWidget(self)
1961 self.settings_tab = tabs
1962 vbox.addWidget(tabs)
1965 grid_ui = QGridLayout(tab1)
1966 grid_ui.setColumnStretch(0,1)
1967 tabs.addTab(tab1, _('Display') )
1969 nz_label = QLabel(_('Display zeros'))
1970 grid_ui.addWidget(nz_label, 0, 0)
1971 nz_e = AmountEdit(None,True)
1972 nz_e.setText("%d"% self.wallet.num_zeros)
1973 grid_ui.addWidget(nz_e, 0, 1)
1974 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1975 grid_ui.addWidget(HelpButton(msg), 0, 2)
1976 if not self.config.is_modifiable('num_zeros'):
1977 for w in [nz_e, nz_label]: w.setEnabled(False)
1979 lang_label=QLabel(_('Language') + ':')
1980 grid_ui.addWidget(lang_label, 1, 0)
1981 lang_combo = QComboBox()
1982 from i18n import languages
1983 lang_combo.addItems(languages.values())
1985 index = languages.keys().index(self.config.get("language",''))
1988 lang_combo.setCurrentIndex(index)
1989 grid_ui.addWidget(lang_combo, 1, 1)
1990 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1991 if not self.config.is_modifiable('language'):
1992 for w in [lang_combo, lang_label]: w.setEnabled(False)
1994 currencies = self.exchanger.get_currencies()
1995 currencies.insert(0, "None")
1997 cur_label=QLabel(_('Currency') + ':')
1998 grid_ui.addWidget(cur_label , 2, 0)
1999 cur_combo = QComboBox()
2000 cur_combo.addItems(currencies)
2002 index = currencies.index(self.config.get('currency', "None"))
2005 cur_combo.setCurrentIndex(index)
2006 grid_ui.addWidget(cur_combo, 2, 1)
2007 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2009 expert_cb = QCheckBox(_('Expert mode'))
2010 expert_cb.setChecked(self.expert_mode)
2011 grid_ui.addWidget(expert_cb, 3, 0)
2012 hh = _('In expert mode, your client will:') + '\n' \
2013 + _(' - Show change addresses in the Receive tab') + '\n' \
2014 + _(' - Display the balance of each address') + '\n' \
2015 + _(' - Add freeze/prioritize actions to addresses.')
2016 grid_ui.addWidget(HelpButton(hh), 3, 2)
2017 grid_ui.setRowStretch(4,1)
2021 grid_wallet = QGridLayout(tab2)
2022 grid_wallet.setColumnStretch(0,1)
2023 tabs.addTab(tab2, _('Wallet') )
2025 fee_label = QLabel(_('Transaction fee'))
2026 grid_wallet.addWidget(fee_label, 0, 0)
2027 fee_e = AmountEdit(self.base_unit)
2028 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2029 grid_wallet.addWidget(fee_e, 0, 2)
2030 msg = _('Fee per kilobyte of transaction.') + ' ' \
2031 + _('Recommended value') + ': ' + self.format_amount(50000)
2032 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2033 if not self.config.is_modifiable('fee_per_kb'):
2034 for w in [fee_e, fee_label]: w.setEnabled(False)
2036 usechange_cb = QCheckBox(_('Use change addresses'))
2037 usechange_cb.setChecked(self.wallet.use_change)
2038 grid_wallet.addWidget(usechange_cb, 1, 0)
2039 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2040 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2042 gap_label = QLabel(_('Gap limit'))
2043 grid_wallet.addWidget(gap_label, 2, 0)
2044 gap_e = AmountEdit(None,True)
2045 gap_e.setText("%d"% self.wallet.gap_limit)
2046 grid_wallet.addWidget(gap_e, 2, 2)
2047 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2048 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2049 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2050 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2051 + _('Warning') + ': ' \
2052 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2053 + _('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'
2054 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2055 if not self.config.is_modifiable('gap_limit'):
2056 for w in [gap_e, gap_label]: w.setEnabled(False)
2058 units = ['BTC', 'mBTC']
2059 unit_label = QLabel(_('Base unit'))
2060 grid_wallet.addWidget(unit_label, 3, 0)
2061 unit_combo = QComboBox()
2062 unit_combo.addItems(units)
2063 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2064 grid_wallet.addWidget(unit_combo, 3, 2)
2065 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2066 + '\n1BTC=1000mBTC.\n' \
2067 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2068 grid_wallet.setRowStretch(4,1)
2073 grid_io = QGridLayout(tab3)
2074 grid_io.setColumnStretch(0,1)
2075 tabs.addTab(tab3, _('Import/Export') )
2077 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2078 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2079 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2080 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2082 grid_io.addWidget(QLabel(_('History')), 2, 0)
2083 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2084 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2086 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2088 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2089 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2090 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2092 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2093 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2094 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2095 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2096 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2099 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2100 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2101 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2102 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2104 grid_io.setRowStretch(6,1)
2109 tab5 = QScrollArea()
2110 tab5.setEnabled(True)
2111 tab5.setWidgetResizable(True)
2113 grid_plugins = QGridLayout()
2114 grid_plugins.setColumnStretch(0,1)
2117 w.setLayout(grid_plugins)
2119 tab5.setMaximumSize(tab3.size()) # optional
2121 w.setMinimumHeight(len(self.plugins)*35)
2123 tabs.addTab(tab5, _('Plugins') )
2124 def mk_toggle(cb, p):
2125 return lambda: cb.setChecked(p.toggle())
2126 for i, p in enumerate(self.plugins):
2128 name, description = p.get_info()
2129 cb = QCheckBox(name)
2130 cb.setDisabled(not p.is_available())
2131 cb.setChecked(p.is_enabled())
2132 cb.clicked.connect(mk_toggle(cb,p))
2133 grid_plugins.addWidget(cb, i, 0)
2134 if p.requires_settings():
2135 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2136 grid_plugins.addWidget(HelpButton(description), i, 2)
2138 print_msg("Error: cannot display plugin", p)
2139 traceback.print_exc(file=sys.stdout)
2140 grid_plugins.setRowStretch(i+1,1)
2142 self.run_hook('create_settings_tab', tabs)
2144 vbox.addLayout(ok_cancel_buttons(d))
2148 if not d.exec_(): return
2150 fee = unicode(fee_e.text())
2152 fee = self.read_amount(fee)
2154 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2157 self.wallet.set_fee(fee)
2159 nz = unicode(nz_e.text())
2164 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2167 if self.wallet.num_zeros != nz:
2168 self.wallet.num_zeros = nz
2169 self.config.set_key('num_zeros', nz, True)
2170 self.update_history_tab()
2171 self.update_receive_tab()
2173 usechange_result = usechange_cb.isChecked()
2174 if self.wallet.use_change != usechange_result:
2175 self.wallet.use_change = usechange_result
2176 self.config.set_key('use_change', self.wallet.use_change, True)
2178 unit_result = units[unit_combo.currentIndex()]
2179 if self.base_unit() != unit_result:
2180 self.decimal_point = 8 if unit_result == 'BTC' else 5
2181 self.config.set_key('decimal_point', self.decimal_point, True)
2182 self.update_history_tab()
2183 self.update_status()
2186 n = int(gap_e.text())
2188 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2191 if self.wallet.gap_limit != n:
2192 r = self.wallet.change_gap_limit(n)
2194 self.update_receive_tab()
2195 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2197 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2199 need_restart = False
2201 lang_request = languages.keys()[lang_combo.currentIndex()]
2202 if lang_request != self.config.get('language'):
2203 self.config.set_key("language", lang_request, True)
2206 cur_request = str(currencies[cur_combo.currentIndex()])
2207 if cur_request != self.config.get('currency', "None"):
2208 self.config.set_key('currency', cur_request, True)
2209 self.update_wallet()
2211 self.run_hook('close_settings_dialog')
2214 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2216 self.receive_tab_set_mode(expert_cb.isChecked())
2218 def run_network_dialog(self):
2219 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2221 def closeEvent(self, event):
2223 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2224 self.save_column_widths()
2225 self.config.set_key("console-history",self.console.history[-50:])
2234 def __init__(self, wallet, config, app=None):
2235 self.wallet = wallet
2236 self.config = config
2238 self.app = QApplication(sys.argv)
2241 def restore_or_create(self):
2242 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2243 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2244 if r==2: return None
2245 return 'restore' if r==1 else 'create'
2248 def verify_seed(self):
2249 r = self.seed_dialog(False)
2250 if r != self.wallet.seed:
2251 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2258 def seed_dialog(self, is_restore=True):
2262 vbox = QVBoxLayout()
2264 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2266 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2268 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2271 label.setWordWrap(True)
2272 vbox.addWidget(label)
2274 seed_e = QTextEdit()
2275 seed_e.setMaximumHeight(100)
2276 vbox.addWidget(seed_e)
2279 grid = QGridLayout()
2281 gap_e = AmountEdit(None, True)
2283 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2284 grid.addWidget(gap_e, 2, 1)
2285 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2286 vbox.addLayout(grid)
2288 vbox.addLayout(ok_cancel_buttons(d))
2291 if not d.exec_(): return
2294 seed = str(seed_e.toPlainText())
2298 seed = mnemonic.mn_decode( seed.split() )
2300 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2304 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2311 gap = int(unicode(gap_e.text()))
2313 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2318 def network_dialog(self):
2319 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2322 def show_seed(self):
2323 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2325 def password_dialog(self):
2326 if self.wallet.seed:
2327 ElectrumWindow.change_password_dialog(self.wallet)
2330 def restore_wallet(self):
2331 wallet = self.wallet
2332 # wait until we are connected, because the user might have selected another server
2333 if not wallet.interface.is_connected:
2334 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2335 waiting_dialog(waiting)
2337 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2338 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2340 wallet.set_up_to_date(False)
2341 wallet.interface.poke('synchronizer')
2342 waiting_dialog(waiting)
2343 if wallet.is_found():
2344 print_error( "Recovery successful" )
2346 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2353 w = ElectrumWindow(self.wallet, self.config)
2354 if url: w.set_url(url)