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.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
290 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
291 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
292 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
294 self.expert_mode = config.get('classic_expert_mode', False)
296 set_language(config.get('language'))
298 self.funds_error = False
299 self.completions = QStringListModel()
301 self.tabs = tabs = QTabWidget(self)
302 self.column_widths = self.config.get("column-widths", default_column_widths )
303 tabs.addTab(self.create_history_tab(), _('History') )
304 tabs.addTab(self.create_send_tab(), _('Send') )
305 tabs.addTab(self.create_receive_tab(), _('Receive') )
306 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
307 tabs.addTab(self.create_console_tab(), _('Console') )
308 tabs.setMinimumSize(600, 400)
309 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
310 self.setCentralWidget(tabs)
311 self.create_status_bar()
313 g = self.config.get("winpos-qt",[100, 100, 840, 400])
314 self.setGeometry(g[0], g[1], g[2], g[3])
315 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
316 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
317 self.setWindowTitle( title )
319 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
320 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
321 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
322 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
324 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
325 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
326 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
327 self.history_list.setFocus(True)
329 self.exchanger = exchange_rate.Exchanger(self)
330 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
332 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
333 if platform.system() == 'Windows':
334 n = 3 if self.wallet.seed else 2
335 tabs.setCurrentIndex (n)
336 tabs.setCurrentIndex (0)
338 # set initial message
339 self.console.showMessage(self.wallet.banner)
341 # plugins that need to change the GUI do it here
342 self.run_hook('init_gui')
346 def init_plugins(self):
347 import imp, pkgutil, __builtin__
348 if __builtin__.use_local_modules:
349 fp, pathname, description = imp.find_module('plugins')
350 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
351 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
352 imp.load_module('electrum_plugins', fp, pathname, description)
353 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
355 import electrum_plugins
356 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
357 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
362 self.plugins.append( p.Plugin(self) )
364 print_msg("Error:cannot initialize plugin",p)
365 traceback.print_exc(file=sys.stdout)
368 def run_hook(self, name, *args):
369 for p in self.plugins:
370 if not p.is_enabled():
380 def set_label(self, name, text = None):
382 old_text = self.wallet.labels.get(name)
385 self.wallet.labels[name] = text
389 self.wallet.labels.pop(name)
391 self.run_hook('set_label', name, text, changed)
395 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
396 def getOpenFileName(self, title, filter = None):
397 directory = self.config.get('io_dir', os.path.expanduser('~'))
398 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
399 if fileName and directory != os.path.dirname(fileName):
400 self.config.set_key('io_dir', os.path.dirname(fileName), True)
403 def getSaveFileName(self, title, filename, filter = None):
404 directory = self.config.get('io_dir', os.path.expanduser('~'))
405 path = os.path.join( directory, filename )
406 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
407 if fileName and directory != os.path.dirname(fileName):
408 self.config.set_key('io_dir', os.path.dirname(fileName), True)
414 QMainWindow.close(self)
415 self.run_hook('close_main_window')
417 def connect_slots(self, sender):
418 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
419 self.previous_payto_e=''
421 def timer_actions(self):
422 self.run_hook('timer_actions')
424 def update_status(self):
425 if self.wallet.interface and self.wallet.interface.is_connected:
426 if not self.wallet.up_to_date:
427 text = _("Synchronizing...")
428 icon = QIcon(":icons/status_waiting.png")
430 c, u = self.wallet.get_balance()
431 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
432 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
433 text += self.create_quote_text(Decimal(c+u)/100000000)
434 icon = QIcon(":icons/status_connected.png")
436 text = _("Not connected")
437 icon = QIcon(":icons/status_disconnected.png")
439 self.status_text = text
440 self.statusBar().showMessage(text)
441 self.status_button.setIcon( icon )
443 def update_wallet(self):
445 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
446 self.update_history_tab()
447 self.update_receive_tab()
448 self.update_contacts_tab()
449 self.update_completions()
452 def create_quote_text(self, btc_balance):
453 quote_currency = self.config.get("currency", "None")
454 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
455 if quote_balance is None:
458 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
461 def create_history_tab(self):
462 self.history_list = l = MyTreeWidget(self)
464 for i,width in enumerate(self.column_widths['history']):
465 l.setColumnWidth(i, width)
466 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
467 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
468 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
470 l.setContextMenuPolicy(Qt.CustomContextMenu)
471 l.customContextMenuRequested.connect(self.create_history_menu)
475 def create_history_menu(self, position):
476 self.history_list.selectedIndexes()
477 item = self.history_list.currentItem()
479 tx_hash = str(item.data(0, Qt.UserRole).toString())
480 if not tx_hash: return
482 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
483 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
484 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
485 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
488 def show_tx_details(self, tx):
489 dialog = QDialog(self)
491 dialog.setWindowTitle(_("Transaction Details"))
493 dialog.setLayout(vbox)
494 dialog.setMinimumSize(600,300)
497 if tx_hash in self.wallet.transactions.keys():
498 is_mine, v, fee = self.wallet.get_tx_value(tx)
499 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
501 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
507 vbox.addWidget(QLabel("Transaction ID:"))
508 e = QLineEdit(tx_hash)
512 vbox.addWidget(QLabel("Date: %s"%time_str))
513 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
516 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
517 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
519 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
520 vbox.addWidget(QLabel("Transaction fee: unknown"))
522 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
524 vbox.addWidget( self.generate_transaction_information_widget(tx) )
526 ok_button = QPushButton(_("Close"))
527 ok_button.setDefault(True)
528 ok_button.clicked.connect(dialog.accept)
532 hbox.addWidget(ok_button)
536 def tx_label_clicked(self, item, column):
537 if column==2 and item.isSelected():
539 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
540 self.history_list.editItem( item, column )
541 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
544 def tx_label_changed(self, item, column):
548 tx_hash = str(item.data(0, Qt.UserRole).toString())
549 tx = self.wallet.transactions.get(tx_hash)
550 text = unicode( item.text(2) )
551 self.set_label(tx_hash, text)
553 item.setForeground(2, QBrush(QColor('black')))
555 text = self.wallet.get_default_label(tx_hash)
556 item.setText(2, text)
557 item.setForeground(2, QBrush(QColor('gray')))
561 def edit_label(self, is_recv):
562 l = self.receive_list if is_recv else self.contacts_list
563 item = l.currentItem()
564 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
565 l.editItem( item, 1 )
566 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
570 def address_label_clicked(self, item, column, l, column_addr, column_label):
571 if column == column_label and item.isSelected():
572 is_editable = item.data(0, 32).toBool()
575 addr = unicode( item.text(column_addr) )
576 label = unicode( item.text(column_label) )
577 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
578 l.editItem( item, column )
579 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
582 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)
1051 self.run_hook('update_receive_item', address, item)
1053 c, u = self.wallet.get_addr_balance(address)
1054 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1055 item.setData(2,0,balance)
1057 if self.expert_mode:
1058 if address in self.wallet.frozen_addresses:
1059 item.setBackgroundColor(0, QColor('lightblue'))
1060 elif address in self.wallet.prioritized_addresses:
1061 item.setBackgroundColor(0, QColor('lightgreen'))
1064 def update_receive_tab(self):
1065 l = self.receive_list
1068 l.setColumnHidden(2, not self.expert_mode)
1069 l.setColumnHidden(3, not self.expert_mode)
1070 if not self.expert_mode:
1071 width = self.column_widths['receive'][0][0]
1072 l.setColumnWidth(0, width)
1074 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1075 l.setColumnWidth(i, width)
1078 for k, account in self.wallet.accounts.items():
1079 name = account.get('name',str(k))
1080 c,u = self.wallet.get_account_balance(k)
1081 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1082 l.addTopLevelItem(account_item)
1083 account_item.setExpanded(True)
1086 for is_change in [0,1]:
1087 name = "Receiving" if not is_change else "Change"
1088 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1089 account_item.addChild(seq_item)
1090 if not is_change: seq_item.setExpanded(True)
1094 for address in account[is_change]:
1095 h = self.wallet.history.get(address,[])
1099 if gap > self.wallet.gap_limit:
1104 num_tx = '*' if h == ['*'] else "%d"%len(h)
1105 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1106 self.update_receive_item(item)
1108 item.setBackgroundColor(1, QColor('red'))
1109 seq_item.addChild(item)
1111 if self.wallet.imported_keys:
1112 c,u = self.wallet.get_imported_balance()
1113 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1114 l.addTopLevelItem(account_item)
1115 account_item.setExpanded(True)
1116 for address in self.wallet.imported_keys.keys():
1117 item = QTreeWidgetItem( [ address, '', '', ''] )
1118 self.update_receive_item(item)
1119 account_item.addChild(item)
1122 # we use column 1 because column 0 may be hidden
1123 l.setCurrentItem(l.topLevelItem(0),1)
1126 def update_contacts_tab(self):
1128 l = self.contacts_list
1131 for address in self.wallet.addressbook:
1132 label = self.wallet.labels.get(address,'')
1133 n = self.wallet.get_num_tx(address)
1134 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1135 item.setFont(0, QFont(MONOSPACE_FONT))
1136 # 32 = label can be edited (bool)
1137 item.setData(0,32, True)
1139 item.setData(0,33, address)
1140 l.addTopLevelItem(item)
1142 self.run_hook('update_contacts_tab', l)
1143 l.setCurrentItem(l.topLevelItem(0))
1147 def create_console_tab(self):
1148 from qt_console import Console
1149 self.console = console = Console()
1150 self.console.history = self.config.get("console-history",[])
1151 self.console.history_index = len(self.console.history)
1153 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1154 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1156 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1158 def mkfunc(f, method):
1159 return lambda *args: apply( f, (method, args, self.password_dialog ))
1161 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1162 methods[m] = mkfunc(c._run, m)
1164 console.updateNamespace(methods)
1168 def create_status_bar(self):
1169 self.status_text = ""
1171 sb.setFixedHeight(35)
1172 qtVersion = qVersion()
1174 update_notification = UpdateLabel(self.config)
1175 if(update_notification.new_version):
1176 sb.addPermanentWidget(update_notification)
1178 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1179 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1180 if self.wallet.seed:
1181 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1182 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1183 if self.wallet.seed:
1184 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1185 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1186 sb.addPermanentWidget( self.status_button )
1188 self.setStatusBar(sb)
1192 self.config.set_key('gui', 'lite', True)
1195 self.lite.mini.show()
1197 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1198 self.lite.main(None)
1200 def new_contact_dialog(self):
1201 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1202 address = unicode(text)
1204 if is_valid(address):
1205 self.wallet.addressbook.append(address)
1207 self.update_contacts_tab()
1208 self.update_history_tab()
1209 self.update_completions()
1211 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1213 def show_master_public_key(self):
1214 dialog = QDialog(self)
1216 dialog.setWindowTitle(_("Master Public Key"))
1218 main_text = QTextEdit()
1219 main_text.setText(self.wallet.get_master_public_key())
1220 main_text.setReadOnly(True)
1221 main_text.setMaximumHeight(170)
1222 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1224 ok_button = QPushButton(_("OK"))
1225 ok_button.setDefault(True)
1226 ok_button.clicked.connect(dialog.accept)
1228 main_layout = QGridLayout()
1229 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1231 main_layout.addWidget(main_text, 1, 0)
1232 main_layout.addWidget(qrw, 1, 1 )
1234 vbox = QVBoxLayout()
1235 vbox.addLayout(main_layout)
1236 hbox = QHBoxLayout()
1238 hbox.addWidget(ok_button)
1239 vbox.addLayout(hbox)
1241 dialog.setLayout(vbox)
1246 def show_seed_dialog(self, password):
1247 if not self.wallet.seed:
1248 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1251 seed = self.wallet.decode_seed(password)
1253 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1255 self.show_seed(seed, self)
1259 def show_seed(self, seed, parent=None):
1260 dialog = QDialog(parent)
1262 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1264 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1266 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1268 seed_text = QTextEdit(brainwallet)
1269 seed_text.setReadOnly(True)
1270 seed_text.setMaximumHeight(130)
1272 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1273 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1274 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1275 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1276 label2 = QLabel(msg2)
1277 label2.setWordWrap(True)
1280 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1281 logo.setMaximumWidth(60)
1283 qrw = QRCodeWidget(seed)
1285 ok_button = QPushButton(_("OK"))
1286 ok_button.setDefault(True)
1287 ok_button.clicked.connect(dialog.accept)
1289 grid = QGridLayout()
1290 #main_layout.addWidget(logo, 0, 0)
1292 grid.addWidget(logo, 0, 0)
1293 grid.addWidget(label1, 0, 1)
1295 grid.addWidget(seed_text, 1, 0, 1, 2)
1297 grid.addWidget(qrw, 0, 2, 2, 1)
1299 vbox = QVBoxLayout()
1300 vbox.addLayout(grid)
1301 vbox.addWidget(label2)
1303 hbox = QHBoxLayout()
1305 hbox.addWidget(ok_button)
1306 vbox.addLayout(hbox)
1308 dialog.setLayout(vbox)
1311 def show_qrcode(self, data, title = "QR code"):
1315 d.setWindowTitle(title)
1316 d.setMinimumSize(270, 300)
1317 vbox = QVBoxLayout()
1318 qrw = QRCodeWidget(data)
1319 vbox.addWidget(qrw, 1)
1320 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1321 hbox = QHBoxLayout()
1325 filename = "qrcode.bmp"
1326 bmp.save_qrcode(qrw.qr, filename)
1327 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1329 b = QPushButton(_("Save"))
1331 b.clicked.connect(print_qr)
1333 b = QPushButton(_("Close"))
1335 b.clicked.connect(d.accept)
1338 vbox.addLayout(hbox)
1343 def do_protect(self, func, args):
1344 if self.wallet.use_encryption:
1345 password = self.password_dialog()
1351 if args != (False,):
1352 args = (self,) + args + (password,)
1354 args = (self,password)
1359 def show_private_key(self, address, password):
1360 if not address: return
1362 pk = self.wallet.get_private_key(address, password)
1363 except BaseException, e:
1364 self.show_message(str(e))
1366 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1370 def do_sign(self, address, message, signature, password):
1372 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1373 signature.setText(sig)
1374 except BaseException, e:
1375 self.show_message(str(e))
1377 def sign_message(self, address):
1378 if not address: return
1381 d.setWindowTitle(_('Sign Message'))
1382 d.setMinimumSize(410, 290)
1384 tab_widget = QTabWidget()
1386 layout = QGridLayout(tab)
1388 sign_address = QLineEdit()
1390 sign_address.setText(address)
1391 layout.addWidget(QLabel(_('Address')), 1, 0)
1392 layout.addWidget(sign_address, 1, 1)
1394 sign_message = QTextEdit()
1395 layout.addWidget(QLabel(_('Message')), 2, 0)
1396 layout.addWidget(sign_message, 2, 1)
1397 layout.setRowStretch(2,3)
1399 sign_signature = QTextEdit()
1400 layout.addWidget(QLabel(_('Signature')), 3, 0)
1401 layout.addWidget(sign_signature, 3, 1)
1402 layout.setRowStretch(3,1)
1405 hbox = QHBoxLayout()
1406 b = QPushButton(_("Sign"))
1408 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1409 b = QPushButton(_("Close"))
1410 b.clicked.connect(d.accept)
1412 layout.addLayout(hbox, 4, 1)
1413 tab_widget.addTab(tab, _("Sign"))
1417 layout = QGridLayout(tab)
1419 verify_address = QLineEdit()
1420 layout.addWidget(QLabel(_('Address')), 1, 0)
1421 layout.addWidget(verify_address, 1, 1)
1423 verify_message = QTextEdit()
1424 layout.addWidget(QLabel(_('Message')), 2, 0)
1425 layout.addWidget(verify_message, 2, 1)
1426 layout.setRowStretch(2,3)
1428 verify_signature = QTextEdit()
1429 layout.addWidget(QLabel(_('Signature')), 3, 0)
1430 layout.addWidget(verify_signature, 3, 1)
1431 layout.setRowStretch(3,1)
1435 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1436 self.show_message(_("Signature verified"))
1437 except BaseException, e:
1438 self.show_message(str(e))
1441 hbox = QHBoxLayout()
1442 b = QPushButton(_("Verify"))
1443 b.clicked.connect(do_verify)
1445 b = QPushButton(_("Close"))
1446 b.clicked.connect(d.accept)
1448 layout.addLayout(hbox, 4, 1)
1449 tab_widget.addTab(tab, _("Verify"))
1451 vbox = QVBoxLayout()
1452 vbox.addWidget(tab_widget)
1459 def question(self, msg):
1460 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1462 def show_message(self, msg):
1463 QMessageBox.information(self, _('Message'), msg, _('OK'))
1465 def password_dialog(self ):
1472 vbox = QVBoxLayout()
1473 msg = _('Please enter your password')
1474 vbox.addWidget(QLabel(msg))
1476 grid = QGridLayout()
1478 grid.addWidget(QLabel(_('Password')), 1, 0)
1479 grid.addWidget(pw, 1, 1)
1480 vbox.addLayout(grid)
1482 vbox.addLayout(ok_cancel_buttons(d))
1485 self.run_hook('password_dialog', pw, grid, 1)
1486 if not d.exec_(): return
1487 return unicode(pw.text())
1494 def change_password_dialog( wallet, parent=None ):
1497 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1505 new_pw = QLineEdit()
1506 new_pw.setEchoMode(2)
1507 conf_pw = QLineEdit()
1508 conf_pw.setEchoMode(2)
1510 vbox = QVBoxLayout()
1512 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1513 +_('To disable wallet encryption, enter an empty new password.')) \
1514 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1516 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1517 +_("Leave these fields empty if you want to disable encryption.")
1518 vbox.addWidget(QLabel(msg))
1520 grid = QGridLayout()
1523 if wallet.use_encryption:
1524 grid.addWidget(QLabel(_('Password')), 1, 0)
1525 grid.addWidget(pw, 1, 1)
1527 grid.addWidget(QLabel(_('New Password')), 2, 0)
1528 grid.addWidget(new_pw, 2, 1)
1530 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1531 grid.addWidget(conf_pw, 3, 1)
1532 vbox.addLayout(grid)
1534 vbox.addLayout(ok_cancel_buttons(d))
1537 if not d.exec_(): return
1539 password = unicode(pw.text()) if wallet.use_encryption else None
1540 new_password = unicode(new_pw.text())
1541 new_password2 = unicode(conf_pw.text())
1544 seed = wallet.decode_seed(password)
1546 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1549 if new_password != new_password2:
1550 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1551 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1553 wallet.update_password(seed, password, new_password)
1556 def seed_dialog(wallet, parent=None):
1560 vbox = QVBoxLayout()
1561 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1562 vbox.addWidget(QLabel(msg))
1564 grid = QGridLayout()
1567 seed_e = QLineEdit()
1568 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1569 grid.addWidget(seed_e, 1, 1)
1570 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1574 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1575 grid.addWidget(gap_e, 2, 1)
1576 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1577 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1578 vbox.addLayout(grid)
1580 vbox.addLayout(ok_cancel_buttons(d))
1583 if not d.exec_(): return
1586 gap = int(unicode(gap_e.text()))
1588 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1592 seed = str(seed_e.text())
1595 print_error("Warning: Not hex, trying decode")
1597 seed = mnemonic.mn_decode( seed.split(' ') )
1599 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1603 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1608 def generate_transaction_information_widget(self, tx):
1609 tabs = QTabWidget(self)
1612 grid_ui = QGridLayout(tab1)
1613 grid_ui.setColumnStretch(0,1)
1614 tabs.addTab(tab1, _('Outputs') )
1616 tree_widget = MyTreeWidget(self)
1617 tree_widget.setColumnCount(2)
1618 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1619 tree_widget.setColumnWidth(0, 300)
1620 tree_widget.setColumnWidth(1, 50)
1622 for address, value in tx.outputs:
1623 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1624 tree_widget.addTopLevelItem(item)
1626 tree_widget.setMaximumHeight(100)
1628 grid_ui.addWidget(tree_widget)
1631 grid_ui = QGridLayout(tab2)
1632 grid_ui.setColumnStretch(0,1)
1633 tabs.addTab(tab2, _('Inputs') )
1635 tree_widget = MyTreeWidget(self)
1636 tree_widget.setColumnCount(2)
1637 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1639 for input_line in tx.inputs:
1640 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1641 tree_widget.addTopLevelItem(item)
1643 tree_widget.setMaximumHeight(100)
1645 grid_ui.addWidget(tree_widget)
1649 def tx_dict_from_text(self, txt):
1651 tx_dict = json.loads(str(txt))
1652 assert "hex" in tx_dict.keys()
1653 assert "complete" in tx_dict.keys()
1654 if not tx_dict["complete"]:
1655 assert "input_info" in tx_dict.keys()
1657 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1662 def read_tx_from_file(self):
1663 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1667 with open(fileName, "r") as f:
1668 file_content = f.read()
1669 except (ValueError, IOError, os.error), reason:
1670 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1672 return self.tx_dict_from_text(file_content)
1676 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1678 self.wallet.signrawtransaction(tx, input_info, [], password)
1680 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1682 with open(fileName, "w+") as f:
1683 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1684 self.show_message(_("Transaction saved successfully"))
1687 except BaseException, e:
1688 self.show_message(str(e))
1691 def send_raw_transaction(self, raw_tx, dialog = ""):
1692 result, result_message = self.wallet.sendtx( raw_tx )
1694 self.show_message("Transaction successfully sent: %s" % (result_message))
1698 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1700 def do_process_from_text(self):
1701 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1704 tx_dict = self.tx_dict_from_text(text)
1706 self.create_process_transaction_window(tx_dict)
1708 def do_process_from_file(self):
1709 tx_dict = self.read_tx_from_file()
1711 self.create_process_transaction_window(tx_dict)
1713 def create_process_transaction_window(self, tx_dict):
1714 tx = Transaction(tx_dict["hex"])
1716 dialog = QDialog(self)
1717 dialog.setMinimumWidth(500)
1718 dialog.setWindowTitle(_('Process raw transaction'))
1724 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1725 l.addWidget(QLabel(_("Actions")), 4,0)
1727 if tx_dict["complete"] == False:
1728 l.addWidget(QLabel(_("Unsigned")), 3,1)
1729 if self.wallet.seed :
1730 b = QPushButton("Sign transaction")
1731 input_info = json.loads(tx_dict["input_info"])
1732 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1733 l.addWidget(b, 4, 1)
1735 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1737 l.addWidget(QLabel(_("Signed")), 3,1)
1738 b = QPushButton("Broadcast transaction")
1739 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1742 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1743 cancelButton = QPushButton(_("Cancel"))
1744 cancelButton.clicked.connect(lambda: dialog.done(0))
1745 l.addWidget(cancelButton, 4,2)
1751 def do_export_privkeys(self, password):
1752 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.")))
1755 select_export = _('Select file to export your private keys to')
1756 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1758 with open(fileName, "w+") as csvfile:
1759 transaction = csv.writer(csvfile)
1760 transaction.writerow(["address", "private_key"])
1763 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1764 transaction.writerow(["%34s"%addr,pk])
1766 self.show_message(_("Private keys exported."))
1768 except (IOError, os.error), reason:
1769 export_error_label = _("Electrum was unable to produce a private key-export.")
1770 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1772 except BaseException, e:
1773 self.show_message(str(e))
1777 def do_import_labels(self):
1778 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1779 if not labelsFile: return
1781 f = open(labelsFile, 'r')
1784 for key, value in json.loads(data).items():
1785 self.wallet.labels[key] = value
1787 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1788 except (IOError, os.error), reason:
1789 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1792 def do_export_labels(self):
1793 labels = self.wallet.labels
1795 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1797 with open(fileName, 'w+') as f:
1798 json.dump(labels, f)
1799 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1800 except (IOError, os.error), reason:
1801 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1804 def do_export_history(self):
1805 from gui_lite import csv_transaction
1806 csv_transaction(self.wallet)
1810 def do_import_privkey(self, password):
1811 if not self.wallet.imported_keys:
1812 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1813 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1814 + _('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>' \
1815 + _('Are you sure you understand what you are doing?'), 3, 4)
1818 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1821 text = str(text).split()
1826 addr = self.wallet.import_key(key, password)
1827 except BaseException as e:
1833 addrlist.append(addr)
1835 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1837 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1838 self.update_receive_tab()
1839 self.update_history_tab()
1842 def settings_dialog(self):
1844 d.setWindowTitle(_('Electrum Settings'))
1846 vbox = QVBoxLayout()
1848 tabs = QTabWidget(self)
1849 vbox.addWidget(tabs)
1852 grid_ui = QGridLayout(tab1)
1853 grid_ui.setColumnStretch(0,1)
1854 tabs.addTab(tab1, _('Display') )
1856 nz_label = QLabel(_('Display zeros'))
1857 grid_ui.addWidget(nz_label, 0, 0)
1859 nz_e.setText("%d"% self.wallet.num_zeros)
1860 grid_ui.addWidget(nz_e, 0, 1)
1861 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1862 grid_ui.addWidget(HelpButton(msg), 0, 2)
1863 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1864 if not self.config.is_modifiable('num_zeros'):
1865 for w in [nz_e, nz_label]: w.setEnabled(False)
1867 lang_label=QLabel(_('Language') + ':')
1868 grid_ui.addWidget(lang_label, 1, 0)
1869 lang_combo = QComboBox()
1870 from i18n import languages
1871 lang_combo.addItems(languages.values())
1873 index = languages.keys().index(self.config.get("language",''))
1876 lang_combo.setCurrentIndex(index)
1877 grid_ui.addWidget(lang_combo, 1, 1)
1878 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1879 if not self.config.is_modifiable('language'):
1880 for w in [lang_combo, lang_label]: w.setEnabled(False)
1882 currencies = self.exchanger.get_currencies()
1883 currencies.insert(0, "None")
1885 cur_label=QLabel(_('Currency') + ':')
1886 grid_ui.addWidget(cur_label , 2, 0)
1887 cur_combo = QComboBox()
1888 cur_combo.addItems(currencies)
1890 index = currencies.index(self.config.get('currency', "None"))
1893 cur_combo.setCurrentIndex(index)
1894 grid_ui.addWidget(cur_combo, 2, 1)
1895 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1897 view_label=QLabel(_('Receive Tab') + ':')
1898 grid_ui.addWidget(view_label , 3, 0)
1899 view_combo = QComboBox()
1900 view_combo.addItems([_('Simple'), _('Advanced')])
1901 view_combo.setCurrentIndex(self.expert_mode)
1902 grid_ui.addWidget(view_combo, 3, 1)
1903 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1904 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1905 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1907 grid_ui.addWidget(HelpButton(hh), 3, 2)
1908 grid_ui.setRowStretch(4,1)
1912 grid_wallet = QGridLayout(tab2)
1913 grid_wallet.setColumnStretch(0,1)
1914 tabs.addTab(tab2, _('Wallet') )
1916 fee_label = QLabel(_('Transaction fee'))
1917 grid_wallet.addWidget(fee_label, 0, 0)
1919 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1920 grid_wallet.addWidget(fee_e, 0, 2)
1921 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1922 + _('Recommended value') + ': 0.001'
1923 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1924 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1925 if not self.config.is_modifiable('fee'):
1926 for w in [fee_e, fee_label]: w.setEnabled(False)
1928 usechange_label = QLabel(_('Use change addresses'))
1929 grid_wallet.addWidget(usechange_label, 1, 0)
1930 usechange_combo = QComboBox()
1931 usechange_combo.addItems([_('Yes'), _('No')])
1932 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1933 grid_wallet.addWidget(usechange_combo, 1, 2)
1934 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1935 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1937 gap_label = QLabel(_('Gap limit'))
1938 grid_wallet.addWidget(gap_label, 2, 0)
1940 gap_e.setText("%d"% self.wallet.gap_limit)
1941 grid_wallet.addWidget(gap_e, 2, 2)
1942 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1943 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1944 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1945 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1946 + _('Warning') + ': ' \
1947 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1948 + _('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'
1949 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1950 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1951 if not self.config.is_modifiable('gap_limit'):
1952 for w in [gap_e, gap_label]: w.setEnabled(False)
1954 grid_wallet.setRowStretch(3,1)
1959 grid_io = QGridLayout(tab3)
1960 grid_io.setColumnStretch(0,1)
1961 tabs.addTab(tab3, _('Import/Export') )
1963 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1964 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1965 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1966 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1968 grid_io.addWidget(QLabel(_('History')), 2, 0)
1969 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1970 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1972 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1974 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1975 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1976 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1978 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1979 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1980 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1981 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1982 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1985 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1986 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1987 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1988 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1990 grid_io.setRowStretch(5,1)
1995 tab5 = QScrollArea()
1996 grid_plugins = QGridLayout(tab5)
1997 grid_plugins.setColumnStretch(0,1)
1998 tabs.addTab(tab5, _('Plugins') )
1999 def mk_toggle(cb, p):
2000 return lambda: cb.setChecked(p.toggle())
2001 for i, p in enumerate(self.plugins):
2003 name, description = p.get_info()
2004 cb = QCheckBox(name)
2005 cb.setDisabled(not p.is_available())
2006 cb.setChecked(p.is_enabled())
2007 cb.clicked.connect(mk_toggle(cb,p))
2008 grid_plugins.addWidget(cb, i, 0)
2009 grid_plugins.addWidget(HelpButton(description), i, 1)
2011 print_msg("Error: cannot display plugin", p)
2012 traceback.print_exc(file=sys.stdout)
2013 grid_plugins.setRowStretch(i+1,1)
2015 vbox.addLayout(ok_cancel_buttons(d))
2019 if not d.exec_(): return
2021 fee = unicode(fee_e.text())
2023 fee = int( 100000000 * Decimal(fee) )
2025 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2028 if self.wallet.fee != fee:
2029 self.wallet.fee = fee
2032 nz = unicode(nz_e.text())
2037 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2040 if self.wallet.num_zeros != nz:
2041 self.wallet.num_zeros = nz
2042 self.config.set_key('num_zeros', nz, True)
2043 self.update_history_tab()
2044 self.update_receive_tab()
2046 usechange_result = usechange_combo.currentIndex() == 0
2047 if self.wallet.use_change != usechange_result:
2048 self.wallet.use_change = usechange_result
2049 self.config.set_key('use_change', self.wallet.use_change, True)
2052 n = int(gap_e.text())
2054 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2057 if self.wallet.gap_limit != n:
2058 r = self.wallet.change_gap_limit(n)
2060 self.update_receive_tab()
2061 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2063 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2065 need_restart = False
2067 lang_request = languages.keys()[lang_combo.currentIndex()]
2068 if lang_request != self.config.get('language'):
2069 self.config.set_key("language", lang_request, True)
2072 cur_request = str(currencies[cur_combo.currentIndex()])
2073 if cur_request != self.config.get('currency', "None"):
2074 self.config.set_key('currency', cur_request, True)
2075 self.update_wallet()
2078 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2080 self.receive_tab_set_mode(view_combo.currentIndex())
2084 def network_dialog(wallet, parent=None):
2085 interface = wallet.interface
2087 if interface.is_connected:
2088 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2090 status = _("Not connected")
2091 server = interface.server
2094 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2095 server = interface.server
2097 plist, servers_list = interface.get_servers_list()
2101 d.setWindowTitle(_('Server'))
2102 d.setMinimumSize(375, 20)
2104 vbox = QVBoxLayout()
2107 hbox = QHBoxLayout()
2109 l.setPixmap(QPixmap(":icons/network.png"))
2112 hbox.addWidget(QLabel(status))
2114 vbox.addLayout(hbox)
2118 grid = QGridLayout()
2120 vbox.addLayout(grid)
2123 server_protocol = QComboBox()
2124 server_host = QLineEdit()
2125 server_host.setFixedWidth(200)
2126 server_port = QLineEdit()
2127 server_port.setFixedWidth(60)
2129 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2130 protocol_letters = 'thsg'
2131 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2132 server_protocol.addItems(protocol_names)
2134 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2135 grid.addWidget(server_protocol, 0, 1)
2136 grid.addWidget(server_host, 0, 2)
2137 grid.addWidget(server_port, 0, 3)
2139 def change_protocol(p):
2140 protocol = protocol_letters[p]
2141 host = unicode(server_host.text())
2142 pp = plist.get(host,DEFAULT_PORTS)
2143 if protocol not in pp.keys():
2144 protocol = pp.keys()[0]
2146 server_host.setText( host )
2147 server_port.setText( port )
2149 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2151 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2152 servers_list_widget = QTreeWidget(parent)
2153 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2154 servers_list_widget.setMaximumHeight(150)
2155 servers_list_widget.setColumnWidth(0, 240)
2156 for _host in servers_list.keys():
2157 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2158 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2160 def change_server(host, protocol=None):
2161 pp = plist.get(host,DEFAULT_PORTS)
2163 port = pp.get(protocol)
2164 if not port: protocol = None
2167 if 't' in pp.keys():
2169 port = pp.get(protocol)
2171 protocol = pp.keys()[0]
2172 port = pp.get(protocol)
2174 server_host.setText( host )
2175 server_port.setText( port )
2176 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2178 if not plist: return
2179 for p in protocol_letters:
2180 i = protocol_letters.index(p)
2181 j = server_protocol.model().index(i,0)
2182 if p not in pp.keys():
2183 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2185 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2189 host, port, protocol = server.split(':')
2190 change_server(host,protocol)
2192 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2193 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2195 if not wallet.config.is_modifiable('server'):
2196 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2199 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2200 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2201 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2202 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2205 proxy_mode = QComboBox()
2206 proxy_host = QLineEdit()
2207 proxy_host.setFixedWidth(200)
2208 proxy_port = QLineEdit()
2209 proxy_port.setFixedWidth(60)
2210 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2212 def check_for_disable(index = False):
2213 if proxy_mode.currentText() != 'NONE':
2214 proxy_host.setEnabled(True)
2215 proxy_port.setEnabled(True)
2217 proxy_host.setEnabled(False)
2218 proxy_port.setEnabled(False)
2221 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2223 if not wallet.config.is_modifiable('proxy'):
2224 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2226 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2227 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2228 proxy_host.setText(proxy_config.get("host"))
2229 proxy_port.setText(proxy_config.get("port"))
2231 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2232 grid.addWidget(proxy_mode, 2, 1)
2233 grid.addWidget(proxy_host, 2, 2)
2234 grid.addWidget(proxy_port, 2, 3)
2237 vbox.addLayout(ok_cancel_buttons(d))
2240 if not d.exec_(): return
2242 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2243 if proxy_mode.currentText() != 'NONE':
2244 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2248 wallet.config.set_key("proxy", proxy, True)
2249 wallet.config.set_key("server", server, True)
2250 interface.set_server(server, proxy)
2251 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2254 def closeEvent(self, event):
2256 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2257 self.save_column_widths()
2258 self.config.set_key("column-widths", self.column_widths, True)
2259 self.config.set_key("console-history",self.console.history[-50:])
2265 def __init__(self, wallet, config, app=None):
2266 self.wallet = wallet
2267 self.config = config
2269 self.app = QApplication(sys.argv)
2272 def restore_or_create(self):
2273 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2274 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2275 if r==2: return None
2276 return 'restore' if r==1 else 'create'
2278 def seed_dialog(self):
2279 return ElectrumWindow.seed_dialog( self.wallet )
2281 def network_dialog(self):
2282 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2285 def show_seed(self):
2286 ElectrumWindow.show_seed(self.wallet.seed)
2289 def password_dialog(self):
2290 if self.wallet.seed:
2291 ElectrumWindow.change_password_dialog(self.wallet)
2294 def restore_wallet(self):
2295 wallet = self.wallet
2296 # wait until we are connected, because the user might have selected another server
2297 if not wallet.interface.is_connected:
2298 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2299 waiting_dialog(waiting)
2301 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2302 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2304 wallet.set_up_to_date(False)
2305 wallet.interface.poke('synchronizer')
2306 waiting_dialog(waiting)
2307 if wallet.is_found():
2308 print_error( "Recovery successful" )
2310 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2317 w = ElectrumWindow(self.wallet, self.config)
2318 if url: w.set_url(url)