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 if tx.requires_fee(self.wallet.verifier) and fee == 0:
799 QMessageBox.warning(self, _('Error'), _("This transaction requires a fee, or it will not be propagated by the network."), _('OK'))
802 self.run_hook('send_tx', tx)
805 self.set_label(tx.hash(), label)
808 h = self.wallet.send_tx(tx)
809 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
810 status, msg = self.wallet.receive_tx( h )
812 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
814 self.update_contacts_tab()
816 QMessageBox.warning(self, _('Error'), msg, _('OK'))
818 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
820 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
821 with open(fileName,'w') as f:
822 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
823 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
825 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
830 def set_url(self, url):
831 address, amount, label, message, signature, identity, url = util.parse_url(url)
833 if label and self.wallet.labels.get(address) != label:
834 if self.question('Give label "%s" to address %s ?'%(label,address)):
835 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
836 self.wallet.addressbook.append(address)
837 self.set_label(address, label)
839 self.run_hook('set_url', url, self.show_message, self.question)
841 self.tabs.setCurrentIndex(1)
842 label = self.wallet.labels.get(address)
843 m_addr = label + ' <'+ address +'>' if label else address
844 self.payto_e.setText(m_addr)
846 self.message_e.setText(message)
847 self.amount_e.setText(amount)
849 self.set_frozen(self.payto_e,True)
850 self.set_frozen(self.amount_e,True)
851 self.set_frozen(self.message_e,True)
852 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
854 self.payto_sig.setVisible(False)
857 self.payto_sig.setVisible(False)
858 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
860 self.set_frozen(e,False)
862 def set_frozen(self,entry,frozen):
864 entry.setReadOnly(True)
865 entry.setFrame(False)
867 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
868 entry.setPalette(palette)
870 entry.setReadOnly(False)
873 palette.setColor(entry.backgroundRole(), QColor('white'))
874 entry.setPalette(palette)
877 def toggle_freeze(self,addr):
879 if addr in self.wallet.frozen_addresses:
880 self.wallet.unfreeze(addr)
882 self.wallet.freeze(addr)
883 self.update_receive_tab()
885 def toggle_priority(self,addr):
887 if addr in self.wallet.prioritized_addresses:
888 self.wallet.unprioritize(addr)
890 self.wallet.prioritize(addr)
891 self.update_receive_tab()
894 def create_list_tab(self, headers):
895 "generic tab creation method"
896 l = MyTreeWidget(self)
897 l.setColumnCount( len(headers) )
898 l.setHeaderLabels( headers )
908 vbox.addWidget(buttons)
913 buttons.setLayout(hbox)
918 def create_receive_tab(self):
919 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
920 l.setContextMenuPolicy(Qt.CustomContextMenu)
921 l.customContextMenuRequested.connect(self.create_receive_menu)
922 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
923 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
924 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
925 self.receive_list = l
926 self.receive_buttons_hbox = hbox
931 def receive_tab_set_mode(self, i):
932 self.save_column_widths()
933 self.expert_mode = (i == 1)
934 self.config.set_key('classic_expert_mode', self.expert_mode, True)
936 self.update_receive_tab()
939 def save_column_widths(self):
940 if not self.expert_mode:
941 widths = [ self.receive_list.columnWidth(0) ]
944 for i in range(self.receive_list.columnCount() -1):
945 widths.append(self.receive_list.columnWidth(i))
946 self.column_widths["receive"][self.expert_mode] = widths
948 self.column_widths["history"] = []
949 for i in range(self.history_list.columnCount() - 1):
950 self.column_widths["history"].append(self.history_list.columnWidth(i))
952 self.column_widths["contacts"] = []
953 for i in range(self.contacts_list.columnCount() - 1):
954 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
957 def create_contacts_tab(self):
958 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
959 l.setContextMenuPolicy(Qt.CustomContextMenu)
960 l.customContextMenuRequested.connect(self.create_contact_menu)
961 for i,width in enumerate(self.column_widths['contacts']):
962 l.setColumnWidth(i, width)
964 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
965 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
966 self.contacts_list = l
967 self.contacts_buttons_hbox = hbox
968 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
973 def delete_imported_key(self, addr):
974 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
975 self.wallet.imported_keys.pop(addr)
976 self.update_receive_tab()
977 self.update_history_tab()
981 def create_receive_menu(self, position):
982 # fixme: this function apparently has a side effect.
983 # if it is not called the menu pops up several times
984 #self.receive_list.selectedIndexes()
986 item = self.receive_list.itemAt(position)
988 addr = unicode(item.text(0))
989 if not is_valid(addr):
990 item.setExpanded(not item.isExpanded())
993 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
994 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
995 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
996 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
997 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
998 if addr in self.wallet.imported_keys:
999 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1001 if self.expert_mode:
1002 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1003 menu.addAction(t, lambda: self.toggle_freeze(addr))
1004 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1005 menu.addAction(t, lambda: self.toggle_priority(addr))
1007 self.run_hook('receive_menu', menu)
1008 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1011 def payto(self, addr):
1013 label = self.wallet.labels.get(addr)
1014 m_addr = label + ' <' + addr + '>' if label else addr
1015 self.tabs.setCurrentIndex(1)
1016 self.payto_e.setText(m_addr)
1017 self.amount_e.setFocus()
1020 def delete_contact(self, x):
1021 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1022 if x in self.wallet.addressbook:
1023 self.wallet.addressbook.remove(x)
1024 self.set_label(x, None)
1025 self.update_history_tab()
1026 self.update_contacts_tab()
1027 self.update_completions()
1030 def create_contact_menu(self, position):
1031 item = self.contacts_list.itemAt(position)
1033 addr = unicode(item.text(0))
1034 label = unicode(item.text(1))
1035 is_editable = item.data(0,32).toBool()
1036 payto_addr = item.data(0,33).toString()
1038 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1039 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1040 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1042 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1043 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1045 self.run_hook('create_contact_menu', menu, item)
1046 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1049 def update_receive_item(self, item):
1050 item.setFont(0, QFont(MONOSPACE_FONT))
1051 address = str(item.data(0,0).toString())
1052 label = self.wallet.labels.get(address,'')
1053 item.setData(1,0,label)
1054 item.setData(0,32, True) # is editable
1056 self.run_hook('update_receive_item', address, item)
1058 c, u = self.wallet.get_addr_balance(address)
1059 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1060 item.setData(2,0,balance)
1062 if self.expert_mode:
1063 if address in self.wallet.frozen_addresses:
1064 item.setBackgroundColor(0, QColor('lightblue'))
1065 elif address in self.wallet.prioritized_addresses:
1066 item.setBackgroundColor(0, QColor('lightgreen'))
1069 def update_receive_tab(self):
1070 l = self.receive_list
1073 l.setColumnHidden(2, not self.expert_mode)
1074 l.setColumnHidden(3, not self.expert_mode)
1075 if not self.expert_mode:
1076 width = self.column_widths['receive'][0][0]
1077 l.setColumnWidth(0, width)
1079 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1080 l.setColumnWidth(i, width)
1083 for k, account in self.wallet.accounts.items():
1084 name = account.get('name',str(k))
1085 c,u = self.wallet.get_account_balance(k)
1086 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1087 l.addTopLevelItem(account_item)
1088 account_item.setExpanded(True)
1091 for is_change in [0,1]:
1092 name = "Receiving" if not is_change else "Change"
1093 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1094 account_item.addChild(seq_item)
1095 if not is_change: seq_item.setExpanded(True)
1099 for address in account[is_change]:
1100 h = self.wallet.history.get(address,[])
1104 if gap > self.wallet.gap_limit:
1109 num_tx = '*' if h == ['*'] else "%d"%len(h)
1110 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1111 self.update_receive_item(item)
1113 item.setBackgroundColor(1, QColor('red'))
1114 seq_item.addChild(item)
1116 if self.wallet.imported_keys:
1117 c,u = self.wallet.get_imported_balance()
1118 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1119 l.addTopLevelItem(account_item)
1120 account_item.setExpanded(True)
1121 for address in self.wallet.imported_keys.keys():
1122 item = QTreeWidgetItem( [ address, '', '', ''] )
1123 self.update_receive_item(item)
1124 account_item.addChild(item)
1127 # we use column 1 because column 0 may be hidden
1128 l.setCurrentItem(l.topLevelItem(0),1)
1131 def update_contacts_tab(self):
1133 l = self.contacts_list
1136 for address in self.wallet.addressbook:
1137 label = self.wallet.labels.get(address,'')
1138 n = self.wallet.get_num_tx(address)
1139 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1140 item.setFont(0, QFont(MONOSPACE_FONT))
1141 # 32 = label can be edited (bool)
1142 item.setData(0,32, True)
1144 item.setData(0,33, address)
1145 l.addTopLevelItem(item)
1147 self.run_hook('update_contacts_tab', l)
1148 l.setCurrentItem(l.topLevelItem(0))
1152 def create_console_tab(self):
1153 from qt_console import Console
1154 self.console = console = Console()
1155 self.console.history = self.config.get("console-history",[])
1156 self.console.history_index = len(self.console.history)
1158 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1159 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1161 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1163 def mkfunc(f, method):
1164 return lambda *args: apply( f, (method, args, self.password_dialog ))
1166 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1167 methods[m] = mkfunc(c._run, m)
1169 console.updateNamespace(methods)
1173 def create_status_bar(self):
1174 self.status_text = ""
1176 sb.setFixedHeight(35)
1177 qtVersion = qVersion()
1179 update_notification = UpdateLabel(self.config)
1180 if(update_notification.new_version):
1181 sb.addPermanentWidget(update_notification)
1183 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1184 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1185 if self.wallet.seed:
1186 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1187 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1188 sb.addPermanentWidget( self.password_button )
1189 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1190 if self.wallet.seed:
1191 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1192 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1193 sb.addPermanentWidget( self.status_button )
1195 self.run_hook('create_status_bar', (sb,))
1197 self.setStatusBar(sb)
1201 self.config.set_key('gui', 'lite', True)
1204 self.lite.mini.show()
1206 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1207 self.lite.main(None)
1209 def new_contact_dialog(self):
1210 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1211 address = unicode(text)
1213 if is_valid(address):
1214 self.wallet.addressbook.append(address)
1216 self.update_contacts_tab()
1217 self.update_history_tab()
1218 self.update_completions()
1220 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1222 def show_master_public_key(self):
1223 dialog = QDialog(self)
1225 dialog.setWindowTitle(_("Master Public Key"))
1227 main_text = QTextEdit()
1228 main_text.setText(self.wallet.get_master_public_key())
1229 main_text.setReadOnly(True)
1230 main_text.setMaximumHeight(170)
1231 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1233 ok_button = QPushButton(_("OK"))
1234 ok_button.setDefault(True)
1235 ok_button.clicked.connect(dialog.accept)
1237 main_layout = QGridLayout()
1238 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1240 main_layout.addWidget(main_text, 1, 0)
1241 main_layout.addWidget(qrw, 1, 1 )
1243 vbox = QVBoxLayout()
1244 vbox.addLayout(main_layout)
1245 hbox = QHBoxLayout()
1247 hbox.addWidget(ok_button)
1248 vbox.addLayout(hbox)
1250 dialog.setLayout(vbox)
1255 def show_seed_dialog(self, password):
1256 if not self.wallet.seed:
1257 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1260 seed = self.wallet.decode_seed(password)
1262 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1264 self.show_seed(seed, self)
1268 def show_seed(self, seed, parent=None):
1269 dialog = QDialog(parent)
1271 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1273 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1275 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1277 seed_text = QTextEdit(brainwallet)
1278 seed_text.setReadOnly(True)
1279 seed_text.setMaximumHeight(130)
1281 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1282 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1283 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1284 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1285 label2 = QLabel(msg2)
1286 label2.setWordWrap(True)
1289 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1290 logo.setMaximumWidth(60)
1292 qrw = QRCodeWidget(seed)
1294 ok_button = QPushButton(_("OK"))
1295 ok_button.setDefault(True)
1296 ok_button.clicked.connect(dialog.accept)
1298 grid = QGridLayout()
1299 #main_layout.addWidget(logo, 0, 0)
1301 grid.addWidget(logo, 0, 0)
1302 grid.addWidget(label1, 0, 1)
1304 grid.addWidget(seed_text, 1, 0, 1, 2)
1306 grid.addWidget(qrw, 0, 2, 2, 1)
1308 vbox = QVBoxLayout()
1309 vbox.addLayout(grid)
1310 vbox.addWidget(label2)
1312 hbox = QHBoxLayout()
1314 hbox.addWidget(ok_button)
1315 vbox.addLayout(hbox)
1317 dialog.setLayout(vbox)
1320 def show_qrcode(self, data, title = "QR code"):
1324 d.setWindowTitle(title)
1325 d.setMinimumSize(270, 300)
1326 vbox = QVBoxLayout()
1327 qrw = QRCodeWidget(data)
1328 vbox.addWidget(qrw, 1)
1329 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1330 hbox = QHBoxLayout()
1334 filename = "qrcode.bmp"
1335 bmp.save_qrcode(qrw.qr, filename)
1336 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1338 b = QPushButton(_("Save"))
1340 b.clicked.connect(print_qr)
1342 b = QPushButton(_("Close"))
1344 b.clicked.connect(d.accept)
1347 vbox.addLayout(hbox)
1352 def do_protect(self, func, args):
1353 if self.wallet.use_encryption:
1354 password = self.password_dialog()
1360 if args != (False,):
1361 args = (self,) + args + (password,)
1363 args = (self,password)
1368 def show_private_key(self, address, password):
1369 if not address: return
1371 pk = self.wallet.get_private_key(address, password)
1372 except BaseException, e:
1373 self.show_message(str(e))
1375 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1379 def do_sign(self, address, message, signature, password):
1381 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1382 signature.setText(sig)
1383 except BaseException, e:
1384 self.show_message(str(e))
1386 def sign_message(self, address):
1387 if not address: return
1390 d.setWindowTitle(_('Sign Message'))
1391 d.setMinimumSize(410, 290)
1393 tab_widget = QTabWidget()
1395 layout = QGridLayout(tab)
1397 sign_address = QLineEdit()
1399 sign_address.setText(address)
1400 layout.addWidget(QLabel(_('Address')), 1, 0)
1401 layout.addWidget(sign_address, 1, 1)
1403 sign_message = QTextEdit()
1404 layout.addWidget(QLabel(_('Message')), 2, 0)
1405 layout.addWidget(sign_message, 2, 1)
1406 layout.setRowStretch(2,3)
1408 sign_signature = QTextEdit()
1409 layout.addWidget(QLabel(_('Signature')), 3, 0)
1410 layout.addWidget(sign_signature, 3, 1)
1411 layout.setRowStretch(3,1)
1414 hbox = QHBoxLayout()
1415 b = QPushButton(_("Sign"))
1417 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1418 b = QPushButton(_("Close"))
1419 b.clicked.connect(d.accept)
1421 layout.addLayout(hbox, 4, 1)
1422 tab_widget.addTab(tab, _("Sign"))
1426 layout = QGridLayout(tab)
1428 verify_address = QLineEdit()
1429 layout.addWidget(QLabel(_('Address')), 1, 0)
1430 layout.addWidget(verify_address, 1, 1)
1432 verify_message = QTextEdit()
1433 layout.addWidget(QLabel(_('Message')), 2, 0)
1434 layout.addWidget(verify_message, 2, 1)
1435 layout.setRowStretch(2,3)
1437 verify_signature = QTextEdit()
1438 layout.addWidget(QLabel(_('Signature')), 3, 0)
1439 layout.addWidget(verify_signature, 3, 1)
1440 layout.setRowStretch(3,1)
1444 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1445 self.show_message(_("Signature verified"))
1446 except BaseException, e:
1447 self.show_message(str(e))
1450 hbox = QHBoxLayout()
1451 b = QPushButton(_("Verify"))
1452 b.clicked.connect(do_verify)
1454 b = QPushButton(_("Close"))
1455 b.clicked.connect(d.accept)
1457 layout.addLayout(hbox, 4, 1)
1458 tab_widget.addTab(tab, _("Verify"))
1460 vbox = QVBoxLayout()
1461 vbox.addWidget(tab_widget)
1468 def question(self, msg):
1469 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1471 def show_message(self, msg):
1472 QMessageBox.information(self, _('Message'), msg, _('OK'))
1474 def password_dialog(self ):
1481 vbox = QVBoxLayout()
1482 msg = _('Please enter your password')
1483 vbox.addWidget(QLabel(msg))
1485 grid = QGridLayout()
1487 grid.addWidget(QLabel(_('Password')), 1, 0)
1488 grid.addWidget(pw, 1, 1)
1489 vbox.addLayout(grid)
1491 vbox.addLayout(ok_cancel_buttons(d))
1494 self.run_hook('password_dialog', pw, grid, 1)
1495 if not d.exec_(): return
1496 return unicode(pw.text())
1503 def change_password_dialog( wallet, parent=None ):
1506 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1514 new_pw = QLineEdit()
1515 new_pw.setEchoMode(2)
1516 conf_pw = QLineEdit()
1517 conf_pw.setEchoMode(2)
1519 vbox = QVBoxLayout()
1521 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1522 +_('To disable wallet encryption, enter an empty new password.')) \
1523 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1525 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1526 +_("Leave these fields empty if you want to disable encryption.")
1527 vbox.addWidget(QLabel(msg))
1529 grid = QGridLayout()
1532 if wallet.use_encryption:
1533 grid.addWidget(QLabel(_('Password')), 1, 0)
1534 grid.addWidget(pw, 1, 1)
1536 grid.addWidget(QLabel(_('New Password')), 2, 0)
1537 grid.addWidget(new_pw, 2, 1)
1539 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1540 grid.addWidget(conf_pw, 3, 1)
1541 vbox.addLayout(grid)
1543 vbox.addLayout(ok_cancel_buttons(d))
1546 if not d.exec_(): return
1548 password = unicode(pw.text()) if wallet.use_encryption else None
1549 new_password = unicode(new_pw.text())
1550 new_password2 = unicode(conf_pw.text())
1553 seed = wallet.decode_seed(password)
1555 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1558 if new_password != new_password2:
1559 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1560 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1562 wallet.update_password(seed, password, new_password)
1564 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1565 parent.password_button.setIcon( icon )
1569 def seed_dialog(wallet, parent=None):
1573 vbox = QVBoxLayout()
1574 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1575 vbox.addWidget(QLabel(msg))
1577 grid = QGridLayout()
1580 seed_e = QLineEdit()
1581 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1582 grid.addWidget(seed_e, 1, 1)
1583 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1587 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1588 grid.addWidget(gap_e, 2, 1)
1589 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1590 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1591 vbox.addLayout(grid)
1593 vbox.addLayout(ok_cancel_buttons(d))
1596 if not d.exec_(): return
1599 gap = int(unicode(gap_e.text()))
1601 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1605 seed = str(seed_e.text())
1608 print_error("Warning: Not hex, trying decode")
1610 seed = mnemonic.mn_decode( seed.split(' ') )
1612 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1616 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1621 def generate_transaction_information_widget(self, tx):
1622 tabs = QTabWidget(self)
1625 grid_ui = QGridLayout(tab1)
1626 grid_ui.setColumnStretch(0,1)
1627 tabs.addTab(tab1, _('Outputs') )
1629 tree_widget = MyTreeWidget(self)
1630 tree_widget.setColumnCount(2)
1631 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1632 tree_widget.setColumnWidth(0, 300)
1633 tree_widget.setColumnWidth(1, 50)
1635 for address, value in tx.outputs:
1636 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1637 tree_widget.addTopLevelItem(item)
1639 tree_widget.setMaximumHeight(100)
1641 grid_ui.addWidget(tree_widget)
1644 grid_ui = QGridLayout(tab2)
1645 grid_ui.setColumnStretch(0,1)
1646 tabs.addTab(tab2, _('Inputs') )
1648 tree_widget = MyTreeWidget(self)
1649 tree_widget.setColumnCount(2)
1650 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1652 for input_line in tx.inputs:
1653 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1654 tree_widget.addTopLevelItem(item)
1656 tree_widget.setMaximumHeight(100)
1658 grid_ui.addWidget(tree_widget)
1662 def tx_dict_from_text(self, txt):
1664 tx_dict = json.loads(str(txt))
1665 assert "hex" in tx_dict.keys()
1666 assert "complete" in tx_dict.keys()
1667 if not tx_dict["complete"]:
1668 assert "input_info" in tx_dict.keys()
1670 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1675 def read_tx_from_file(self):
1676 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1680 with open(fileName, "r") as f:
1681 file_content = f.read()
1682 except (ValueError, IOError, os.error), reason:
1683 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1685 return self.tx_dict_from_text(file_content)
1689 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1691 self.wallet.signrawtransaction(tx, input_info, [], password)
1693 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1695 with open(fileName, "w+") as f:
1696 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1697 self.show_message(_("Transaction saved successfully"))
1700 except BaseException, e:
1701 self.show_message(str(e))
1704 def send_raw_transaction(self, raw_tx, dialog = ""):
1705 result, result_message = self.wallet.sendtx( raw_tx )
1707 self.show_message("Transaction successfully sent: %s" % (result_message))
1711 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1713 def do_process_from_text(self):
1714 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1717 tx_dict = self.tx_dict_from_text(text)
1719 self.create_process_transaction_window(tx_dict)
1721 def do_process_from_file(self):
1722 tx_dict = self.read_tx_from_file()
1724 self.create_process_transaction_window(tx_dict)
1726 def create_process_transaction_window(self, tx_dict):
1727 tx = Transaction(tx_dict["hex"])
1729 dialog = QDialog(self)
1730 dialog.setMinimumWidth(500)
1731 dialog.setWindowTitle(_('Process raw transaction'))
1737 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1738 l.addWidget(QLabel(_("Actions")), 4,0)
1740 if tx_dict["complete"] == False:
1741 l.addWidget(QLabel(_("Unsigned")), 3,1)
1742 if self.wallet.seed :
1743 b = QPushButton("Sign transaction")
1744 input_info = json.loads(tx_dict["input_info"])
1745 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1746 l.addWidget(b, 4, 1)
1748 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1750 l.addWidget(QLabel(_("Signed")), 3,1)
1751 b = QPushButton("Broadcast transaction")
1752 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1755 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1756 cancelButton = QPushButton(_("Cancel"))
1757 cancelButton.clicked.connect(lambda: dialog.done(0))
1758 l.addWidget(cancelButton, 4,2)
1764 def do_export_privkeys(self, password):
1765 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.")))
1768 select_export = _('Select file to export your private keys to')
1769 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1771 with open(fileName, "w+") as csvfile:
1772 transaction = csv.writer(csvfile)
1773 transaction.writerow(["address", "private_key"])
1776 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1777 transaction.writerow(["%34s"%addr,pk])
1779 self.show_message(_("Private keys exported."))
1781 except (IOError, os.error), reason:
1782 export_error_label = _("Electrum was unable to produce a private key-export.")
1783 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1785 except BaseException, e:
1786 self.show_message(str(e))
1790 def do_import_labels(self):
1791 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1792 if not labelsFile: return
1794 f = open(labelsFile, 'r')
1797 for key, value in json.loads(data).items():
1798 self.wallet.labels[key] = value
1800 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1801 except (IOError, os.error), reason:
1802 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1805 def do_export_labels(self):
1806 labels = self.wallet.labels
1808 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1810 with open(fileName, 'w+') as f:
1811 json.dump(labels, f)
1812 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1813 except (IOError, os.error), reason:
1814 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1817 def do_export_history(self):
1818 from gui_lite import csv_transaction
1819 csv_transaction(self.wallet)
1823 def do_import_privkey(self, password):
1824 if not self.wallet.imported_keys:
1825 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1826 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1827 + _('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>' \
1828 + _('Are you sure you understand what you are doing?'), 3, 4)
1831 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1834 text = str(text).split()
1839 addr = self.wallet.import_key(key, password)
1840 except BaseException as e:
1846 addrlist.append(addr)
1848 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1850 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1851 self.update_receive_tab()
1852 self.update_history_tab()
1855 def settings_dialog(self):
1857 d.setWindowTitle(_('Electrum Settings'))
1859 vbox = QVBoxLayout()
1861 tabs = QTabWidget(self)
1862 self.settings_tab = tabs
1863 vbox.addWidget(tabs)
1866 grid_ui = QGridLayout(tab1)
1867 grid_ui.setColumnStretch(0,1)
1868 tabs.addTab(tab1, _('Display') )
1870 nz_label = QLabel(_('Display zeros'))
1871 grid_ui.addWidget(nz_label, 0, 0)
1873 nz_e.setText("%d"% self.wallet.num_zeros)
1874 grid_ui.addWidget(nz_e, 0, 1)
1875 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1876 grid_ui.addWidget(HelpButton(msg), 0, 2)
1877 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1878 if not self.config.is_modifiable('num_zeros'):
1879 for w in [nz_e, nz_label]: w.setEnabled(False)
1881 lang_label=QLabel(_('Language') + ':')
1882 grid_ui.addWidget(lang_label, 1, 0)
1883 lang_combo = QComboBox()
1884 from i18n import languages
1885 lang_combo.addItems(languages.values())
1887 index = languages.keys().index(self.config.get("language",''))
1890 lang_combo.setCurrentIndex(index)
1891 grid_ui.addWidget(lang_combo, 1, 1)
1892 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1893 if not self.config.is_modifiable('language'):
1894 for w in [lang_combo, lang_label]: w.setEnabled(False)
1896 currencies = self.exchanger.get_currencies()
1897 currencies.insert(0, "None")
1899 cur_label=QLabel(_('Currency') + ':')
1900 grid_ui.addWidget(cur_label , 2, 0)
1901 cur_combo = QComboBox()
1902 cur_combo.addItems(currencies)
1904 index = currencies.index(self.config.get('currency', "None"))
1907 cur_combo.setCurrentIndex(index)
1908 grid_ui.addWidget(cur_combo, 2, 1)
1909 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1911 view_label=QLabel(_('Receive Tab') + ':')
1912 grid_ui.addWidget(view_label , 3, 0)
1913 view_combo = QComboBox()
1914 view_combo.addItems([_('Simple'), _('Advanced')])
1915 view_combo.setCurrentIndex(self.expert_mode)
1916 grid_ui.addWidget(view_combo, 3, 1)
1917 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1918 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1919 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1921 grid_ui.addWidget(HelpButton(hh), 3, 2)
1922 grid_ui.setRowStretch(4,1)
1926 grid_wallet = QGridLayout(tab2)
1927 grid_wallet.setColumnStretch(0,1)
1928 tabs.addTab(tab2, _('Wallet') )
1930 fee_label = QLabel(_('Transaction fee'))
1931 grid_wallet.addWidget(fee_label, 0, 0)
1933 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1934 grid_wallet.addWidget(fee_e, 0, 2)
1935 msg = _('Fee per kilobyte of transaction.') + ' ' \
1936 + _('Recommended value') + ': 0.0001'
1937 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1938 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1939 if not self.config.is_modifiable('fee'):
1940 for w in [fee_e, fee_label]: w.setEnabled(False)
1942 usechange_label = QLabel(_('Use change addresses'))
1943 grid_wallet.addWidget(usechange_label, 1, 0)
1944 usechange_combo = QComboBox()
1945 usechange_combo.addItems([_('Yes'), _('No')])
1946 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1947 grid_wallet.addWidget(usechange_combo, 1, 2)
1948 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1949 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1951 gap_label = QLabel(_('Gap limit'))
1952 grid_wallet.addWidget(gap_label, 2, 0)
1954 gap_e.setText("%d"% self.wallet.gap_limit)
1955 grid_wallet.addWidget(gap_e, 2, 2)
1956 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1957 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1958 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1959 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1960 + _('Warning') + ': ' \
1961 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1962 + _('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'
1963 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1964 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1965 if not self.config.is_modifiable('gap_limit'):
1966 for w in [gap_e, gap_label]: w.setEnabled(False)
1968 grid_wallet.setRowStretch(3,1)
1973 grid_io = QGridLayout(tab3)
1974 grid_io.setColumnStretch(0,1)
1975 tabs.addTab(tab3, _('Import/Export') )
1977 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1978 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1979 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1980 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1982 grid_io.addWidget(QLabel(_('History')), 2, 0)
1983 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1984 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1986 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1988 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1989 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1990 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1992 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1993 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1994 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1995 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1996 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1999 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2000 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2001 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2002 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2004 grid_io.setRowStretch(5,1)
2009 tab5 = QScrollArea()
2010 grid_plugins = QGridLayout(tab5)
2011 grid_plugins.setColumnStretch(0,1)
2012 tabs.addTab(tab5, _('Plugins') )
2013 def mk_toggle(cb, p):
2014 return lambda: cb.setChecked(p.toggle())
2015 for i, p in enumerate(self.plugins):
2017 name, description = p.get_info()
2018 cb = QCheckBox(name)
2019 cb.setDisabled(not p.is_available())
2020 cb.setChecked(p.is_enabled())
2021 cb.clicked.connect(mk_toggle(cb,p))
2022 grid_plugins.addWidget(cb, i, 0)
2023 if p.requires_settings():
2024 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2025 grid_plugins.addWidget(HelpButton(description), i, 2)
2027 print_msg("Error: cannot display plugin", p)
2028 traceback.print_exc(file=sys.stdout)
2029 grid_plugins.setRowStretch(i+1,1)
2031 self.run_hook('create_settings_tab', tabs)
2033 vbox.addLayout(ok_cancel_buttons(d))
2037 if not d.exec_(): return
2039 fee = unicode(fee_e.text())
2041 fee = int( 100000000 * Decimal(fee) )
2043 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2046 if self.wallet.fee != fee:
2047 self.wallet.fee = fee
2050 nz = unicode(nz_e.text())
2055 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2058 if self.wallet.num_zeros != nz:
2059 self.wallet.num_zeros = nz
2060 self.config.set_key('num_zeros', nz, True)
2061 self.update_history_tab()
2062 self.update_receive_tab()
2064 usechange_result = usechange_combo.currentIndex() == 0
2065 if self.wallet.use_change != usechange_result:
2066 self.wallet.use_change = usechange_result
2067 self.config.set_key('use_change', self.wallet.use_change, True)
2070 n = int(gap_e.text())
2072 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2075 if self.wallet.gap_limit != n:
2076 r = self.wallet.change_gap_limit(n)
2078 self.update_receive_tab()
2079 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2081 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2083 need_restart = False
2085 lang_request = languages.keys()[lang_combo.currentIndex()]
2086 if lang_request != self.config.get('language'):
2087 self.config.set_key("language", lang_request, True)
2090 cur_request = str(currencies[cur_combo.currentIndex()])
2091 if cur_request != self.config.get('currency', "None"):
2092 self.config.set_key('currency', cur_request, True)
2093 self.update_wallet()
2095 self.run_hook('close_settings_dialog')
2098 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2100 self.receive_tab_set_mode(view_combo.currentIndex())
2104 def network_dialog(wallet, parent=None):
2105 interface = wallet.interface
2107 if interface.is_connected:
2108 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2110 status = _("Not connected")
2111 server = interface.server
2114 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2115 server = interface.server
2117 plist, servers_list = interface.get_servers_list()
2121 d.setWindowTitle(_('Server'))
2122 d.setMinimumSize(375, 20)
2124 vbox = QVBoxLayout()
2127 hbox = QHBoxLayout()
2129 l.setPixmap(QPixmap(":icons/network.png"))
2132 hbox.addWidget(QLabel(status))
2134 vbox.addLayout(hbox)
2138 grid = QGridLayout()
2140 vbox.addLayout(grid)
2143 server_protocol = QComboBox()
2144 server_host = QLineEdit()
2145 server_host.setFixedWidth(200)
2146 server_port = QLineEdit()
2147 server_port.setFixedWidth(60)
2149 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2150 protocol_letters = 'thsg'
2151 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2152 server_protocol.addItems(protocol_names)
2154 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2155 grid.addWidget(server_protocol, 0, 1)
2156 grid.addWidget(server_host, 0, 2)
2157 grid.addWidget(server_port, 0, 3)
2159 def change_protocol(p):
2160 protocol = protocol_letters[p]
2161 host = unicode(server_host.text())
2162 pp = plist.get(host,DEFAULT_PORTS)
2163 if protocol not in pp.keys():
2164 protocol = pp.keys()[0]
2166 server_host.setText( host )
2167 server_port.setText( port )
2169 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2171 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2172 servers_list_widget = QTreeWidget(parent)
2173 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2174 servers_list_widget.setMaximumHeight(150)
2175 servers_list_widget.setColumnWidth(0, 240)
2176 for _host in servers_list.keys():
2177 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2178 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2180 def change_server(host, protocol=None):
2181 pp = plist.get(host,DEFAULT_PORTS)
2183 port = pp.get(protocol)
2184 if not port: protocol = None
2187 if 't' in pp.keys():
2189 port = pp.get(protocol)
2191 protocol = pp.keys()[0]
2192 port = pp.get(protocol)
2194 server_host.setText( host )
2195 server_port.setText( port )
2196 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2198 if not plist: return
2199 for p in protocol_letters:
2200 i = protocol_letters.index(p)
2201 j = server_protocol.model().index(i,0)
2202 if p not in pp.keys() and interface.is_connected:
2203 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2205 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2209 host, port, protocol = server.split(':')
2210 change_server(host,protocol)
2212 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2213 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2215 if not wallet.config.is_modifiable('server'):
2216 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2219 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2220 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2221 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2222 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2225 proxy_mode = QComboBox()
2226 proxy_host = QLineEdit()
2227 proxy_host.setFixedWidth(200)
2228 proxy_port = QLineEdit()
2229 proxy_port.setFixedWidth(60)
2230 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2232 def check_for_disable(index = False):
2233 if proxy_mode.currentText() != 'NONE':
2234 proxy_host.setEnabled(True)
2235 proxy_port.setEnabled(True)
2237 proxy_host.setEnabled(False)
2238 proxy_port.setEnabled(False)
2241 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2243 if not wallet.config.is_modifiable('proxy'):
2244 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2246 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2247 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2248 proxy_host.setText(proxy_config.get("host"))
2249 proxy_port.setText(proxy_config.get("port"))
2251 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2252 grid.addWidget(proxy_mode, 2, 1)
2253 grid.addWidget(proxy_host, 2, 2)
2254 grid.addWidget(proxy_port, 2, 3)
2257 vbox.addLayout(ok_cancel_buttons(d))
2260 if not d.exec_(): return
2262 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2263 if proxy_mode.currentText() != 'NONE':
2264 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2268 wallet.config.set_key("proxy", proxy, True)
2269 wallet.config.set_key("server", server, True)
2270 interface.set_server(server, proxy)
2271 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2274 def closeEvent(self, event):
2276 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2277 self.save_column_widths()
2278 self.config.set_key("column_widths", self.column_widths, True)
2279 self.config.set_key("console-history",self.console.history[-50:])
2285 def __init__(self, wallet, config, app=None):
2286 self.wallet = wallet
2287 self.config = config
2289 self.app = QApplication(sys.argv)
2292 def restore_or_create(self):
2293 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2294 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2295 if r==2: return None
2296 return 'restore' if r==1 else 'create'
2298 def seed_dialog(self):
2299 return ElectrumWindow.seed_dialog( self.wallet )
2301 def network_dialog(self):
2302 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2305 def show_seed(self):
2306 ElectrumWindow.show_seed(self.wallet.seed)
2309 def password_dialog(self):
2310 if self.wallet.seed:
2311 ElectrumWindow.change_password_dialog(self.wallet)
2314 def restore_wallet(self):
2315 wallet = self.wallet
2316 # wait until we are connected, because the user might have selected another server
2317 if not wallet.interface.is_connected:
2318 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2319 waiting_dialog(waiting)
2321 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2322 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2324 wallet.set_up_to_date(False)
2325 wallet.interface.poke('synchronizer')
2326 waiting_dialog(waiting)
2327 if wallet.is_found():
2328 print_error( "Recovery successful" )
2330 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2337 w = ElectrumWindow(self.wallet, self.config)
2338 if url: w.set_url(url)