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
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
23 from qrcodewidget import QRCodeWidget
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
33 import PyQt4.QtGui as QtGui
34 from electrum.interface import DEFAULT_SERVERS
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 decimal import Decimal
57 if platform.system() == 'Windows':
58 MONOSPACE_FONT = 'Lucida Console'
59 elif platform.system() == 'Darwin':
60 MONOSPACE_FONT = 'Monaco'
62 MONOSPACE_FONT = 'monospace'
64 from electrum import ELECTRUM_VERSION
67 class UpdateLabel(QtGui.QLabel):
68 def __init__(self, config, parent=None):
69 QtGui.QLabel.__init__(self, parent)
70 self.new_version = False
73 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
74 con.request("GET", "/version")
75 res = con.getresponse()
76 except socket.error as msg:
77 print_error("Could not retrieve version information")
81 self.latest_version = res.read()
82 self.latest_version = self.latest_version.replace("\n","")
83 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
85 self.current_version = ELECTRUM_VERSION
86 if(self.compare_versions(self.latest_version, self.current_version) == 1):
87 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
88 if(self.compare_versions(self.latest_version, latest_seen) == 1):
89 self.new_version = True
90 self.setText(_("New version available") + ": " + self.latest_version)
93 def compare_versions(self, version1, version2):
95 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
96 return cmp(normalize(version1), normalize(version2))
98 def ignore_this_version(self):
100 self.config.set_key("last_seen_version", self.latest_version, True)
101 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
104 def ignore_all_version(self):
106 self.config.set_key("last_seen_version", "9.9.9", True)
107 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
110 def open_website(self):
111 webbrowser.open("http://electrum.org/download.html")
114 def mouseReleaseEvent(self, event):
115 dialog = QDialog(self)
116 dialog.setWindowTitle(_('Electrum update'))
119 main_layout = QGridLayout()
120 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
122 ignore_version = QPushButton(_("Ignore this version"))
123 ignore_version.clicked.connect(self.ignore_this_version)
125 ignore_all_versions = QPushButton(_("Ignore all versions"))
126 ignore_all_versions.clicked.connect(self.ignore_all_version)
128 open_website = QPushButton(_("Goto download page"))
129 open_website.clicked.connect(self.open_website)
131 main_layout.addWidget(ignore_version, 1, 0)
132 main_layout.addWidget(ignore_all_versions, 1, 1)
133 main_layout.addWidget(open_website, 1, 2)
135 dialog.setLayout(main_layout)
139 if not dialog.exec_(): return
141 def numbify(entry, is_int = False):
142 text = unicode(entry.text()).strip()
143 pos = entry.cursorPosition()
145 if not is_int: chars +='.'
146 s = ''.join([i for i in text if i in chars])
150 s = s.replace('.','')
151 s = s[:p] + '.' + s[p:p+8]
153 amount = int( Decimal(s) * 100000000 )
162 entry.setCursorPosition(pos)
166 class Timer(QtCore.QThread):
169 self.emit(QtCore.SIGNAL('timersignal'))
172 class HelpButton(QPushButton):
173 def __init__(self, text):
174 QPushButton.__init__(self, '?')
175 self.setFocusPolicy(Qt.NoFocus)
176 self.setFixedWidth(20)
177 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
180 class EnterButton(QPushButton):
181 def __init__(self, text, func):
182 QPushButton.__init__(self, text)
184 self.clicked.connect(func)
186 def keyPressEvent(self, e):
187 if e.key() == QtCore.Qt.Key_Return:
190 class MyTreeWidget(QTreeWidget):
191 def __init__(self, parent):
192 QTreeWidget.__init__(self, parent)
195 for i in range(0,self.viewport().height()/5):
196 if self.itemAt(QPoint(0,i*5)) == item:
200 for j in range(0,30):
201 if self.itemAt(QPoint(0,i*5 + j)) != item:
203 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
205 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
210 class StatusBarButton(QPushButton):
211 def __init__(self, icon, tooltip, func):
212 QPushButton.__init__(self, icon, '')
213 self.setToolTip(tooltip)
215 self.setMaximumWidth(25)
216 self.clicked.connect(func)
219 def keyPressEvent(self, e):
220 if e.key() == QtCore.Qt.Key_Return:
227 def waiting_dialog(f):
233 w.setWindowTitle('Electrum')
243 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
248 def ok_cancel_buttons(dialog, ok_label=_("OK") ):
251 b = QPushButton(_("Cancel"))
253 b.clicked.connect(dialog.reject)
254 b = QPushButton(ok_label)
256 b.clicked.connect(dialog.accept)
261 def text_dialog(parent, title, label, ok_label):
262 dialog = QDialog(parent)
263 dialog.setMinimumWidth(500)
264 dialog.setWindowTitle(title)
268 l.addWidget(QLabel(label))
271 l.addLayout(ok_cancel_buttons(dialog, ok_label))
273 return unicode(txt.toPlainText())
277 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
278 "receive":[[370],[370,200,130]] }
280 class ElectrumWindow(QMainWindow):
282 def __init__(self, wallet, config):
283 QMainWindow.__init__(self)
289 self.create_status_bar()
291 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
292 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
293 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
294 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
296 self.expert_mode = config.get('classic_expert_mode', False)
298 set_language(config.get('language'))
300 self.funds_error = False
301 self.completions = QStringListModel()
303 self.tabs = tabs = QTabWidget(self)
304 self.column_widths = self.config.get("column-widths", default_column_widths )
305 tabs.addTab(self.create_history_tab(), _('History') )
306 tabs.addTab(self.create_send_tab(), _('Send') )
307 tabs.addTab(self.create_receive_tab(), _('Receive') )
308 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
309 tabs.addTab(self.create_console_tab(), _('Console') )
310 tabs.setMinimumSize(600, 400)
311 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
312 self.setCentralWidget(tabs)
314 g = self.config.get("winpos-qt",[100, 100, 840, 400])
315 self.setGeometry(g[0], g[1], g[2], g[3])
316 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
317 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
318 self.setWindowTitle( title )
320 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
321 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
322 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
323 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
325 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
326 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
327 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
328 self.history_list.setFocus(True)
330 self.exchanger = exchange_rate.Exchanger(self)
331 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
333 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
334 if platform.system() == 'Windows':
335 n = 3 if self.wallet.seed else 2
336 tabs.setCurrentIndex (n)
337 tabs.setCurrentIndex (0)
339 # set initial message
340 self.console.showMessage(self.wallet.interface.banner)
342 # plugins that need to change the GUI do it here
343 self.run_hook('init_gui')
347 def init_plugins(self):
348 import imp, pkgutil, __builtin__
349 if __builtin__.use_local_modules:
350 fp, pathname, description = imp.find_module('plugins')
351 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
352 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
353 imp.load_module('electrum_plugins', fp, pathname, description)
354 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
356 import electrum_plugins
357 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
358 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
363 self.plugins.append( p.Plugin(self) )
365 print_msg("Error:cannot initialize plugin",p)
366 traceback.print_exc(file=sys.stdout)
369 def run_hook(self, name, *args):
370 for p in self.plugins:
371 if not p.is_enabled():
381 def set_label(self, name, text = None):
383 old_text = self.wallet.labels.get(name)
386 self.wallet.labels[name] = text
390 self.wallet.labels.pop(name)
392 self.run_hook('set_label', name, text, changed)
396 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
397 def getOpenFileName(self, title, filter = None):
398 directory = self.config.get('io_dir', os.path.expanduser('~'))
399 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
400 if fileName and directory != os.path.dirname(fileName):
401 self.config.set_key('io_dir', os.path.dirname(fileName), True)
404 def getSaveFileName(self, title, filename, filter = None):
405 directory = self.config.get('io_dir', os.path.expanduser('~'))
406 path = os.path.join( directory, filename )
407 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
408 if fileName and directory != os.path.dirname(fileName):
409 self.config.set_key('io_dir', os.path.dirname(fileName), True)
415 QMainWindow.close(self)
416 self.run_hook('close_main_window')
418 def connect_slots(self, sender):
419 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
420 self.previous_payto_e=''
422 def timer_actions(self):
423 self.run_hook('timer_actions')
425 def update_status(self):
426 if self.wallet.interface and self.wallet.interface.is_connected:
427 if not self.wallet.up_to_date:
428 text = _("Synchronizing...")
429 icon = QIcon(":icons/status_waiting.png")
431 c, u = self.wallet.get_balance()
432 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
433 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
434 text += self.create_quote_text(Decimal(c+u)/100000000)
435 icon = QIcon(":icons/status_connected.png")
437 text = _("Not connected")
438 icon = QIcon(":icons/status_disconnected.png")
440 self.status_text = text
441 self.statusBar().showMessage(text)
442 self.status_button.setIcon( icon )
444 def update_wallet(self):
446 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
447 self.update_history_tab()
448 self.update_receive_tab()
449 self.update_contacts_tab()
450 self.update_completions()
453 def create_quote_text(self, btc_balance):
454 quote_currency = self.config.get("currency", "None")
455 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
456 if quote_balance is None:
459 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
462 def create_history_tab(self):
463 self.history_list = l = MyTreeWidget(self)
465 for i,width in enumerate(self.column_widths['history']):
466 l.setColumnWidth(i, width)
467 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
468 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
469 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
471 l.setContextMenuPolicy(Qt.CustomContextMenu)
472 l.customContextMenuRequested.connect(self.create_history_menu)
476 def create_history_menu(self, position):
477 self.history_list.selectedIndexes()
478 item = self.history_list.currentItem()
480 tx_hash = str(item.data(0, Qt.UserRole).toString())
481 if not tx_hash: return
483 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
484 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
485 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
486 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
489 def show_tx_details(self, tx):
490 dialog = QDialog(self)
492 dialog.setWindowTitle(_("Transaction Details"))
494 dialog.setLayout(vbox)
495 dialog.setMinimumSize(600,300)
498 if tx_hash in self.wallet.transactions.keys():
499 is_mine, v, fee = self.wallet.get_tx_value(tx)
500 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
502 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
508 vbox.addWidget(QLabel("Transaction ID:"))
509 e = QLineEdit(tx_hash)
513 vbox.addWidget(QLabel("Date: %s"%time_str))
514 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
517 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
518 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
520 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
521 vbox.addWidget(QLabel("Transaction fee: unknown"))
523 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
525 vbox.addWidget( self.generate_transaction_information_widget(tx) )
527 ok_button = QPushButton(_("Close"))
528 ok_button.setDefault(True)
529 ok_button.clicked.connect(dialog.accept)
533 hbox.addWidget(ok_button)
537 def tx_label_clicked(self, item, column):
538 if column==2 and item.isSelected():
540 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
541 self.history_list.editItem( item, column )
542 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
545 def tx_label_changed(self, item, column):
549 tx_hash = str(item.data(0, Qt.UserRole).toString())
550 tx = self.wallet.transactions.get(tx_hash)
551 text = unicode( item.text(2) )
552 self.set_label(tx_hash, text)
554 item.setForeground(2, QBrush(QColor('black')))
556 text = self.wallet.get_default_label(tx_hash)
557 item.setText(2, text)
558 item.setForeground(2, QBrush(QColor('gray')))
562 def edit_label(self, is_recv):
563 l = self.receive_list if is_recv else self.contacts_list
564 item = l.currentItem()
565 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
566 l.editItem( item, 1 )
567 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
571 def address_label_clicked(self, item, column, l, column_addr, column_label):
572 if column == column_label and item.isSelected():
573 is_editable = item.data(0, 32).toBool()
576 addr = unicode( item.text(column_addr) )
577 label = unicode( item.text(column_label) )
578 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
579 l.editItem( item, column )
580 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
583 def address_label_changed(self, item, column, l, column_addr, column_label):
584 if column == column_label:
585 addr = unicode( item.text(column_addr) )
586 text = unicode( item.text(column_label) )
587 is_editable = item.data(0, 32).toBool()
591 changed = self.set_label(addr, text)
593 self.update_history_tab()
594 self.update_completions()
596 self.current_item_changed(item)
598 self.run_hook('item_changed', item, column)
601 def current_item_changed(self, a):
602 self.run_hook('current_item_changed', a)
606 def update_history_tab(self):
608 self.history_list.clear()
609 for item in self.wallet.get_tx_history():
610 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
613 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
619 icon = QIcon(":icons/unconfirmed.png")
621 icon = QIcon(":icons/clock%d.png"%conf)
623 icon = QIcon(":icons/confirmed.png")
626 icon = QIcon(":icons/unconfirmed.png")
628 if value is not None:
629 v_str = format_satoshis(value, True, self.wallet.num_zeros)
633 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
636 label, is_default_label = self.wallet.get_label(tx_hash)
638 label = _('Pruned transaction outputs')
639 is_default_label = False
641 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
642 item.setFont(2, QFont(MONOSPACE_FONT))
643 item.setFont(3, QFont(MONOSPACE_FONT))
644 item.setFont(4, QFont(MONOSPACE_FONT))
646 item.setForeground(3, QBrush(QColor("#BC1E1E")))
648 item.setData(0, Qt.UserRole, tx_hash)
649 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
651 item.setForeground(2, QBrush(QColor('grey')))
653 item.setIcon(0, icon)
654 self.history_list.insertTopLevelItem(0,item)
657 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
660 def create_send_tab(self):
665 grid.setColumnMinimumWidth(3,300)
666 grid.setColumnStretch(5,1)
668 self.payto_e = QLineEdit()
669 grid.addWidget(QLabel(_('Pay to')), 1, 0)
670 grid.addWidget(self.payto_e, 1, 1, 1, 3)
672 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)
674 completer = QCompleter()
675 completer.setCaseSensitivity(False)
676 self.payto_e.setCompleter(completer)
677 completer.setModel(self.completions)
679 self.message_e = QLineEdit()
680 grid.addWidget(QLabel(_('Description')), 2, 0)
681 grid.addWidget(self.message_e, 2, 1, 1, 3)
682 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)
684 self.amount_e = QLineEdit()
685 grid.addWidget(QLabel(_('Amount')), 3, 0)
686 grid.addWidget(self.amount_e, 3, 1, 1, 2)
687 grid.addWidget(HelpButton(
688 _('Amount to be sent.') + '\n\n' \
689 + _('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.')), 3, 3)
691 self.fee_e = QLineEdit()
692 grid.addWidget(QLabel(_('Fee')), 4, 0)
693 grid.addWidget(self.fee_e, 4, 1, 1, 2)
694 grid.addWidget(HelpButton(
695 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
696 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
697 + _('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)
700 b = EnterButton(_("Send"), self.do_send)
702 b = EnterButton(_("Create unsigned transaction"), self.do_send)
703 grid.addWidget(b, 6, 1)
705 b = EnterButton(_("Clear"),self.do_clear)
706 grid.addWidget(b, 6, 2)
708 self.payto_sig = QLabel('')
709 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
711 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
712 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
721 def entry_changed( is_fee ):
722 self.funds_error = False
723 amount = numbify(self.amount_e)
724 fee = numbify(self.fee_e)
725 if not is_fee: fee = None
728 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
730 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
733 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
734 text = self.status_text
737 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
738 self.funds_error = True
739 text = _( "Not enough funds" )
741 self.statusBar().showMessage(text)
742 self.amount_e.setPalette(palette)
743 self.fee_e.setPalette(palette)
745 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
746 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
748 self.run_hook('create_send_tab', grid)
752 def update_completions(self):
754 for addr,label in self.wallet.labels.items():
755 if addr in self.wallet.addressbook:
756 l.append( label + ' <' + addr + '>')
758 self.run_hook('update_completions', l)
759 self.completions.setStringList(l)
763 return lambda s, *args: s.do_protect(func, args)
767 def do_send(self, password):
769 label = unicode( self.message_e.text() )
770 r = unicode( self.payto_e.text() )
773 # label or alias, with address in brackets
774 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
775 to_address = m.group(2) if m else r
777 if not is_valid(to_address):
778 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
782 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
784 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
787 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
789 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
793 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
794 except BaseException, e:
795 self.show_message(str(e))
798 self.run_hook('send_tx', tx)
801 self.set_label(tx.hash(), label)
804 h = self.wallet.send_tx(tx)
805 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
806 status, msg = self.wallet.receive_tx( h )
808 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
810 self.update_contacts_tab()
812 QMessageBox.warning(self, _('Error'), msg, _('OK'))
814 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
816 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
817 with open(fileName,'w') as f:
818 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
819 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
821 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
826 def set_url(self, url):
827 address, amount, label, message, signature, identity, url = util.parse_url(url)
829 if label and self.wallet.labels.get(address) != label:
830 if self.question('Give label "%s" to address %s ?'%(label,address)):
831 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
832 self.wallet.addressbook.append(address)
833 self.set_label(address, label)
835 self.run_hook('set_url', url, self.show_message, self.question)
837 self.tabs.setCurrentIndex(1)
838 label = self.wallet.labels.get(address)
839 m_addr = label + ' <'+ address +'>' if label else address
840 self.payto_e.setText(m_addr)
842 self.message_e.setText(message)
843 self.amount_e.setText(amount)
845 self.set_frozen(self.payto_e,True)
846 self.set_frozen(self.amount_e,True)
847 self.set_frozen(self.message_e,True)
848 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
850 self.payto_sig.setVisible(False)
853 self.payto_sig.setVisible(False)
854 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
856 self.set_frozen(e,False)
858 def set_frozen(self,entry,frozen):
860 entry.setReadOnly(True)
861 entry.setFrame(False)
863 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
864 entry.setPalette(palette)
866 entry.setReadOnly(False)
869 palette.setColor(entry.backgroundRole(), QColor('white'))
870 entry.setPalette(palette)
873 def toggle_freeze(self,addr):
875 if addr in self.wallet.frozen_addresses:
876 self.wallet.unfreeze(addr)
878 self.wallet.freeze(addr)
879 self.update_receive_tab()
881 def toggle_priority(self,addr):
883 if addr in self.wallet.prioritized_addresses:
884 self.wallet.unprioritize(addr)
886 self.wallet.prioritize(addr)
887 self.update_receive_tab()
890 def create_list_tab(self, headers):
891 "generic tab creation method"
892 l = MyTreeWidget(self)
893 l.setColumnCount( len(headers) )
894 l.setHeaderLabels( headers )
904 vbox.addWidget(buttons)
909 buttons.setLayout(hbox)
914 def create_receive_tab(self):
915 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
916 l.setContextMenuPolicy(Qt.CustomContextMenu)
917 l.customContextMenuRequested.connect(self.create_receive_menu)
918 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
919 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
920 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
921 self.receive_list = l
922 self.receive_buttons_hbox = hbox
927 def receive_tab_set_mode(self, i):
928 self.save_column_widths()
929 self.expert_mode = (i == 1)
930 self.config.set_key('classic_expert_mode', self.expert_mode, True)
932 self.update_receive_tab()
935 def save_column_widths(self):
936 if not self.expert_mode:
937 widths = [ self.receive_list.columnWidth(0) ]
940 for i in range(self.receive_list.columnCount() -1):
941 widths.append(self.receive_list.columnWidth(i))
942 self.column_widths["receive"][self.expert_mode] = widths
944 self.column_widths["history"] = []
945 for i in range(self.history_list.columnCount() - 1):
946 self.column_widths["history"].append(self.history_list.columnWidth(i))
948 self.column_widths["contacts"] = []
949 for i in range(self.contacts_list.columnCount() - 1):
950 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
953 def create_contacts_tab(self):
954 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
955 l.setContextMenuPolicy(Qt.CustomContextMenu)
956 l.customContextMenuRequested.connect(self.create_contact_menu)
957 for i,width in enumerate(self.column_widths['contacts']):
958 l.setColumnWidth(i, width)
960 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
961 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
962 self.contacts_list = l
963 self.contacts_buttons_hbox = hbox
964 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
969 def delete_imported_key(self, addr):
970 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
971 self.wallet.imported_keys.pop(addr)
972 self.update_receive_tab()
973 self.update_history_tab()
977 def create_receive_menu(self, position):
978 # fixme: this function apparently has a side effect.
979 # if it is not called the menu pops up several times
980 #self.receive_list.selectedIndexes()
982 item = self.receive_list.itemAt(position)
984 addr = unicode(item.text(0))
985 if not is_valid(addr):
986 item.setExpanded(not item.isExpanded())
989 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
990 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
991 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
992 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
993 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
994 if addr in self.wallet.imported_keys:
995 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
998 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
999 menu.addAction(t, lambda: self.toggle_freeze(addr))
1000 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1001 menu.addAction(t, lambda: self.toggle_priority(addr))
1003 self.run_hook('receive_menu', menu)
1004 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1007 def payto(self, addr):
1009 label = self.wallet.labels.get(addr)
1010 m_addr = label + ' <' + addr + '>' if label else addr
1011 self.tabs.setCurrentIndex(1)
1012 self.payto_e.setText(m_addr)
1013 self.amount_e.setFocus()
1016 def delete_contact(self, x):
1017 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1018 if x in self.wallet.addressbook:
1019 self.wallet.addressbook.remove(x)
1020 self.set_label(x, None)
1021 self.update_history_tab()
1022 self.update_contacts_tab()
1023 self.update_completions()
1026 def create_contact_menu(self, position):
1027 item = self.contacts_list.itemAt(position)
1029 addr = unicode(item.text(0))
1030 label = unicode(item.text(1))
1031 is_editable = item.data(0,32).toBool()
1032 payto_addr = item.data(0,33).toString()
1034 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1035 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1036 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1038 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1039 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1041 self.run_hook('create_contact_menu', menu, item)
1042 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1045 def update_receive_item(self, item):
1046 item.setFont(0, QFont(MONOSPACE_FONT))
1047 address = str(item.data(0,0).toString())
1048 label = self.wallet.labels.get(address,'')
1049 item.setData(1,0,label)
1050 item.setData(0,32, True) # is editable
1052 self.run_hook('update_receive_item', address, item)
1054 c, u = self.wallet.get_addr_balance(address)
1055 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1056 item.setData(2,0,balance)
1058 if self.expert_mode:
1059 if address in self.wallet.frozen_addresses:
1060 item.setBackgroundColor(0, QColor('lightblue'))
1061 elif address in self.wallet.prioritized_addresses:
1062 item.setBackgroundColor(0, QColor('lightgreen'))
1065 def update_receive_tab(self):
1066 l = self.receive_list
1069 l.setColumnHidden(2, not self.expert_mode)
1070 l.setColumnHidden(3, not self.expert_mode)
1071 if not self.expert_mode:
1072 width = self.column_widths['receive'][0][0]
1073 l.setColumnWidth(0, width)
1075 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1076 l.setColumnWidth(i, width)
1079 for k, account in self.wallet.accounts.items():
1080 name = account.get('name',str(k))
1081 c,u = self.wallet.get_account_balance(k)
1082 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1083 l.addTopLevelItem(account_item)
1084 account_item.setExpanded(True)
1087 for is_change in [0,1]:
1088 name = "Receiving" if not is_change else "Change"
1089 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1090 account_item.addChild(seq_item)
1091 if not is_change: seq_item.setExpanded(True)
1095 for address in account[is_change]:
1096 h = self.wallet.history.get(address,[])
1100 if gap > self.wallet.gap_limit:
1105 num_tx = '*' if h == ['*'] else "%d"%len(h)
1106 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1107 self.update_receive_item(item)
1109 item.setBackgroundColor(1, QColor('red'))
1110 seq_item.addChild(item)
1112 if self.wallet.imported_keys:
1113 c,u = self.wallet.get_imported_balance()
1114 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1115 l.addTopLevelItem(account_item)
1116 account_item.setExpanded(True)
1117 for address in self.wallet.imported_keys.keys():
1118 item = QTreeWidgetItem( [ address, '', '', ''] )
1119 self.update_receive_item(item)
1120 account_item.addChild(item)
1123 # we use column 1 because column 0 may be hidden
1124 l.setCurrentItem(l.topLevelItem(0),1)
1127 def update_contacts_tab(self):
1129 l = self.contacts_list
1132 for address in self.wallet.addressbook:
1133 label = self.wallet.labels.get(address,'')
1134 n = self.wallet.get_num_tx(address)
1135 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1136 item.setFont(0, QFont(MONOSPACE_FONT))
1137 # 32 = label can be edited (bool)
1138 item.setData(0,32, True)
1140 item.setData(0,33, address)
1141 l.addTopLevelItem(item)
1143 self.run_hook('update_contacts_tab', l)
1144 l.setCurrentItem(l.topLevelItem(0))
1148 def create_console_tab(self):
1149 from qt_console import Console
1150 self.console = console = Console()
1151 self.console.history = self.config.get("console-history",[])
1152 self.console.history_index = len(self.console.history)
1154 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1155 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1157 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1159 def mkfunc(f, method):
1160 return lambda *args: apply( f, (method, args, self.password_dialog ))
1162 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1163 methods[m] = mkfunc(c._run, m)
1165 console.updateNamespace(methods)
1169 def create_status_bar(self):
1170 self.status_text = ""
1172 sb.setFixedHeight(35)
1173 qtVersion = qVersion()
1175 update_notification = UpdateLabel(self.config)
1176 if(update_notification.new_version):
1177 sb.addPermanentWidget(update_notification)
1179 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1180 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1181 if self.wallet.seed:
1182 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1183 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1184 sb.addPermanentWidget( self.password_button )
1185 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1186 if self.wallet.seed:
1187 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1188 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1189 sb.addPermanentWidget( self.status_button )
1191 self.run_hook('create_status_bar', (sb,))
1193 self.setStatusBar(sb)
1197 self.config.set_key('gui', 'lite', True)
1200 self.lite.mini.show()
1202 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1203 self.lite.main(None)
1205 def new_contact_dialog(self):
1206 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1207 address = unicode(text)
1209 if is_valid(address):
1210 self.wallet.addressbook.append(address)
1212 self.update_contacts_tab()
1213 self.update_history_tab()
1214 self.update_completions()
1216 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1218 def show_master_public_key(self):
1219 dialog = QDialog(self)
1221 dialog.setWindowTitle(_("Master Public Key"))
1223 main_text = QTextEdit()
1224 main_text.setText(self.wallet.get_master_public_key())
1225 main_text.setReadOnly(True)
1226 main_text.setMaximumHeight(170)
1227 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1229 ok_button = QPushButton(_("OK"))
1230 ok_button.setDefault(True)
1231 ok_button.clicked.connect(dialog.accept)
1233 main_layout = QGridLayout()
1234 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1236 main_layout.addWidget(main_text, 1, 0)
1237 main_layout.addWidget(qrw, 1, 1 )
1239 vbox = QVBoxLayout()
1240 vbox.addLayout(main_layout)
1241 hbox = QHBoxLayout()
1243 hbox.addWidget(ok_button)
1244 vbox.addLayout(hbox)
1246 dialog.setLayout(vbox)
1251 def show_seed_dialog(self, password):
1252 if not self.wallet.seed:
1253 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1256 seed = self.wallet.decode_seed(password)
1258 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1260 self.show_seed(seed, self)
1264 def show_seed(self, seed, parent=None):
1265 dialog = QDialog(parent)
1267 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1269 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1271 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1273 seed_text = QTextEdit(brainwallet)
1274 seed_text.setReadOnly(True)
1275 seed_text.setMaximumHeight(130)
1277 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1278 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1279 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1280 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1281 label2 = QLabel(msg2)
1282 label2.setWordWrap(True)
1285 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1286 logo.setMaximumWidth(60)
1288 qrw = QRCodeWidget(seed)
1290 ok_button = QPushButton(_("OK"))
1291 ok_button.setDefault(True)
1292 ok_button.clicked.connect(dialog.accept)
1294 grid = QGridLayout()
1295 #main_layout.addWidget(logo, 0, 0)
1297 grid.addWidget(logo, 0, 0)
1298 grid.addWidget(label1, 0, 1)
1300 grid.addWidget(seed_text, 1, 0, 1, 2)
1302 grid.addWidget(qrw, 0, 2, 2, 1)
1304 vbox = QVBoxLayout()
1305 vbox.addLayout(grid)
1306 vbox.addWidget(label2)
1308 hbox = QHBoxLayout()
1310 hbox.addWidget(ok_button)
1311 vbox.addLayout(hbox)
1313 dialog.setLayout(vbox)
1316 def show_qrcode(self, data, title = "QR code"):
1320 d.setWindowTitle(title)
1321 d.setMinimumSize(270, 300)
1322 vbox = QVBoxLayout()
1323 qrw = QRCodeWidget(data)
1324 vbox.addWidget(qrw, 1)
1325 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1326 hbox = QHBoxLayout()
1330 filename = "qrcode.bmp"
1331 bmp.save_qrcode(qrw.qr, filename)
1332 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1334 b = QPushButton(_("Save"))
1336 b.clicked.connect(print_qr)
1338 b = QPushButton(_("Close"))
1340 b.clicked.connect(d.accept)
1343 vbox.addLayout(hbox)
1348 def do_protect(self, func, args):
1349 if self.wallet.use_encryption:
1350 password = self.password_dialog()
1356 if args != (False,):
1357 args = (self,) + args + (password,)
1359 args = (self,password)
1364 def show_private_key(self, address, password):
1365 if not address: return
1367 pk = self.wallet.get_private_key(address, password)
1368 except BaseException, e:
1369 self.show_message(str(e))
1371 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1375 def do_sign(self, address, message, signature, password):
1377 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1378 signature.setText(sig)
1379 except BaseException, e:
1380 self.show_message(str(e))
1382 def sign_message(self, address):
1383 if not address: return
1386 d.setWindowTitle(_('Sign Message'))
1387 d.setMinimumSize(410, 290)
1389 tab_widget = QTabWidget()
1391 layout = QGridLayout(tab)
1393 sign_address = QLineEdit()
1395 sign_address.setText(address)
1396 layout.addWidget(QLabel(_('Address')), 1, 0)
1397 layout.addWidget(sign_address, 1, 1)
1399 sign_message = QTextEdit()
1400 layout.addWidget(QLabel(_('Message')), 2, 0)
1401 layout.addWidget(sign_message, 2, 1)
1402 layout.setRowStretch(2,3)
1404 sign_signature = QTextEdit()
1405 layout.addWidget(QLabel(_('Signature')), 3, 0)
1406 layout.addWidget(sign_signature, 3, 1)
1407 layout.setRowStretch(3,1)
1410 hbox = QHBoxLayout()
1411 b = QPushButton(_("Sign"))
1413 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1414 b = QPushButton(_("Close"))
1415 b.clicked.connect(d.accept)
1417 layout.addLayout(hbox, 4, 1)
1418 tab_widget.addTab(tab, _("Sign"))
1422 layout = QGridLayout(tab)
1424 verify_address = QLineEdit()
1425 layout.addWidget(QLabel(_('Address')), 1, 0)
1426 layout.addWidget(verify_address, 1, 1)
1428 verify_message = QTextEdit()
1429 layout.addWidget(QLabel(_('Message')), 2, 0)
1430 layout.addWidget(verify_message, 2, 1)
1431 layout.setRowStretch(2,3)
1433 verify_signature = QTextEdit()
1434 layout.addWidget(QLabel(_('Signature')), 3, 0)
1435 layout.addWidget(verify_signature, 3, 1)
1436 layout.setRowStretch(3,1)
1440 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1441 self.show_message(_("Signature verified"))
1442 except BaseException, e:
1443 self.show_message(str(e))
1446 hbox = QHBoxLayout()
1447 b = QPushButton(_("Verify"))
1448 b.clicked.connect(do_verify)
1450 b = QPushButton(_("Close"))
1451 b.clicked.connect(d.accept)
1453 layout.addLayout(hbox, 4, 1)
1454 tab_widget.addTab(tab, _("Verify"))
1456 vbox = QVBoxLayout()
1457 vbox.addWidget(tab_widget)
1464 def question(self, msg):
1465 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1467 def show_message(self, msg):
1468 QMessageBox.information(self, _('Message'), msg, _('OK'))
1470 def password_dialog(self ):
1477 vbox = QVBoxLayout()
1478 msg = _('Please enter your password')
1479 vbox.addWidget(QLabel(msg))
1481 grid = QGridLayout()
1483 grid.addWidget(QLabel(_('Password')), 1, 0)
1484 grid.addWidget(pw, 1, 1)
1485 vbox.addLayout(grid)
1487 vbox.addLayout(ok_cancel_buttons(d))
1490 self.run_hook('password_dialog', pw, grid, 1)
1491 if not d.exec_(): return
1492 return unicode(pw.text())
1499 def change_password_dialog( wallet, parent=None ):
1502 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1510 new_pw = QLineEdit()
1511 new_pw.setEchoMode(2)
1512 conf_pw = QLineEdit()
1513 conf_pw.setEchoMode(2)
1515 vbox = QVBoxLayout()
1517 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1518 +_('To disable wallet encryption, enter an empty new password.')) \
1519 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1521 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1522 +_("Leave these fields empty if you want to disable encryption.")
1523 vbox.addWidget(QLabel(msg))
1525 grid = QGridLayout()
1528 if wallet.use_encryption:
1529 grid.addWidget(QLabel(_('Password')), 1, 0)
1530 grid.addWidget(pw, 1, 1)
1532 grid.addWidget(QLabel(_('New Password')), 2, 0)
1533 grid.addWidget(new_pw, 2, 1)
1535 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1536 grid.addWidget(conf_pw, 3, 1)
1537 vbox.addLayout(grid)
1539 vbox.addLayout(ok_cancel_buttons(d))
1542 if not d.exec_(): return
1544 password = unicode(pw.text()) if wallet.use_encryption else None
1545 new_password = unicode(new_pw.text())
1546 new_password2 = unicode(conf_pw.text())
1549 seed = wallet.decode_seed(password)
1551 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1554 if new_password != new_password2:
1555 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1556 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1558 wallet.update_password(seed, password, new_password)
1560 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1561 parent.password_button.setIcon( icon )
1565 def seed_dialog(wallet, parent=None):
1569 vbox = QVBoxLayout()
1570 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1571 vbox.addWidget(QLabel(msg))
1573 grid = QGridLayout()
1576 seed_e = QLineEdit()
1577 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1578 grid.addWidget(seed_e, 1, 1)
1579 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1583 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1584 grid.addWidget(gap_e, 2, 1)
1585 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1586 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1587 vbox.addLayout(grid)
1589 vbox.addLayout(ok_cancel_buttons(d))
1592 if not d.exec_(): return
1595 gap = int(unicode(gap_e.text()))
1597 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1601 seed = str(seed_e.text())
1604 print_error("Warning: Not hex, trying decode")
1606 seed = mnemonic.mn_decode( seed.split(' ') )
1608 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1612 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1617 def generate_transaction_information_widget(self, tx):
1618 tabs = QTabWidget(self)
1621 grid_ui = QGridLayout(tab1)
1622 grid_ui.setColumnStretch(0,1)
1623 tabs.addTab(tab1, _('Outputs') )
1625 tree_widget = MyTreeWidget(self)
1626 tree_widget.setColumnCount(2)
1627 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1628 tree_widget.setColumnWidth(0, 300)
1629 tree_widget.setColumnWidth(1, 50)
1631 for address, value in tx.outputs:
1632 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1633 tree_widget.addTopLevelItem(item)
1635 tree_widget.setMaximumHeight(100)
1637 grid_ui.addWidget(tree_widget)
1640 grid_ui = QGridLayout(tab2)
1641 grid_ui.setColumnStretch(0,1)
1642 tabs.addTab(tab2, _('Inputs') )
1644 tree_widget = MyTreeWidget(self)
1645 tree_widget.setColumnCount(2)
1646 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1648 for input_line in tx.inputs:
1649 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1650 tree_widget.addTopLevelItem(item)
1652 tree_widget.setMaximumHeight(100)
1654 grid_ui.addWidget(tree_widget)
1658 def tx_dict_from_text(self, txt):
1660 tx_dict = json.loads(str(txt))
1661 assert "hex" in tx_dict.keys()
1662 assert "complete" in tx_dict.keys()
1663 if not tx_dict["complete"]:
1664 assert "input_info" in tx_dict.keys()
1666 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1671 def read_tx_from_file(self):
1672 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1676 with open(fileName, "r") as f:
1677 file_content = f.read()
1678 except (ValueError, IOError, os.error), reason:
1679 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1681 return self.tx_dict_from_text(file_content)
1685 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1687 self.wallet.signrawtransaction(tx, input_info, [], password)
1689 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1691 with open(fileName, "w+") as f:
1692 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1693 self.show_message(_("Transaction saved successfully"))
1696 except BaseException, e:
1697 self.show_message(str(e))
1700 def send_raw_transaction(self, raw_tx, dialog = ""):
1701 result, result_message = self.wallet.sendtx( raw_tx )
1703 self.show_message("Transaction successfully sent: %s" % (result_message))
1707 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1709 def do_process_from_text(self):
1710 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1713 tx_dict = self.tx_dict_from_text(text)
1715 self.create_process_transaction_window(tx_dict)
1717 def do_process_from_file(self):
1718 tx_dict = self.read_tx_from_file()
1720 self.create_process_transaction_window(tx_dict)
1722 def create_process_transaction_window(self, tx_dict):
1723 tx = Transaction(tx_dict["hex"])
1725 dialog = QDialog(self)
1726 dialog.setMinimumWidth(500)
1727 dialog.setWindowTitle(_('Process raw transaction'))
1733 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1734 l.addWidget(QLabel(_("Actions")), 4,0)
1736 if tx_dict["complete"] == False:
1737 l.addWidget(QLabel(_("Unsigned")), 3,1)
1738 if self.wallet.seed :
1739 b = QPushButton("Sign transaction")
1740 input_info = json.loads(tx_dict["input_info"])
1741 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1742 l.addWidget(b, 4, 1)
1744 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1746 l.addWidget(QLabel(_("Signed")), 3,1)
1747 b = QPushButton("Broadcast transaction")
1748 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1751 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1752 cancelButton = QPushButton(_("Cancel"))
1753 cancelButton.clicked.connect(lambda: dialog.done(0))
1754 l.addWidget(cancelButton, 4,2)
1760 def do_export_privkeys(self, password):
1761 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.")))
1764 select_export = _('Select file to export your private keys to')
1765 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1767 with open(fileName, "w+") as csvfile:
1768 transaction = csv.writer(csvfile)
1769 transaction.writerow(["address", "private_key"])
1772 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1773 transaction.writerow(["%34s"%addr,pk])
1775 self.show_message(_("Private keys exported."))
1777 except (IOError, os.error), reason:
1778 export_error_label = _("Electrum was unable to produce a private key-export.")
1779 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1781 except BaseException, e:
1782 self.show_message(str(e))
1786 def do_import_labels(self):
1787 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1788 if not labelsFile: return
1790 f = open(labelsFile, 'r')
1793 for key, value in json.loads(data).items():
1794 self.wallet.labels[key] = value
1796 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1797 except (IOError, os.error), reason:
1798 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1801 def do_export_labels(self):
1802 labels = self.wallet.labels
1804 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1806 with open(fileName, 'w+') as f:
1807 json.dump(labels, f)
1808 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1809 except (IOError, os.error), reason:
1810 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1813 def do_export_history(self):
1814 from gui_lite import csv_transaction
1815 csv_transaction(self.wallet)
1819 def do_import_privkey(self, password):
1820 if not self.wallet.imported_keys:
1821 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1822 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1823 + _('In addition, when you send bitcoins from one of your imported addresses, the "change" will be sent to an address derived from your seed, unless you disabled this option.') + '<p>' \
1824 + _('Are you sure you understand what you are doing?'), 3, 4)
1827 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1830 text = str(text).split()
1835 addr = self.wallet.import_key(key, password)
1836 except BaseException as e:
1842 addrlist.append(addr)
1844 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1846 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1847 self.update_receive_tab()
1848 self.update_history_tab()
1851 def settings_dialog(self):
1853 d.setWindowTitle(_('Electrum Settings'))
1855 vbox = QVBoxLayout()
1857 tabs = QTabWidget(self)
1858 self.settings_tab = tabs
1859 vbox.addWidget(tabs)
1862 grid_ui = QGridLayout(tab1)
1863 grid_ui.setColumnStretch(0,1)
1864 tabs.addTab(tab1, _('Display') )
1866 nz_label = QLabel(_('Display zeros'))
1867 grid_ui.addWidget(nz_label, 0, 0)
1869 nz_e.setText("%d"% self.wallet.num_zeros)
1870 grid_ui.addWidget(nz_e, 0, 1)
1871 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1872 grid_ui.addWidget(HelpButton(msg), 0, 2)
1873 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1874 if not self.config.is_modifiable('num_zeros'):
1875 for w in [nz_e, nz_label]: w.setEnabled(False)
1877 lang_label=QLabel(_('Language') + ':')
1878 grid_ui.addWidget(lang_label, 1, 0)
1879 lang_combo = QComboBox()
1880 from i18n import languages
1881 lang_combo.addItems(languages.values())
1883 index = languages.keys().index(self.config.get("language",''))
1886 lang_combo.setCurrentIndex(index)
1887 grid_ui.addWidget(lang_combo, 1, 1)
1888 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1889 if not self.config.is_modifiable('language'):
1890 for w in [lang_combo, lang_label]: w.setEnabled(False)
1892 currencies = self.exchanger.get_currencies()
1893 currencies.insert(0, "None")
1895 cur_label=QLabel(_('Currency') + ':')
1896 grid_ui.addWidget(cur_label , 2, 0)
1897 cur_combo = QComboBox()
1898 cur_combo.addItems(currencies)
1900 index = currencies.index(self.config.get('currency', "None"))
1903 cur_combo.setCurrentIndex(index)
1904 grid_ui.addWidget(cur_combo, 2, 1)
1905 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1907 view_label=QLabel(_('Receive Tab') + ':')
1908 grid_ui.addWidget(view_label , 3, 0)
1909 view_combo = QComboBox()
1910 view_combo.addItems([_('Simple'), _('Advanced')])
1911 view_combo.setCurrentIndex(self.expert_mode)
1912 grid_ui.addWidget(view_combo, 3, 1)
1913 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1914 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1915 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1917 grid_ui.addWidget(HelpButton(hh), 3, 2)
1918 grid_ui.setRowStretch(4,1)
1922 grid_wallet = QGridLayout(tab2)
1923 grid_wallet.setColumnStretch(0,1)
1924 tabs.addTab(tab2, _('Wallet') )
1926 fee_label = QLabel(_('Transaction fee'))
1927 grid_wallet.addWidget(fee_label, 0, 0)
1929 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1930 grid_wallet.addWidget(fee_e, 0, 2)
1931 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1932 + _('Recommended value') + ': 0.001'
1933 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1934 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1935 if not self.config.is_modifiable('fee'):
1936 for w in [fee_e, fee_label]: w.setEnabled(False)
1938 usechange_label = QLabel(_('Use change addresses'))
1939 grid_wallet.addWidget(usechange_label, 1, 0)
1940 usechange_combo = QComboBox()
1941 usechange_combo.addItems([_('Yes'), _('No')])
1942 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1943 grid_wallet.addWidget(usechange_combo, 1, 2)
1944 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1945 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1947 gap_label = QLabel(_('Gap limit'))
1948 grid_wallet.addWidget(gap_label, 2, 0)
1950 gap_e.setText("%d"% self.wallet.gap_limit)
1951 grid_wallet.addWidget(gap_e, 2, 2)
1952 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1953 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1954 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1955 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1956 + _('Warning') + ': ' \
1957 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1958 + _('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'
1959 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1960 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1961 if not self.config.is_modifiable('gap_limit'):
1962 for w in [gap_e, gap_label]: w.setEnabled(False)
1964 grid_wallet.setRowStretch(3,1)
1969 grid_io = QGridLayout(tab3)
1970 grid_io.setColumnStretch(0,1)
1971 tabs.addTab(tab3, _('Import/Export') )
1973 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1974 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1975 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1976 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1978 grid_io.addWidget(QLabel(_('History')), 2, 0)
1979 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1980 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1982 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1984 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1985 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1986 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1988 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1989 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1990 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1991 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1992 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1995 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1996 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1997 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1998 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2000 grid_io.setRowStretch(5,1)
2005 tab5 = QScrollArea()
2006 grid_plugins = QGridLayout(tab5)
2007 grid_plugins.setColumnStretch(0,1)
2008 tabs.addTab(tab5, _('Plugins') )
2009 def mk_toggle(cb, p):
2010 return lambda: cb.setChecked(p.toggle())
2011 for i, p in enumerate(self.plugins):
2013 name, description = p.get_info()
2014 cb = QCheckBox(name)
2015 cb.setDisabled(not p.is_available())
2016 cb.setChecked(p.is_enabled())
2017 cb.clicked.connect(mk_toggle(cb,p))
2018 grid_plugins.addWidget(cb, i, 0)
2019 if p.requires_settings():
2020 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2021 grid_plugins.addWidget(HelpButton(description), i, 2)
2023 print_msg("Error: cannot display plugin", p)
2024 traceback.print_exc(file=sys.stdout)
2025 grid_plugins.setRowStretch(i+1,1)
2027 self.run_hook('create_settings_tab', tabs)
2029 vbox.addLayout(ok_cancel_buttons(d))
2033 if not d.exec_(): return
2035 fee = unicode(fee_e.text())
2037 fee = int( 100000000 * Decimal(fee) )
2039 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2042 if self.wallet.fee != fee:
2043 self.wallet.fee = fee
2046 nz = unicode(nz_e.text())
2051 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2054 if self.wallet.num_zeros != nz:
2055 self.wallet.num_zeros = nz
2056 self.config.set_key('num_zeros', nz, True)
2057 self.update_history_tab()
2058 self.update_receive_tab()
2060 usechange_result = usechange_combo.currentIndex() == 0
2061 if self.wallet.use_change != usechange_result:
2062 self.wallet.use_change = usechange_result
2063 self.config.set_key('use_change', self.wallet.use_change, True)
2066 n = int(gap_e.text())
2068 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2071 if self.wallet.gap_limit != n:
2072 r = self.wallet.change_gap_limit(n)
2074 self.update_receive_tab()
2075 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2077 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2079 need_restart = False
2081 lang_request = languages.keys()[lang_combo.currentIndex()]
2082 if lang_request != self.config.get('language'):
2083 self.config.set_key("language", lang_request, True)
2086 cur_request = str(currencies[cur_combo.currentIndex()])
2087 if cur_request != self.config.get('currency', "None"):
2088 self.config.set_key('currency', cur_request, True)
2089 self.update_wallet()
2091 self.run_hook('close_settings_dialog')
2094 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2096 self.receive_tab_set_mode(view_combo.currentIndex())
2100 def network_dialog(wallet, parent=None):
2101 interface = wallet.interface
2103 if interface.is_connected:
2104 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2106 status = _("Not connected")
2107 server = interface.server
2110 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2111 server = interface.server
2113 plist, servers_list = interface.get_servers_list()
2117 d.setWindowTitle(_('Server'))
2118 d.setMinimumSize(375, 20)
2120 vbox = QVBoxLayout()
2123 hbox = QHBoxLayout()
2125 l.setPixmap(QPixmap(":icons/network.png"))
2128 hbox.addWidget(QLabel(status))
2130 vbox.addLayout(hbox)
2134 grid = QGridLayout()
2136 vbox.addLayout(grid)
2139 server_protocol = QComboBox()
2140 server_host = QLineEdit()
2141 server_host.setFixedWidth(200)
2142 server_port = QLineEdit()
2143 server_port.setFixedWidth(60)
2145 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2146 protocol_letters = 'thsg'
2147 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2148 server_protocol.addItems(protocol_names)
2150 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2151 grid.addWidget(server_protocol, 0, 1)
2152 grid.addWidget(server_host, 0, 2)
2153 grid.addWidget(server_port, 0, 3)
2155 def change_protocol(p):
2156 protocol = protocol_letters[p]
2157 host = unicode(server_host.text())
2158 pp = plist.get(host,DEFAULT_PORTS)
2159 if protocol not in pp.keys():
2160 protocol = pp.keys()[0]
2162 server_host.setText( host )
2163 server_port.setText( port )
2165 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2167 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2168 servers_list_widget = QTreeWidget(parent)
2169 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2170 servers_list_widget.setMaximumHeight(150)
2171 servers_list_widget.setColumnWidth(0, 240)
2172 for _host in servers_list.keys():
2173 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2174 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2176 def change_server(host, protocol=None):
2177 pp = plist.get(host,DEFAULT_PORTS)
2179 port = pp.get(protocol)
2180 if not port: protocol = None
2183 if 't' in pp.keys():
2185 port = pp.get(protocol)
2187 protocol = pp.keys()[0]
2188 port = pp.get(protocol)
2190 server_host.setText( host )
2191 server_port.setText( port )
2192 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2194 if not plist: return
2195 for p in protocol_letters:
2196 i = protocol_letters.index(p)
2197 j = server_protocol.model().index(i,0)
2198 if p not in pp.keys() and interface.is_connected:
2199 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2201 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2205 host, port, protocol = server.split(':')
2206 change_server(host,protocol)
2208 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2209 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2211 if not wallet.config.is_modifiable('server'):
2212 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2215 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2216 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2217 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2218 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2221 proxy_mode = QComboBox()
2222 proxy_host = QLineEdit()
2223 proxy_host.setFixedWidth(200)
2224 proxy_port = QLineEdit()
2225 proxy_port.setFixedWidth(60)
2226 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2228 def check_for_disable(index = False):
2229 if proxy_mode.currentText() != 'NONE':
2230 proxy_host.setEnabled(True)
2231 proxy_port.setEnabled(True)
2233 proxy_host.setEnabled(False)
2234 proxy_port.setEnabled(False)
2237 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2239 if not wallet.config.is_modifiable('proxy'):
2240 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2242 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2243 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2244 proxy_host.setText(proxy_config.get("host"))
2245 proxy_port.setText(proxy_config.get("port"))
2247 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2248 grid.addWidget(proxy_mode, 2, 1)
2249 grid.addWidget(proxy_host, 2, 2)
2250 grid.addWidget(proxy_port, 2, 3)
2253 vbox.addLayout(ok_cancel_buttons(d))
2256 if not d.exec_(): return
2258 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2259 if proxy_mode.currentText() != 'NONE':
2260 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2264 wallet.config.set_key("proxy", proxy, True)
2265 wallet.config.set_key("server", server, True)
2266 interface.set_server(server, proxy)
2267 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2270 def closeEvent(self, event):
2272 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2273 self.save_column_widths()
2274 self.config.set_key("column-widths", self.column_widths, True)
2275 self.config.set_key("console-history",self.console.history[-50:])
2281 def __init__(self, wallet, config, app=None):
2282 self.wallet = wallet
2283 self.config = config
2285 self.app = QApplication(sys.argv)
2288 def restore_or_create(self):
2289 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2290 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2291 if r==2: return None
2292 return 'restore' if r==1 else 'create'
2294 def seed_dialog(self):
2295 return ElectrumWindow.seed_dialog( self.wallet )
2297 def network_dialog(self):
2298 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2301 def show_seed(self):
2302 ElectrumWindow.show_seed(self.wallet.seed)
2305 def password_dialog(self):
2306 if self.wallet.seed:
2307 ElectrumWindow.change_password_dialog(self.wallet)
2310 def restore_wallet(self):
2311 wallet = self.wallet
2312 # wait until we are connected, because the user might have selected another server
2313 if not wallet.interface.is_connected:
2314 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2315 waiting_dialog(waiting)
2317 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2318 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2320 wallet.set_up_to_date(False)
2321 wallet.interface.poke('synchronizer')
2322 waiting_dialog(waiting)
2323 if wallet.is_found():
2324 print_error( "Recovery successful" )
2326 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2333 w = ElectrumWindow(self.wallet, self.config)
2334 if url: w.set_url(url)