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 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
66 from electrum import ELECTRUM_VERSION
69 class UpdateLabel(QtGui.QLabel):
70 def __init__(self, config, parent=None):
71 QtGui.QLabel.__init__(self, parent)
72 self.new_version = False
75 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
76 con.request("GET", "/version")
77 res = con.getresponse()
78 except socket.error as msg:
79 print_error("Could not retrieve version information")
83 self.latest_version = res.read()
84 self.latest_version = self.latest_version.replace("\n","")
85 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
87 self.current_version = ELECTRUM_VERSION
88 if(self.compare_versions(self.latest_version, self.current_version) == 1):
89 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
90 if(self.compare_versions(self.latest_version, latest_seen) == 1):
91 self.new_version = True
92 self.setText(_("New version available") + ": " + self.latest_version)
95 def compare_versions(self, version1, version2):
97 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
98 return cmp(normalize(version1), normalize(version2))
100 def ignore_this_version(self):
102 self.config.set_key("last_seen_version", self.latest_version, True)
103 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
106 def ignore_all_version(self):
108 self.config.set_key("last_seen_version", "9.9.9", True)
109 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
112 def open_website(self):
113 webbrowser.open("http://electrum.org/download.html")
116 def mouseReleaseEvent(self, event):
117 dialog = QDialog(self)
118 dialog.setWindowTitle(_('Electrum update'))
121 main_layout = QGridLayout()
122 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
124 ignore_version = QPushButton(_("Ignore this version"))
125 ignore_version.clicked.connect(self.ignore_this_version)
127 ignore_all_versions = QPushButton(_("Ignore all versions"))
128 ignore_all_versions.clicked.connect(self.ignore_all_version)
130 open_website = QPushButton(_("Goto download page"))
131 open_website.clicked.connect(self.open_website)
133 main_layout.addWidget(ignore_version, 1, 0)
134 main_layout.addWidget(ignore_all_versions, 1, 1)
135 main_layout.addWidget(open_website, 1, 2)
137 dialog.setLayout(main_layout)
141 if not dialog.exec_(): return
143 def numbify(entry, is_int = False):
144 text = unicode(entry.text()).strip()
145 pos = entry.cursorPosition()
147 if not is_int: chars +='.'
148 s = ''.join([i for i in text if i in chars])
152 s = s.replace('.','')
153 s = s[:p] + '.' + s[p:p+8]
155 amount = int( Decimal(s) * 100000000 )
164 entry.setCursorPosition(pos)
168 class Timer(QtCore.QThread):
171 self.emit(QtCore.SIGNAL('timersignal'))
174 class HelpButton(QPushButton):
175 def __init__(self, text):
176 QPushButton.__init__(self, '?')
177 self.setFocusPolicy(Qt.NoFocus)
178 self.setFixedWidth(20)
179 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
182 class EnterButton(QPushButton):
183 def __init__(self, text, func):
184 QPushButton.__init__(self, text)
186 self.clicked.connect(func)
188 def keyPressEvent(self, e):
189 if e.key() == QtCore.Qt.Key_Return:
192 class MyTreeWidget(QTreeWidget):
193 def __init__(self, parent):
194 QTreeWidget.__init__(self, parent)
197 for i in range(0,self.viewport().height()/5):
198 if self.itemAt(QPoint(0,i*5)) == item:
202 for j in range(0,30):
203 if self.itemAt(QPoint(0,i*5 + j)) != item:
205 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
207 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
212 class StatusBarButton(QPushButton):
213 def __init__(self, icon, tooltip, func):
214 QPushButton.__init__(self, icon, '')
215 self.setToolTip(tooltip)
217 self.setMaximumWidth(25)
218 self.clicked.connect(func)
221 def keyPressEvent(self, e):
222 if e.key() == QtCore.Qt.Key_Return:
230 def waiting_dialog(f):
236 w.setWindowTitle('Electrum')
246 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
251 def ok_cancel_buttons(dialog):
254 b = QPushButton("Cancel")
256 b.clicked.connect(dialog.reject)
257 b = QPushButton("OK")
259 b.clicked.connect(dialog.accept)
264 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
265 "receive":[[370],[370,200,130]] }
267 class ElectrumWindow(QMainWindow):
269 def __init__(self, wallet, config):
270 QMainWindow.__init__(self)
276 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
277 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
278 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
279 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
281 self.expert_mode = config.get('classic_expert_mode', False)
283 set_language(config.get('language'))
285 self.funds_error = False
286 self.completions = QStringListModel()
288 self.tabs = tabs = QTabWidget(self)
289 self.column_widths = self.config.get("column-widths", default_column_widths )
290 tabs.addTab(self.create_history_tab(), _('History') )
291 tabs.addTab(self.create_send_tab(), _('Send') )
292 tabs.addTab(self.create_receive_tab(), _('Receive') )
293 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
294 tabs.addTab(self.create_console_tab(), _('Console') )
295 tabs.setMinimumSize(600, 400)
296 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
297 self.setCentralWidget(tabs)
298 self.create_status_bar()
300 g = self.config.get("winpos-qt",[100, 100, 840, 400])
301 self.setGeometry(g[0], g[1], g[2], g[3])
302 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
303 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
304 self.setWindowTitle( title )
306 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
307 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
308 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
309 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
311 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
312 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
313 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
314 self.history_list.setFocus(True)
316 self.exchanger = exchange_rate.Exchanger(self)
317 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
319 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
320 if platform.system() == 'Windows':
321 n = 3 if self.wallet.seed else 2
322 tabs.setCurrentIndex (n)
323 tabs.setCurrentIndex (0)
325 # set initial message
326 self.console.showMessage(self.wallet.banner)
330 def init_plugins(self):
332 if os.path.exists("plugins"):
333 fp, pathname, description = imp.find_module('plugins')
334 imp.load_module('electrum_plugins', fp, pathname, description)
335 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
336 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
338 import electrum_plugins
339 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
340 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
342 self.plugin_hooks = {}
343 for p in self.plugins:
347 print_msg("Error:cannot initialize plugin",p)
348 traceback.print_exc(file=sys.stdout)
350 def set_hook(self, name, callback):
351 h = self.plugin_hooks.get(name, [])
353 self.plugin_hooks[name] = h
355 def unset_hook(self, name, callback):
356 h = self.plugin_hooks.get(name,[])
357 if callback in h: h.remove(callback)
358 self.plugin_hooks[name] = h
360 def run_hook(self, name, args):
361 for cb in self.plugin_hooks.get(name,[]):
366 QMainWindow.close(self)
367 self.run_hook('close_main_window', (self,))
369 def connect_slots(self, sender):
370 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
371 self.previous_payto_e=''
373 def timer_actions(self):
374 self.run_hook('timer_actions', (self,))
376 if self.payto_e.hasFocus():
378 r = unicode( self.payto_e.text() )
379 if r != self.previous_payto_e:
380 self.previous_payto_e = r
382 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
384 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
388 s = r + ' <' + to_address + '>'
389 self.payto_e.setText(s)
393 def update_status(self):
394 if self.wallet.interface and self.wallet.interface.is_connected:
395 if not self.wallet.up_to_date:
396 text = _("Synchronizing...")
397 icon = QIcon(":icons/status_waiting.png")
399 c, u = self.wallet.get_balance()
400 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
401 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
402 text += self.create_quote_text(Decimal(c+u)/100000000)
403 icon = QIcon(":icons/status_connected.png")
405 text = _("Not connected")
406 icon = QIcon(":icons/status_disconnected.png")
408 self.status_text = text
409 self.statusBar().showMessage(text)
410 self.status_button.setIcon( icon )
412 def update_wallet(self):
414 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
415 self.update_history_tab()
416 self.update_receive_tab()
417 self.update_contacts_tab()
418 self.update_completions()
421 def create_quote_text(self, btc_balance):
422 quote_currency = self.config.get("currency", "None")
423 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
424 if quote_balance is None:
427 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
430 def create_history_tab(self):
431 self.history_list = l = MyTreeWidget(self)
433 for i,width in enumerate(self.column_widths['history']):
434 l.setColumnWidth(i, width)
435 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
436 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
437 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
439 l.setContextMenuPolicy(Qt.CustomContextMenu)
440 l.customContextMenuRequested.connect(self.create_history_menu)
444 def create_history_menu(self, position):
445 self.history_list.selectedIndexes()
446 item = self.history_list.currentItem()
448 tx_hash = str(item.data(0, Qt.UserRole).toString())
449 if not tx_hash: return
451 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
452 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
453 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
454 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
457 def show_tx_details(self, tx):
458 dialog = QDialog(self)
460 dialog.setWindowTitle(_("Transaction Details"))
462 dialog.setLayout(vbox)
463 dialog.setMinimumSize(600,300)
466 if tx_hash in self.wallet.transactions.keys():
467 is_mine, v, fee = self.wallet.get_tx_value(tx)
468 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
470 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
476 vbox.addWidget(QLabel("Transaction ID:"))
477 e = QLineEdit(tx_hash)
481 vbox.addWidget(QLabel("Date: %s"%time_str))
482 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
485 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
486 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
488 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
489 vbox.addWidget(QLabel("Transaction fee: unknown"))
491 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
493 vbox.addWidget( self.generate_transaction_information_widget(tx) )
495 ok_button = QPushButton(_("Close"))
496 ok_button.setDefault(True)
497 ok_button.clicked.connect(dialog.accept)
501 hbox.addWidget(ok_button)
505 def tx_label_clicked(self, item, column):
506 if column==2 and item.isSelected():
508 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
509 self.history_list.editItem( item, column )
510 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
513 def tx_label_changed(self, item, column):
517 tx_hash = str(item.data(0, Qt.UserRole).toString())
518 tx = self.wallet.transactions.get(tx_hash)
519 s = self.wallet.labels.get(tx_hash)
520 text = unicode( item.text(2) )
522 self.wallet.labels[tx_hash] = text
523 item.setForeground(2, QBrush(QColor('black')))
525 if s: self.wallet.labels.pop(tx_hash)
526 text = self.wallet.get_default_label(tx_hash)
527 item.setText(2, text)
528 item.setForeground(2, QBrush(QColor('gray')))
532 def edit_label(self, is_recv):
533 l = self.receive_list if is_recv else self.contacts_list
534 item = l.currentItem()
535 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
536 l.editItem( item, 1 )
537 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
541 def address_label_clicked(self, item, column, l, column_addr, column_label):
542 if column == column_label and item.isSelected():
543 addr = unicode( item.text(column_addr) )
544 label = unicode( item.text(column_label) )
545 if label in self.wallet.aliases.keys():
547 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
548 l.editItem( item, column )
549 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
552 def address_label_changed(self, item, column, l, column_addr, column_label):
554 if column == column_label:
555 addr = unicode( item.text(column_addr) )
556 text = unicode( item.text(column_label) )
560 if text not in self.wallet.aliases.keys():
561 old_addr = self.wallet.labels.get(text)
563 self.wallet.labels[addr] = text
566 print_error("Error: This is one of your aliases")
567 label = self.wallet.labels.get(addr,'')
568 item.setText(column_label, QString(label))
570 s = self.wallet.labels.get(addr)
572 self.wallet.labels.pop(addr)
576 self.update_history_tab()
577 self.update_completions()
579 self.current_item_changed(item)
581 self.run_hook('item_changed',(self, item, column))
584 def current_item_changed(self, a):
585 self.run_hook('current_item_changed',(self, a))
589 def update_history_tab(self):
591 self.history_list.clear()
592 for item in self.wallet.get_tx_history():
593 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
596 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
602 icon = QIcon(":icons/unconfirmed.png")
604 icon = QIcon(":icons/clock%d.png"%conf)
606 icon = QIcon(":icons/confirmed.png")
609 icon = QIcon(":icons/unconfirmed.png")
611 if value is not None:
612 v_str = format_satoshis(value, True, self.wallet.num_zeros)
616 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
619 label, is_default_label = self.wallet.get_label(tx_hash)
621 label = _('Pruned transaction outputs')
622 is_default_label = False
624 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
625 item.setFont(2, QFont(MONOSPACE_FONT))
626 item.setFont(3, QFont(MONOSPACE_FONT))
627 item.setFont(4, QFont(MONOSPACE_FONT))
629 item.setForeground(3, QBrush(QColor("#BC1E1E")))
631 item.setData(0, Qt.UserRole, tx_hash)
632 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
634 item.setForeground(2, QBrush(QColor('grey')))
636 item.setIcon(0, icon)
637 self.history_list.insertTopLevelItem(0,item)
640 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
643 def create_send_tab(self):
648 grid.setColumnMinimumWidth(3,300)
649 grid.setColumnStretch(5,1)
651 self.payto_e = QLineEdit()
652 grid.addWidget(QLabel(_('Pay to')), 1, 0)
653 grid.addWidget(self.payto_e, 1, 1, 1, 3)
655 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)
657 completer = QCompleter()
658 completer.setCaseSensitivity(False)
659 self.payto_e.setCompleter(completer)
660 completer.setModel(self.completions)
662 self.message_e = QLineEdit()
663 grid.addWidget(QLabel(_('Description')), 2, 0)
664 grid.addWidget(self.message_e, 2, 1, 1, 3)
665 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)
667 self.amount_e = QLineEdit()
668 grid.addWidget(QLabel(_('Amount')), 3, 0)
669 grid.addWidget(self.amount_e, 3, 1, 1, 2)
670 grid.addWidget(HelpButton(
671 _('Amount to be sent.') + '\n\n' \
672 + _('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)
674 self.fee_e = QLineEdit()
675 grid.addWidget(QLabel(_('Fee')), 4, 0)
676 grid.addWidget(self.fee_e, 4, 1, 1, 2)
677 grid.addWidget(HelpButton(
678 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
679 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
680 + _('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)
683 b = EnterButton(_("Send"), self.do_send)
685 b = EnterButton(_("Create unsigned transaction"), self.do_send)
686 grid.addWidget(b, 6, 1)
688 b = EnterButton(_("Clear"),self.do_clear)
689 grid.addWidget(b, 6, 2)
691 self.payto_sig = QLabel('')
692 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
694 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
695 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
704 def entry_changed( is_fee ):
705 self.funds_error = False
706 amount = numbify(self.amount_e)
707 fee = numbify(self.fee_e)
708 if not is_fee: fee = None
711 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
713 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
716 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
717 text = self.status_text
720 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
721 self.funds_error = True
722 text = _( "Not enough funds" )
724 self.statusBar().showMessage(text)
725 self.amount_e.setPalette(palette)
726 self.fee_e.setPalette(palette)
728 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
729 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
731 self.run_hook('create_send_tab',(self,grid))
735 def update_completions(self):
737 for addr,label in self.wallet.labels.items():
738 if addr in self.wallet.addressbook:
739 l.append( label + ' <' + addr + '>')
740 l = l + self.wallet.aliases.keys()
742 self.completions.setStringList(l)
746 return lambda s, *args: s.do_protect(func, args)
750 def do_send(self, password):
752 label = unicode( self.message_e.text() )
753 r = unicode( self.payto_e.text() )
757 m1 = re.match(ALIAS_REGEXP, r)
758 # label or alias, with address in brackets
759 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
762 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
766 to_address = m2.group(2)
770 if not is_valid(to_address):
771 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
775 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
777 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
780 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
782 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
786 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
787 except BaseException, e:
788 self.show_message(str(e))
791 self.run_hook('send_tx', (self.wallet, self, tx))
794 self.wallet.labels[tx.hash()] = label
797 h = self.wallet.send_tx(tx)
798 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
799 status, msg = self.wallet.receive_tx( h )
801 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
803 self.update_contacts_tab()
805 QMessageBox.warning(self, _('Error'), msg, _('OK'))
807 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
809 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
810 with open(fileName,'w') as f:
811 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
812 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
814 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
819 def set_url(self, url):
820 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
821 self.tabs.setCurrentIndex(1)
822 label = self.wallet.labels.get(payto)
823 m_addr = label + ' <'+ payto+'>' if label else payto
824 self.payto_e.setText(m_addr)
826 self.message_e.setText(message)
827 self.amount_e.setText(amount)
829 self.set_frozen(self.payto_e,True)
830 self.set_frozen(self.amount_e,True)
831 self.set_frozen(self.message_e,True)
832 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
834 self.payto_sig.setVisible(False)
837 self.payto_sig.setVisible(False)
838 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
840 self.set_frozen(e,False)
842 def set_frozen(self,entry,frozen):
844 entry.setReadOnly(True)
845 entry.setFrame(False)
847 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
848 entry.setPalette(palette)
850 entry.setReadOnly(False)
853 palette.setColor(entry.backgroundRole(), QColor('white'))
854 entry.setPalette(palette)
857 def toggle_freeze(self,addr):
859 if addr in self.wallet.frozen_addresses:
860 self.wallet.unfreeze(addr)
862 self.wallet.freeze(addr)
863 self.update_receive_tab()
865 def toggle_priority(self,addr):
867 if addr in self.wallet.prioritized_addresses:
868 self.wallet.unprioritize(addr)
870 self.wallet.prioritize(addr)
871 self.update_receive_tab()
874 def create_list_tab(self, headers):
875 "generic tab creation method"
876 l = MyTreeWidget(self)
877 l.setColumnCount( len(headers) )
878 l.setHeaderLabels( headers )
888 vbox.addWidget(buttons)
893 buttons.setLayout(hbox)
898 def create_receive_tab(self):
899 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
900 l.setContextMenuPolicy(Qt.CustomContextMenu)
901 l.customContextMenuRequested.connect(self.create_receive_menu)
902 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
903 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
904 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
905 self.receive_list = l
906 self.receive_buttons_hbox = hbox
911 def receive_tab_set_mode(self, i):
912 self.save_column_widths()
913 self.expert_mode = (i == 1)
914 self.config.set_key('classic_expert_mode', self.expert_mode, True)
916 self.update_receive_tab()
919 def save_column_widths(self):
920 if not self.expert_mode:
921 widths = [ self.receive_list.columnWidth(0) ]
924 for i in range(self.receive_list.columnCount() -1):
925 widths.append(self.receive_list.columnWidth(i))
926 self.column_widths["receive"][self.expert_mode] = widths
928 self.column_widths["history"] = []
929 for i in range(self.history_list.columnCount() - 1):
930 self.column_widths["history"].append(self.history_list.columnWidth(i))
932 self.column_widths["contacts"] = []
933 for i in range(self.contacts_list.columnCount() - 1):
934 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
937 def create_contacts_tab(self):
938 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
939 l.setContextMenuPolicy(Qt.CustomContextMenu)
940 l.customContextMenuRequested.connect(self.create_contact_menu)
941 for i,width in enumerate(self.column_widths['contacts']):
942 l.setColumnWidth(i, width)
944 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
945 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
946 self.contacts_list = l
947 self.contacts_buttons_hbox = hbox
948 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
953 def delete_imported_key(self, addr):
954 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
955 self.wallet.imported_keys.pop(addr)
956 self.update_receive_tab()
957 self.update_history_tab()
961 def create_receive_menu(self, position):
962 # fixme: this function apparently has a side effect.
963 # if it is not called the menu pops up several times
964 #self.receive_list.selectedIndexes()
966 item = self.receive_list.itemAt(position)
968 addr = unicode(item.text(0))
969 if not is_valid(addr):
970 item.setExpanded(not item.isExpanded())
973 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
974 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
975 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
976 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
977 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
978 if addr in self.wallet.imported_keys:
979 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
982 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
983 menu.addAction(t, lambda: self.toggle_freeze(addr))
984 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
985 menu.addAction(t, lambda: self.toggle_priority(addr))
987 self.run_hook('receive_menu', (self, menu,))
988 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
991 def payto(self, x, is_alias):
998 label = self.wallet.labels.get(addr)
999 m_addr = label + ' <' + addr + '>' if label else addr
1000 self.tabs.setCurrentIndex(1)
1001 self.payto_e.setText(m_addr)
1002 self.amount_e.setFocus()
1004 def delete_contact(self, x, is_alias):
1005 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1006 if not is_alias and x in self.wallet.addressbook:
1007 self.wallet.addressbook.remove(x)
1008 if x in self.wallet.labels.keys():
1009 self.wallet.labels.pop(x)
1010 elif is_alias and x in self.wallet.aliases:
1011 self.wallet.aliases.pop(x)
1012 self.update_history_tab()
1013 self.update_contacts_tab()
1014 self.update_completions()
1016 def create_contact_menu(self, position):
1017 # fixme: this function apparently has a side effect.
1018 # if it is not called the menu pops up several times
1019 #self.contacts_list.selectedIndexes()
1021 item = self.contacts_list.itemAt(position)
1023 addr = unicode(item.text(0))
1024 label = unicode(item.text(1))
1025 is_alias = label in self.wallet.aliases.keys()
1026 x = label if is_alias else addr
1028 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1029 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1030 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1032 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1034 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1035 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1036 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1039 def update_receive_item(self, item):
1040 item.setFont(0, QFont(MONOSPACE_FONT))
1041 address = str(item.data(0,0).toString())
1042 label = self.wallet.labels.get(address,'')
1043 item.setData(1,0,label)
1045 self.run_hook('update_receive_item', (self, address, item))
1047 c, u = self.wallet.get_addr_balance(address)
1048 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1049 item.setData(2,0,balance)
1051 if self.expert_mode:
1052 if address in self.wallet.frozen_addresses:
1053 item.setBackgroundColor(0, QColor('lightblue'))
1054 elif address in self.wallet.prioritized_addresses:
1055 item.setBackgroundColor(0, QColor('lightgreen'))
1058 def update_receive_tab(self):
1059 l = self.receive_list
1062 l.setColumnHidden(2, not self.expert_mode)
1063 l.setColumnHidden(3, not self.expert_mode)
1064 if not self.expert_mode:
1065 width = self.column_widths['receive'][0][0]
1066 l.setColumnWidth(0, width)
1068 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1069 l.setColumnWidth(i, width)
1072 for k, account in self.wallet.accounts.items():
1073 name = account.get('name',str(k))
1074 c,u = self.wallet.get_account_balance(k)
1075 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1076 l.addTopLevelItem(account_item)
1077 account_item.setExpanded(True)
1080 for is_change in [0,1]:
1081 name = "Receiving" if not is_change else "Change"
1082 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1083 account_item.addChild(seq_item)
1084 if not is_change: seq_item.setExpanded(True)
1088 for address in account[is_change]:
1089 h = self.wallet.history.get(address,[])
1094 if gap > self.wallet.gap_limit:
1099 num_tx = '*' if h == ['*'] else "%d"%len(h)
1100 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1101 self.update_receive_item(item)
1103 item.setBackgroundColor(1, QColor('red'))
1104 seq_item.addChild(item)
1106 if self.wallet.imported_keys:
1107 c,u = self.wallet.get_imported_balance()
1108 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1109 l.addTopLevelItem(account_item)
1110 account_item.setExpanded(True)
1111 for address in self.wallet.imported_keys.keys():
1112 item = QTreeWidgetItem( [ address, '', '', ''] )
1113 self.update_receive_item(item)
1114 account_item.addChild(item)
1117 # we use column 1 because column 0 may be hidden
1118 l.setCurrentItem(l.topLevelItem(0),1)
1120 def show_contact_details(self, m):
1121 a = self.wallet.aliases.get(m)
1123 if a[0] in self.wallet.authorities.keys():
1124 s = self.wallet.authorities.get(a[0])
1127 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1128 QMessageBox.information(self, 'Alias', msg, 'OK')
1130 def update_contacts_tab(self):
1132 l = self.contacts_list
1136 for alias, v in self.wallet.aliases.items():
1138 alias_targets.append(target)
1139 item = QTreeWidgetItem( [ target, alias, '-'] )
1140 item.setBackgroundColor(0, QColor('lightgray'))
1141 l.addTopLevelItem(item)
1143 for address in self.wallet.addressbook:
1144 if address in alias_targets: continue
1145 label = self.wallet.labels.get(address,'')
1147 for tx in self.wallet.transactions.values():
1148 if address in map(lambda x: x[0], tx.outputs): n += 1
1150 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1151 item.setFont(0, QFont(MONOSPACE_FONT))
1152 l.addTopLevelItem(item)
1154 l.setCurrentItem(l.topLevelItem(0))
1157 def create_console_tab(self):
1158 from qt_console import Console
1159 self.console = console = Console()
1160 self.console.history = self.config.get("console-history",[])
1161 self.console.history_index = len(self.console.history)
1163 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1164 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1166 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1168 def mkfunc(f, method):
1169 return lambda *args: apply( f, (method, args, self.password_dialog ))
1171 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1172 methods[m] = mkfunc(c._run, m)
1174 console.updateNamespace(methods)
1178 def create_status_bar(self):
1179 self.status_text = ""
1181 sb.setFixedHeight(35)
1182 qtVersion = qVersion()
1184 update_notification = UpdateLabel(self.config)
1185 if(update_notification.new_version):
1186 sb.addPermanentWidget(update_notification)
1188 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1189 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1190 if self.wallet.seed:
1191 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1192 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1193 if self.wallet.seed:
1194 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1195 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1196 sb.addPermanentWidget( self.status_button )
1198 self.setStatusBar(sb)
1202 self.config.set_key('gui', 'lite', True)
1205 self.lite.mini.show()
1207 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1208 self.lite.main(None)
1210 def new_contact_dialog(self):
1211 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1212 address = unicode(text)
1214 if is_valid(address):
1215 self.wallet.addressbook.append(address)
1217 self.update_contacts_tab()
1218 self.update_history_tab()
1219 self.update_completions()
1221 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1223 def show_master_public_key(self):
1224 dialog = QDialog(self)
1226 dialog.setWindowTitle(_("Master Public Key"))
1228 main_text = QTextEdit()
1229 main_text.setText(self.wallet.get_master_public_key())
1230 main_text.setReadOnly(True)
1231 main_text.setMaximumHeight(170)
1232 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1234 ok_button = QPushButton(_("OK"))
1235 ok_button.setDefault(True)
1236 ok_button.clicked.connect(dialog.accept)
1238 main_layout = QGridLayout()
1239 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1241 main_layout.addWidget(main_text, 1, 0)
1242 main_layout.addWidget(qrw, 1, 1 )
1244 vbox = QVBoxLayout()
1245 vbox.addLayout(main_layout)
1246 hbox = QHBoxLayout()
1248 hbox.addWidget(ok_button)
1249 vbox.addLayout(hbox)
1251 dialog.setLayout(vbox)
1256 def show_seed_dialog(self, password):
1257 if not self.wallet.seed:
1258 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1261 seed = self.wallet.decode_seed(password)
1263 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1265 self.show_seed(seed, self)
1269 def show_seed(self, seed, parent=None):
1270 dialog = QDialog(parent)
1272 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1274 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1276 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1278 seed_text = QTextEdit(brainwallet)
1279 seed_text.setReadOnly(True)
1280 seed_text.setMaximumHeight(130)
1282 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1283 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1284 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1285 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1286 label2 = QLabel(msg2)
1287 label2.setWordWrap(True)
1290 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1291 logo.setMaximumWidth(60)
1293 qrw = QRCodeWidget(seed)
1295 ok_button = QPushButton(_("OK"))
1296 ok_button.setDefault(True)
1297 ok_button.clicked.connect(dialog.accept)
1299 grid = QGridLayout()
1300 #main_layout.addWidget(logo, 0, 0)
1302 grid.addWidget(logo, 0, 0)
1303 grid.addWidget(label1, 0, 1)
1305 grid.addWidget(seed_text, 1, 0, 1, 2)
1307 grid.addWidget(qrw, 0, 2, 2, 1)
1309 vbox = QVBoxLayout()
1310 vbox.addLayout(grid)
1311 vbox.addWidget(label2)
1313 hbox = QHBoxLayout()
1315 hbox.addWidget(ok_button)
1316 vbox.addLayout(hbox)
1318 dialog.setLayout(vbox)
1321 def show_qrcode(self, data, title = "QR code"):
1325 d.setWindowTitle(title)
1326 d.setMinimumSize(270, 300)
1327 vbox = QVBoxLayout()
1328 qrw = QRCodeWidget(data)
1329 vbox.addWidget(qrw, 1)
1330 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1331 hbox = QHBoxLayout()
1335 filename = "qrcode.bmp"
1336 bmp.save_qrcode(qrw.qr, filename)
1337 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1339 b = QPushButton(_("Save"))
1341 b.clicked.connect(print_qr)
1343 b = QPushButton(_("Close"))
1345 b.clicked.connect(d.accept)
1348 vbox.addLayout(hbox)
1353 def do_protect(self, func, args):
1354 if self.wallet.use_encryption:
1355 password = self.password_dialog()
1361 if args != (False,):
1362 args = (self,) + args + (password,)
1364 args = (self,password)
1369 def show_private_key(self, address, password):
1370 if not address: return
1372 pk = self.wallet.get_private_key(address, password)
1373 except BaseException, e:
1374 self.show_message(str(e))
1376 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1380 def do_sign(self, address, message, signature, password):
1382 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1383 signature.setText(sig)
1384 except BaseException, e:
1385 self.show_message(str(e))
1387 def sign_message(self, address):
1388 if not address: return
1391 d.setWindowTitle(_('Sign Message'))
1392 d.setMinimumSize(410, 290)
1394 tab_widget = QTabWidget()
1396 layout = QGridLayout(tab)
1398 sign_address = QLineEdit()
1400 sign_address.setText(address)
1401 layout.addWidget(QLabel(_('Address')), 1, 0)
1402 layout.addWidget(sign_address, 1, 1)
1404 sign_message = QTextEdit()
1405 layout.addWidget(QLabel(_('Message')), 2, 0)
1406 layout.addWidget(sign_message, 2, 1)
1407 layout.setRowStretch(2,3)
1409 sign_signature = QTextEdit()
1410 layout.addWidget(QLabel(_('Signature')), 3, 0)
1411 layout.addWidget(sign_signature, 3, 1)
1412 layout.setRowStretch(3,1)
1415 hbox = QHBoxLayout()
1416 b = QPushButton(_("Sign"))
1418 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1419 b = QPushButton(_("Close"))
1420 b.clicked.connect(d.accept)
1422 layout.addLayout(hbox, 4, 1)
1423 tab_widget.addTab(tab, _("Sign"))
1427 layout = QGridLayout(tab)
1429 verify_address = QLineEdit()
1430 layout.addWidget(QLabel(_('Address')), 1, 0)
1431 layout.addWidget(verify_address, 1, 1)
1433 verify_message = QTextEdit()
1434 layout.addWidget(QLabel(_('Message')), 2, 0)
1435 layout.addWidget(verify_message, 2, 1)
1436 layout.setRowStretch(2,3)
1438 verify_signature = QTextEdit()
1439 layout.addWidget(QLabel(_('Signature')), 3, 0)
1440 layout.addWidget(verify_signature, 3, 1)
1441 layout.setRowStretch(3,1)
1445 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1446 self.show_message(_("Signature verified"))
1447 except BaseException, e:
1448 self.show_message(str(e))
1451 hbox = QHBoxLayout()
1452 b = QPushButton(_("Verify"))
1453 b.clicked.connect(do_verify)
1455 b = QPushButton(_("Close"))
1456 b.clicked.connect(d.accept)
1458 layout.addLayout(hbox, 4, 1)
1459 tab_widget.addTab(tab, _("Verify"))
1461 vbox = QVBoxLayout()
1462 vbox.addWidget(tab_widget)
1469 def question(self, msg):
1470 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1472 def show_message(self, msg):
1473 QMessageBox.information(self, _('Message'), msg, _('OK'))
1475 def password_dialog(self ):
1482 vbox = QVBoxLayout()
1483 msg = _('Please enter your password')
1484 vbox.addWidget(QLabel(msg))
1486 grid = QGridLayout()
1488 grid.addWidget(QLabel(_('Password')), 1, 0)
1489 grid.addWidget(pw, 1, 1)
1490 vbox.addLayout(grid)
1492 vbox.addLayout(ok_cancel_buttons(d))
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)
1565 def seed_dialog(wallet, parent=None):
1569 vbox = QVBoxLayout()
1570 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1571 vbox.addWidget(QLabel(msg))
1573 grid = QGridLayout()
1576 seed_e = QLineEdit()
1577 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1578 grid.addWidget(seed_e, 1, 1)
1582 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1583 grid.addWidget(gap_e, 2, 1)
1584 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1585 vbox.addLayout(grid)
1587 vbox.addLayout(ok_cancel_buttons(d))
1590 if not d.exec_(): return
1593 gap = int(unicode(gap_e.text()))
1595 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1599 seed = str(seed_e.text())
1602 print_error("Warning: Not hex, trying decode")
1604 seed = mnemonic.mn_decode( seed.split(' ') )
1606 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1610 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1615 def generate_transaction_information_widget(self, tx):
1616 tabs = QTabWidget(self)
1619 grid_ui = QGridLayout(tab1)
1620 grid_ui.setColumnStretch(0,1)
1621 tabs.addTab(tab1, _('Outputs') )
1623 tree_widget = MyTreeWidget(self)
1624 tree_widget.setColumnCount(2)
1625 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1626 tree_widget.setColumnWidth(0, 300)
1627 tree_widget.setColumnWidth(1, 50)
1629 for address, value in tx.outputs:
1630 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1631 tree_widget.addTopLevelItem(item)
1633 tree_widget.setMaximumHeight(100)
1635 grid_ui.addWidget(tree_widget)
1638 grid_ui = QGridLayout(tab2)
1639 grid_ui.setColumnStretch(0,1)
1640 tabs.addTab(tab2, _('Inputs') )
1642 tree_widget = MyTreeWidget(self)
1643 tree_widget.setColumnCount(2)
1644 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1646 for input_line in tx.inputs:
1647 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1648 tree_widget.addTopLevelItem(item)
1650 tree_widget.setMaximumHeight(100)
1652 grid_ui.addWidget(tree_widget)
1656 def tx_dict_from_text(self, txt):
1658 tx_dict = json.loads(str(txt))
1659 assert "hex" in tx_dict.keys()
1660 assert "complete" in tx_dict.keys()
1661 if not tx_dict["complete"]:
1662 assert "input_info" in tx_dict.keys()
1664 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1669 def read_tx_from_file(self):
1670 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1674 with open(fileName, "r") as f:
1675 file_content = f.read()
1676 except (ValueError, IOError, os.error), reason:
1677 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1679 return self.tx_dict_from_text(file_content)
1683 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1685 self.wallet.signrawtransaction(tx, input_info, [], password)
1687 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1689 with open(fileName, "w+") as f:
1690 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1691 self.show_message(_("Transaction saved succesfully"))
1694 except BaseException, e:
1695 self.show_message(str(e))
1698 def send_raw_transaction(self, raw_tx, dialog = ""):
1699 result, result_message = self.wallet.sendtx( raw_tx )
1701 self.show_message("Transaction succesfully sent: %s" % (result_message))
1705 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1707 def do_process_from_text(self):
1708 dialog = QDialog(self)
1709 dialog.setMinimumWidth(500)
1710 dialog.setWindowTitle(_('Input raw transaction'))
1714 l.addWidget(QLabel(_("Transaction:")))
1718 ok_button = QPushButton(_("Load transaction"))
1719 ok_button.setDefault(True)
1720 ok_button.clicked.connect(dialog.accept)
1721 l.addWidget(ok_button)
1724 tx_dict = self.tx_dict_from_text(unicode(txt.toPlainText()))
1726 self.create_process_transaction_window(tx_dict)
1728 def do_process_from_file(self):
1729 tx_dict = self.read_tx_from_file()
1731 self.create_process_transaction_window(tx_dict)
1733 def create_process_transaction_window(self, tx_dict):
1734 tx = Transaction(tx_dict["hex"])
1736 dialog = QDialog(self)
1737 dialog.setMinimumWidth(500)
1738 dialog.setWindowTitle(_('Process raw transaction'))
1744 l.addWidget(QLabel(_("Transaction status: ")), 3,0)
1745 l.addWidget(QLabel(_("Actions")), 4,0)
1747 if tx_dict["complete"] == False:
1748 l.addWidget(QLabel(_("Unsigned")), 3,1)
1749 if self.wallet.seed :
1750 b = QPushButton("Sign transaction")
1751 input_info = json.loads(tx_dict["input_info"])
1752 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1753 l.addWidget(b, 4, 1)
1755 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1757 l.addWidget(QLabel(_("Signed")), 3,1)
1758 b = QPushButton("Broadcast transaction")
1759 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1762 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1763 cancelButton = QPushButton(_("Cancel"))
1764 cancelButton.clicked.connect(lambda: dialog.done(0))
1765 l.addWidget(cancelButton, 4,2)
1771 def do_export_privkeys(self, password):
1772 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.")))
1775 select_export = _('Select file to export your private keys to')
1776 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1778 with open(fileName, "w+") as csvfile:
1779 transaction = csv.writer(csvfile)
1780 transaction.writerow(["address", "private_key"])
1783 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1784 transaction.writerow(["%34s"%addr,pk])
1786 self.show_message(_("Private keys exported."))
1788 except (IOError, os.error), reason:
1789 export_error_label = _("Electrum was unable to produce a private key-export.")
1790 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1792 except BaseException, e:
1793 self.show_message(str(e))
1797 def do_import_labels(self):
1798 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1799 if not labelsFile: return
1801 f = open(labelsFile, 'r')
1804 for key, value in json.loads(data).items():
1805 self.wallet.labels[key] = value
1807 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1808 except (IOError, os.error), reason:
1809 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1812 def do_export_labels(self):
1813 labels = self.wallet.labels
1815 labelsFile = util.user_dir() + '/labels.dat'
1816 f = open(labelsFile, 'w+')
1817 json.dump(labels, f)
1819 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1820 except (IOError, os.error), reason:
1821 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1824 def do_export_history(self):
1825 from gui_lite import csv_transaction
1826 csv_transaction(self.wallet)
1830 def do_import_privkey(self, password):
1831 if not self.wallet.imported_keys:
1832 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1833 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1834 + _('Are you sure you understand what you are doing?'), 3, 4)
1837 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1839 sec = str(text).strip()
1841 addr = self.wallet.import_key(sec, password)
1843 QMessageBox.critical(None, _("Unable to import key"), "error")
1845 QMessageBox.information(None, _("Key imported"), addr)
1846 self.update_receive_tab()
1847 self.update_history_tab()
1848 except BaseException as e:
1849 QMessageBox.critical(None, _("Unable to import key"), str(e))
1852 def settings_dialog(self):
1854 d.setWindowTitle(_('Electrum Settings'))
1856 vbox = QVBoxLayout()
1858 tabs = QTabWidget(self)
1859 vbox.addWidget(tabs)
1862 grid_ui = QGridLayout(tab1)
1863 grid_ui.setColumnStretch(0,1)
1864 tabs.addTab(tab1, _('Display') )
1866 nz_label = QLabel(_('Display zeros'))
1867 grid_ui.addWidget(nz_label, 3, 0)
1869 nz_e.setText("%d"% self.wallet.num_zeros)
1870 grid_ui.addWidget(nz_e, 3, 1)
1871 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1872 grid_ui.addWidget(HelpButton(msg), 3, 2)
1873 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1874 if not self.config.is_modifiable('num_zeros'):
1875 for w in [nz_e, nz_label]: w.setEnabled(False)
1877 lang_label=QLabel(_('Language') + ':')
1878 grid_ui.addWidget(lang_label , 8, 0)
1879 lang_combo = QComboBox()
1880 from i18n import languages
1881 lang_combo.addItems(languages.values())
1883 index = languages.keys().index(self.config.get("language",''))
1886 lang_combo.setCurrentIndex(index)
1887 grid_ui.addWidget(lang_combo, 8, 1)
1888 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1889 if not self.config.is_modifiable('language'):
1890 for w in [lang_combo, lang_label]: w.setEnabled(False)
1892 currencies = self.exchanger.get_currencies()
1893 currencies.insert(0, "None")
1895 cur_label=QLabel(_('Currency') + ':')
1896 grid_ui.addWidget(cur_label , 9, 0)
1897 cur_combo = QComboBox()
1898 cur_combo.addItems(currencies)
1900 index = currencies.index(self.config.get('currency', "None"))
1903 cur_combo.setCurrentIndex(index)
1904 grid_ui.addWidget(cur_combo, 9, 1)
1905 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1907 view_label=QLabel(_('Receive Tab') + ':')
1908 grid_ui.addWidget(view_label , 10, 0)
1909 view_combo = QComboBox()
1910 view_combo.addItems([_('Simple'), _('Advanced')])
1911 view_combo.setCurrentIndex(self.expert_mode)
1912 grid_ui.addWidget(view_combo, 10, 1)
1913 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1914 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1915 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1917 grid_ui.addWidget(HelpButton(hh), 10, 2)
1921 grid_wallet = QGridLayout(tab2)
1922 grid_wallet.setColumnStretch(0,1)
1923 tabs.addTab(tab2, _('Wallet') )
1925 grid_wallet.addWidget(QLabel(_("Load raw transaction")), 3, 0)
1926 grid_wallet.addWidget(EnterButton(_("From file"), self.do_process_from_file),3,1)
1927 grid_wallet.addWidget(EnterButton(_("From text"), self.do_process_from_text),3,2)
1928 grid_wallet.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")),3,3)
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 transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1936 + _('Recommended value') + ': 0.001'
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)
1998 grid_io.setRowStretch(4,1)
2004 grid_plugins = QGridLayout(tab5)
2005 grid_plugins.setColumnStretch(0,1)
2006 tabs.addTab(tab5, _('Plugins') )
2007 def mk_toggle(cb, p):
2008 return lambda: cb.setChecked(p.toggle(self))
2009 for i, p in enumerate(self.plugins):
2011 name, description = p.get_info()
2012 cb = QCheckBox(name)
2013 cb.setChecked(p.is_enabled())
2014 cb.stateChanged.connect(mk_toggle(cb,p))
2015 grid_plugins.addWidget(cb, i, 0)
2016 grid_plugins.addWidget(HelpButton(description), i, 2)
2018 print_msg("Error: cannot display plugin", p)
2019 traceback.print_exc(file=sys.stdout)
2020 grid_plugins.setRowStretch(i+1,1)
2022 vbox.addLayout(ok_cancel_buttons(d))
2026 if not d.exec_(): return
2028 fee = unicode(fee_e.text())
2030 fee = int( 100000000 * Decimal(fee) )
2032 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2035 if self.wallet.fee != fee:
2036 self.wallet.fee = fee
2039 nz = unicode(nz_e.text())
2044 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2047 if self.wallet.num_zeros != nz:
2048 self.wallet.num_zeros = nz
2049 self.config.set_key('num_zeros', nz, True)
2050 self.update_history_tab()
2051 self.update_receive_tab()
2053 usechange_result = usechange_combo.currentIndex() == 0
2054 if self.wallet.use_change != usechange_result:
2055 self.wallet.use_change = usechange_result
2056 self.config.set_key('use_change', self.wallet.use_change, True)
2059 n = int(gap_e.text())
2061 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2064 if self.wallet.gap_limit != n:
2065 r = self.wallet.change_gap_limit(n)
2067 self.update_receive_tab()
2068 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2070 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2072 need_restart = False
2074 lang_request = languages.keys()[lang_combo.currentIndex()]
2075 if lang_request != self.config.get('language'):
2076 self.config.set_key("language", lang_request, True)
2079 cur_request = str(currencies[cur_combo.currentIndex()])
2080 if cur_request != self.config.get('currency', "None"):
2081 self.config.set_key('currency', cur_request, True)
2082 self.update_wallet()
2085 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2087 self.receive_tab_set_mode(view_combo.currentIndex())
2091 def network_dialog(wallet, parent=None):
2092 interface = wallet.interface
2094 if interface.is_connected:
2095 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2097 status = _("Not connected")
2098 server = interface.server
2101 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2102 server = interface.server
2104 plist, servers_list = interface.get_servers_list()
2108 d.setWindowTitle(_('Server'))
2109 d.setMinimumSize(375, 20)
2111 vbox = QVBoxLayout()
2114 hbox = QHBoxLayout()
2116 l.setPixmap(QPixmap(":icons/network.png"))
2119 hbox.addWidget(QLabel(status))
2121 vbox.addLayout(hbox)
2125 grid = QGridLayout()
2127 vbox.addLayout(grid)
2130 server_protocol = QComboBox()
2131 server_host = QLineEdit()
2132 server_host.setFixedWidth(200)
2133 server_port = QLineEdit()
2134 server_port.setFixedWidth(60)
2136 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2137 protocol_letters = 'thsg'
2138 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2139 server_protocol.addItems(protocol_names)
2141 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2142 grid.addWidget(server_protocol, 0, 1)
2143 grid.addWidget(server_host, 0, 2)
2144 grid.addWidget(server_port, 0, 3)
2146 def change_protocol(p):
2147 protocol = protocol_letters[p]
2148 host = unicode(server_host.text())
2149 pp = plist.get(host,DEFAULT_PORTS)
2150 if protocol not in pp.keys():
2151 protocol = pp.keys()[0]
2153 server_host.setText( host )
2154 server_port.setText( port )
2156 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2158 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2159 servers_list_widget = QTreeWidget(parent)
2160 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2161 servers_list_widget.setMaximumHeight(150)
2162 servers_list_widget.setColumnWidth(0, 240)
2163 for _host in servers_list.keys():
2164 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2165 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2167 def change_server(host, protocol=None):
2168 pp = plist.get(host,DEFAULT_PORTS)
2170 port = pp.get(protocol)
2171 if not port: protocol = None
2174 if 't' in pp.keys():
2176 port = pp.get(protocol)
2178 protocol = pp.keys()[0]
2179 port = pp.get(protocol)
2181 server_host.setText( host )
2182 server_port.setText( port )
2183 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2185 if not plist: return
2186 for p in protocol_letters:
2187 i = protocol_letters.index(p)
2188 j = server_protocol.model().index(i,0)
2189 if p not in pp.keys():
2190 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2192 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2196 host, port, protocol = server.split(':')
2197 change_server(host,protocol)
2199 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2200 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2202 if not wallet.config.is_modifiable('server'):
2203 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2206 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2207 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2208 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2209 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2212 proxy_mode = QComboBox()
2213 proxy_host = QLineEdit()
2214 proxy_host.setFixedWidth(200)
2215 proxy_port = QLineEdit()
2216 proxy_port.setFixedWidth(60)
2217 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2219 def check_for_disable(index = False):
2220 if proxy_mode.currentText() != 'NONE':
2221 proxy_host.setEnabled(True)
2222 proxy_port.setEnabled(True)
2224 proxy_host.setEnabled(False)
2225 proxy_port.setEnabled(False)
2228 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2230 if not wallet.config.is_modifiable('proxy'):
2231 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2233 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2234 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2235 proxy_host.setText(proxy_config.get("host"))
2236 proxy_port.setText(proxy_config.get("port"))
2238 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2239 grid.addWidget(proxy_mode, 2, 1)
2240 grid.addWidget(proxy_host, 2, 2)
2241 grid.addWidget(proxy_port, 2, 3)
2244 vbox.addLayout(ok_cancel_buttons(d))
2247 if not d.exec_(): return
2249 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2250 if proxy_mode.currentText() != 'NONE':
2251 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2255 wallet.config.set_key("proxy", proxy, True)
2256 wallet.config.set_key("server", server, True)
2257 interface.set_server(server, proxy)
2258 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2261 def closeEvent(self, event):
2263 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2264 self.save_column_widths()
2265 self.config.set_key("column-widths", self.column_widths, True)
2266 self.config.set_key("console-history",self.console.history[-50:])
2272 def __init__(self, wallet, config, app=None):
2273 self.wallet = wallet
2274 self.config = config
2276 self.app = QApplication(sys.argv)
2279 def restore_or_create(self):
2280 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2281 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2282 if r==2: return None
2283 return 'restore' if r==1 else 'create'
2285 def seed_dialog(self):
2286 return ElectrumWindow.seed_dialog( self.wallet )
2288 def network_dialog(self):
2289 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2292 def show_seed(self):
2293 ElectrumWindow.show_seed(self.wallet.seed)
2296 def password_dialog(self):
2297 if self.wallet.seed:
2298 ElectrumWindow.change_password_dialog(self.wallet)
2301 def restore_wallet(self):
2302 wallet = self.wallet
2303 # wait until we are connected, because the user might have selected another server
2304 if not wallet.interface.is_connected:
2305 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2306 waiting_dialog(waiting)
2308 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2309 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2311 wallet.set_up_to_date(False)
2312 wallet.interface.poke('synchronizer')
2313 waiting_dialog(waiting)
2314 if wallet.is_found():
2315 print_error( "Recovery successful" )
2317 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2324 w = ElectrumWindow(self.wallet, self.config)
2325 if url: w.set_url(url)