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
45 import bmp, pyqrnative
48 from decimal import Decimal
56 if platform.system() == 'Windows':
57 MONOSPACE_FONT = 'Lucida Console'
58 elif platform.system() == 'Darwin':
59 MONOSPACE_FONT = 'Monaco'
61 MONOSPACE_FONT = 'monospace'
63 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
65 from electrum import ELECTRUM_VERSION
68 class UpdateLabel(QtGui.QLabel):
69 def __init__(self, config, parent=None):
70 QtGui.QLabel.__init__(self, parent)
71 self.new_version = False
74 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
75 con.request("GET", "/version")
76 res = con.getresponse()
77 except socket.error as msg:
78 print_error("Could not retrieve version information")
82 self.latest_version = res.read()
83 self.latest_version = self.latest_version.replace("\n","")
84 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
86 self.current_version = ELECTRUM_VERSION
87 if(self.compare_versions(self.latest_version, self.current_version) == 1):
88 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
89 if(self.compare_versions(self.latest_version, latest_seen) == 1):
90 self.new_version = True
91 self.setText(_("New version available") + ": " + self.latest_version)
94 def compare_versions(self, version1, version2):
96 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
97 return cmp(normalize(version1), normalize(version2))
99 def ignore_this_version(self):
101 self.config.set_key("last_seen_version", self.latest_version, True)
102 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
105 def ignore_all_version(self):
107 self.config.set_key("last_seen_version", "9.9.9", True)
108 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
111 def open_website(self):
112 webbrowser.open("http://electrum.org/download.html")
115 def mouseReleaseEvent(self, event):
116 dialog = QDialog(self)
117 dialog.setWindowTitle(_('Electrum update'))
120 main_layout = QGridLayout()
121 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
123 ignore_version = QPushButton(_("Ignore this version"))
124 ignore_version.clicked.connect(self.ignore_this_version)
126 ignore_all_versions = QPushButton(_("Ignore all versions"))
127 ignore_all_versions.clicked.connect(self.ignore_all_version)
129 open_website = QPushButton(_("Goto download page"))
130 open_website.clicked.connect(self.open_website)
132 main_layout.addWidget(ignore_version, 1, 0)
133 main_layout.addWidget(ignore_all_versions, 1, 1)
134 main_layout.addWidget(open_website, 1, 2)
136 dialog.setLayout(main_layout)
140 if not dialog.exec_(): return
142 def numbify(entry, is_int = False):
143 text = unicode(entry.text()).strip()
144 pos = entry.cursorPosition()
146 if not is_int: chars +='.'
147 s = ''.join([i for i in text if i in chars])
151 s = s.replace('.','')
152 s = s[:p] + '.' + s[p:p+8]
154 amount = int( Decimal(s) * 100000000 )
163 entry.setCursorPosition(pos)
167 class Timer(QtCore.QThread):
170 self.emit(QtCore.SIGNAL('timersignal'))
173 class HelpButton(QPushButton):
174 def __init__(self, text):
175 QPushButton.__init__(self, '?')
176 self.setFocusPolicy(Qt.NoFocus)
177 self.setFixedWidth(20)
178 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
181 class EnterButton(QPushButton):
182 def __init__(self, text, func):
183 QPushButton.__init__(self, text)
185 self.clicked.connect(func)
187 def keyPressEvent(self, e):
188 if e.key() == QtCore.Qt.Key_Return:
191 class MyTreeWidget(QTreeWidget):
192 def __init__(self, parent):
193 QTreeWidget.__init__(self, parent)
196 for i in range(0,self.viewport().height()/5):
197 if self.itemAt(QPoint(0,i*5)) == item:
201 for j in range(0,30):
202 if self.itemAt(QPoint(0,i*5 + j)) != item:
204 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
206 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
211 class StatusBarButton(QPushButton):
212 def __init__(self, icon, tooltip, func):
213 QPushButton.__init__(self, icon, '')
214 self.setToolTip(tooltip)
216 self.setMaximumWidth(25)
217 self.clicked.connect(func)
220 def keyPressEvent(self, e):
221 if e.key() == QtCore.Qt.Key_Return:
229 def waiting_dialog(f):
235 w.setWindowTitle('Electrum')
245 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
250 def ok_cancel_buttons(dialog):
253 b = QPushButton("OK")
255 b.clicked.connect(dialog.accept)
256 b = QPushButton("Cancel")
258 b.clicked.connect(dialog.reject)
262 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
263 "receive":[[370],[370,200,130,130],[370,200,130,130]] }
265 class ElectrumWindow(QMainWindow):
267 def __init__(self, wallet, config):
268 QMainWindow.__init__(self)
274 self.wallet.interface.register_callback('updated', self.update_callback)
275 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
276 self.wallet.interface.register_callback('disconnected', self.update_callback)
277 self.wallet.interface.register_callback('disconnecting', self.update_callback)
279 self.expert_mode = config.get('classic_expert_mode', False)
280 self.merchant_name = config.get('merchant_name', 'Invoice')
282 set_language(config.get('language'))
284 self.funds_error = False
285 self.completions = QStringListModel()
287 self.tabs = tabs = QTabWidget(self)
288 self.column_widths = self.config.get("column-widths", default_column_widths )
289 tabs.addTab(self.create_history_tab(), _('History') )
290 tabs.addTab(self.create_send_tab(), _('Send') )
291 tabs.addTab(self.create_receive_tab(), _('Receive') )
292 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
293 tabs.addTab(self.create_console_tab(), _('Console') )
294 tabs.setMinimumSize(600, 400)
295 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
296 self.setCentralWidget(tabs)
297 self.create_status_bar()
299 g = self.config.get("winpos-qt",[100, 100, 840, 400])
300 self.setGeometry(g[0], g[1], g[2], g[3])
301 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
302 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
303 self.setWindowTitle( title )
305 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
306 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
307 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
308 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
310 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
311 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
312 self.history_list.setFocus(True)
314 self.exchanger = exchange_rate.Exchanger(self)
315 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
317 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
318 if platform.system() == 'Windows':
319 n = 3 if self.wallet.seed else 2
320 tabs.setCurrentIndex (n)
321 tabs.setCurrentIndex (0)
323 # set initial message
324 self.console.showMessage(self.wallet.banner)
328 def init_plugins(self):
329 if os.path.exists("plugins"):
331 fp, pathname, description = imp.find_module('plugins')
332 imp.load_module('electrum_plugins', fp, pathname, description)
333 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
334 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
338 self.plugin_hooks = {}
339 for p in self.plugins:
343 print_msg("Error:cannot initialize plugin",p)
344 traceback.print_exc(file=sys.stdout)
346 def set_hook(self, name, callback):
347 h = self.plugin_hooks.get(name, [])
349 self.plugin_hooks[name] = h
351 def unset_hook(self, name, callback):
352 h = self.plugin_hooks.get(name,[])
353 if callback in h: h.remove(callback)
354 self.plugin_hooks[name] = h
356 def run_hook(self, name, args):
357 for cb in self.plugin_hooks.get(name,[]):
362 QMainWindow.close(self)
363 self.run_hook('close_main_window', (self,))
365 def connect_slots(self, sender):
366 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
367 self.previous_payto_e=''
369 def timer_actions(self):
370 self.run_hook('timer_actions', (self,))
372 if self.payto_e.hasFocus():
374 r = unicode( self.payto_e.text() )
375 if r != self.previous_payto_e:
376 self.previous_payto_e = r
378 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
380 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
384 s = r + ' <' + to_address + '>'
385 self.payto_e.setText(s)
388 def update_callback(self):
389 self.emit(QtCore.SIGNAL('updatesignal'))
391 def update_wallet(self):
392 if self.wallet.interface and self.wallet.interface.is_connected:
393 if not self.wallet.up_to_date:
394 text = _("Synchronizing...")
395 icon = QIcon(":icons/status_waiting.png")
397 c, u = self.wallet.get_balance()
398 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
399 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
400 text += self.create_quote_text(Decimal(c+u)/100000000)
401 icon = QIcon(":icons/status_connected.png")
403 text = _("Not connected")
404 icon = QIcon(":icons/status_disconnected.png")
406 self.status_text = text
407 self.statusBar().showMessage(text)
408 self.status_button.setIcon( icon )
410 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
411 self.update_history_tab()
412 self.update_receive_tab()
413 self.update_contacts_tab()
414 self.update_completions()
417 def create_quote_text(self, btc_balance):
418 quote_currency = self.config.get("currency", "None")
419 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
420 if quote_balance is None:
423 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
426 def create_history_tab(self):
427 self.history_list = l = MyTreeWidget(self)
429 for i,width in enumerate(self.column_widths['history']):
430 l.setColumnWidth(i, width)
431 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
432 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
433 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
435 l.setContextMenuPolicy(Qt.CustomContextMenu)
436 l.customContextMenuRequested.connect(self.create_history_menu)
440 def create_history_menu(self, position):
441 self.history_list.selectedIndexes()
442 item = self.history_list.currentItem()
444 tx_hash = str(item.data(0, Qt.UserRole).toString())
445 if not tx_hash: return
447 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
448 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
449 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
450 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
453 def show_tx_details(self, tx):
454 dialog = QDialog(None)
456 dialog.setWindowTitle(_("Transaction Details"))
458 dialog.setLayout(vbox)
459 dialog.setMinimumSize(600,300)
462 if tx_hash in self.wallet.transactions.keys():
463 is_mine, v, fee = self.wallet.get_tx_value(tx)
464 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
466 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
472 vbox.addWidget(QLabel("Transaction ID:"))
473 e = QLineEdit(tx_hash)
477 vbox.addWidget(QLabel("Date: %s"%time_str))
478 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
481 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
482 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
484 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
485 vbox.addWidget(QLabel("Transaction fee: unknown"))
487 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
489 vbox.addWidget( self.generate_transaction_information_widget(tx) )
491 ok_button = QPushButton(_("Close"))
492 ok_button.setDefault(True)
493 ok_button.clicked.connect(dialog.accept)
497 hbox.addWidget(ok_button)
501 def tx_label_clicked(self, item, column):
502 if column==2 and item.isSelected():
504 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
505 self.history_list.editItem( item, column )
506 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
509 def tx_label_changed(self, item, column):
513 tx_hash = str(item.data(0, Qt.UserRole).toString())
514 tx = self.wallet.transactions.get(tx_hash)
515 s = self.wallet.labels.get(tx_hash)
516 text = unicode( item.text(2) )
518 self.wallet.labels[tx_hash] = text
519 item.setForeground(2, QBrush(QColor('black')))
521 if s: self.wallet.labels.pop(tx_hash)
522 text = self.wallet.get_default_label(tx_hash)
523 item.setText(2, text)
524 item.setForeground(2, QBrush(QColor('gray')))
528 def edit_label(self, is_recv):
529 l = self.receive_list if is_recv else self.contacts_list
530 c = 2 if is_recv else 1
531 item = l.currentItem()
532 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
533 l.editItem( item, c )
534 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
538 def address_label_clicked(self, item, column, l, column_addr, column_label):
539 if column == column_label and item.isSelected():
540 addr = unicode( item.text(column_addr) )
541 label = unicode( item.text(column_label) )
542 if label in self.wallet.aliases.keys():
544 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
545 l.editItem( item, column )
546 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
549 def address_label_changed(self, item, column, l, column_addr, column_label):
551 if column == column_label:
552 addr = unicode( item.text(column_addr) )
553 text = unicode( item.text(column_label) )
557 if text not in self.wallet.aliases.keys():
558 old_addr = self.wallet.labels.get(text)
560 self.wallet.labels[addr] = text
563 print_error("Error: This is one of your aliases")
564 label = self.wallet.labels.get(addr,'')
565 item.setText(column_label, QString(label))
567 s = self.wallet.labels.get(addr)
569 self.wallet.labels.pop(addr)
573 self.update_history_tab()
574 self.update_completions()
576 self.current_item_changed(item)
578 self.run_hook('item_changed',(self, item, column))
581 def current_item_changed(self, a):
582 self.run_hook('current_item_changed',(self, a))
586 def update_history_tab(self):
588 self.history_list.clear()
589 for item in self.wallet.get_tx_history():
590 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
593 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
599 icon = QIcon(":icons/unconfirmed.png")
601 icon = QIcon(":icons/clock%d.png"%conf)
603 icon = QIcon(":icons/confirmed.png")
606 icon = QIcon(":icons/unconfirmed.png")
608 if value is not None:
609 v_str = format_satoshis(value, True, self.wallet.num_zeros)
613 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
616 label, is_default_label = self.wallet.get_label(tx_hash)
618 label = _('Pruned transaction outputs')
619 is_default_label = False
621 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
622 item.setFont(2, QFont(MONOSPACE_FONT))
623 item.setFont(3, QFont(MONOSPACE_FONT))
624 item.setFont(4, QFont(MONOSPACE_FONT))
626 item.setForeground(3, QBrush(QColor("#BC1E1E")))
628 item.setData(0, Qt.UserRole, tx_hash)
629 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
631 item.setForeground(2, QBrush(QColor('grey')))
633 item.setIcon(0, icon)
634 self.history_list.insertTopLevelItem(0,item)
637 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
640 def create_send_tab(self):
645 grid.setColumnMinimumWidth(3,300)
646 grid.setColumnStretch(5,1)
648 self.payto_e = QLineEdit()
649 grid.addWidget(QLabel(_('Pay to')), 1, 0)
650 grid.addWidget(self.payto_e, 1, 1, 1, 3)
652 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)
654 completer = QCompleter()
655 completer.setCaseSensitivity(False)
656 self.payto_e.setCompleter(completer)
657 completer.setModel(self.completions)
659 self.message_e = QLineEdit()
660 grid.addWidget(QLabel(_('Description')), 2, 0)
661 grid.addWidget(self.message_e, 2, 1, 1, 3)
662 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)
664 self.amount_e = QLineEdit()
665 grid.addWidget(QLabel(_('Amount')), 3, 0)
666 grid.addWidget(self.amount_e, 3, 1, 1, 2)
667 grid.addWidget(HelpButton(
668 _('Amount to be sent.') + '\n\n' \
669 + _('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)
671 self.fee_e = QLineEdit()
672 grid.addWidget(QLabel(_('Fee')), 4, 0)
673 grid.addWidget(self.fee_e, 4, 1, 1, 2)
674 grid.addWidget(HelpButton(
675 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
676 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
677 + _('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)
680 b = EnterButton(_("Send"), self.do_send)
682 b = EnterButton(_("Create unsigned transaction"), self.do_send)
683 grid.addWidget(b, 6, 1)
685 b = EnterButton(_("Clear"),self.do_clear)
686 grid.addWidget(b, 6, 2)
688 self.payto_sig = QLabel('')
689 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
691 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
692 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
701 def entry_changed( is_fee ):
702 self.funds_error = False
703 amount = numbify(self.amount_e)
704 fee = numbify(self.fee_e)
705 if not is_fee: fee = None
708 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
710 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
713 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
714 text = self.status_text
717 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
718 self.funds_error = True
719 text = _( "Not enough funds" )
721 self.statusBar().showMessage(text)
722 self.amount_e.setPalette(palette)
723 self.fee_e.setPalette(palette)
725 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
726 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
728 self.run_hook('create_send_tab',(self,grid))
732 def update_completions(self):
734 for addr,label in self.wallet.labels.items():
735 if addr in self.wallet.addressbook:
736 l.append( label + ' <' + addr + '>')
737 l = l + self.wallet.aliases.keys()
739 self.completions.setStringList(l)
745 label = unicode( self.message_e.text() )
746 r = unicode( self.payto_e.text() )
750 m1 = re.match(ALIAS_REGEXP, r)
751 # label or alias, with address in brackets
752 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
755 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
759 to_address = m2.group(2)
763 if not is_valid(to_address):
764 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
768 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
770 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
773 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
775 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
778 if self.wallet.use_encryption:
779 password = self.password_dialog()
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', (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))
970 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
971 menu.addAction(_("QR code"), lambda: ElectrumWindow.show_qrcode("bitcoin:" + addr, _("Address")) )
972 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
973 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
974 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
975 if addr in self.wallet.imported_keys:
976 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
979 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
980 menu.addAction(t, lambda: self.toggle_freeze(addr))
981 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
982 menu.addAction(t, lambda: self.toggle_priority(addr))
984 self.run_hook('receive_menu', (self, menu,))
985 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
988 def payto(self, x, is_alias):
995 label = self.wallet.labels.get(addr)
996 m_addr = label + ' <' + addr + '>' if label else addr
997 self.tabs.setCurrentIndex(1)
998 self.payto_e.setText(m_addr)
999 self.amount_e.setFocus()
1001 def delete_contact(self, x, is_alias):
1002 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1003 if not is_alias and x in self.wallet.addressbook:
1004 self.wallet.addressbook.remove(x)
1005 if x in self.wallet.labels.keys():
1006 self.wallet.labels.pop(x)
1007 elif is_alias and x in self.wallet.aliases:
1008 self.wallet.aliases.pop(x)
1009 self.update_history_tab()
1010 self.update_contacts_tab()
1011 self.update_completions()
1013 def create_contact_menu(self, position):
1014 # fixme: this function apparently has a side effect.
1015 # if it is not called the menu pops up several times
1016 #self.contacts_list.selectedIndexes()
1018 item = self.contacts_list.itemAt(position)
1020 addr = unicode(item.text(0))
1021 label = unicode(item.text(1))
1022 is_alias = label in self.wallet.aliases.keys()
1023 x = label if is_alias else addr
1025 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1026 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1027 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1029 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1031 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1032 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1033 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1036 def update_receive_item(self, item):
1037 address = str(item.data(0,0).toString())
1038 label = self.wallet.labels.get(address,'')
1039 item.setData(1,0,label)
1041 self.run_hook('update_receive_item', (self, address, item))
1043 c, u = self.wallet.get_addr_balance(address)
1044 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1045 item.setData(3,0,balance)
1047 if self.expert_mode:
1048 if address in self.wallet.frozen_addresses:
1049 item.setBackgroundColor(0, QColor('lightblue'))
1050 elif address in self.wallet.prioritized_addresses:
1051 item.setBackgroundColor(0, QColor('lightgreen'))
1054 def update_receive_tab(self):
1055 l = self.receive_list
1058 l.setColumnHidden(3, not self.expert_mode)
1059 l.setColumnHidden(4, not self.expert_mode)
1060 if not self.expert_mode:
1061 width = self.column_widths['receive'][0][0]
1062 l.setColumnWidth(0, width)
1064 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1065 l.setColumnWidth(i, width)
1068 for k, account in self.wallet.accounts.items():
1069 name = account.get('name',str(k))
1070 c,u = self.wallet.get_account_balance(k)
1071 account_item = QTreeWidgetItem( [ name, '', '', format_satoshis(c+u), ''] )
1072 l.addTopLevelItem(account_item)
1073 account_item.setExpanded(True)
1076 for is_change in [0,1]:
1077 name = "Receiving" if not is_change else "Change"
1078 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1079 account_item.addChild(seq_item)
1080 if not is_change: seq_item.setExpanded(True)
1084 for address in account[is_change]:
1085 h = self.wallet.history.get(address,[])
1090 if gap > self.wallet.gap_limit:
1095 num_tx = '*' if h == ['*'] else "%d"%len(h)
1096 item = QTreeWidgetItem( [ address, '', '', '', num_tx] )
1097 item.setFont(0, QFont(MONOSPACE_FONT))
1098 item.setFont(2, QFont(MONOSPACE_FONT))
1099 self.update_receive_item(item)
1101 item.setBackgroundColor(1, QColor('red'))
1102 seq_item.addChild(item)
1104 if self.wallet.imported_keys:
1105 c,u = self.wallet.get_imported_balance()
1106 account_item = QTreeWidgetItem( [ _('Imported'), '', '', format_satoshis(c+u), ''] )
1107 l.addTopLevelItem(account_item)
1108 account_item.setExpanded(True)
1109 for address in self.wallet.imported_keys.keys():
1110 item = QTreeWidgetItem( [ address, '', '', '', ''] )
1111 item.setFont(0, QFont(MONOSPACE_FONT))
1112 item.setFont(2, QFont(MONOSPACE_FONT))
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 from electrum import util, bitcoin, commands
1160 self.console = console = Console()
1161 self.console.history = self.config.get("console-history",[])
1162 self.console.history_index = len(self.console.history)
1164 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1165 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1167 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1169 def mkfunc(f, method):
1170 return lambda *args: apply( f, (method, args, self.password_dialog ))
1172 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1173 methods[m] = mkfunc(c._run, m)
1175 console.updateNamespace(methods)
1179 def create_status_bar(self):
1180 self.status_text = ""
1182 sb.setFixedHeight(35)
1183 qtVersion = qVersion()
1185 update_notification = UpdateLabel(self.config)
1186 if(update_notification.new_version):
1187 sb.addPermanentWidget(update_notification)
1189 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1190 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1191 if self.wallet.seed:
1192 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1193 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1194 if self.wallet.seed:
1195 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1196 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1197 sb.addPermanentWidget( self.status_button )
1199 self.setStatusBar(sb)
1203 self.config.set_key('gui', 'lite', True)
1206 self.lite.mini.show()
1208 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1209 self.lite.main(None)
1211 def new_contact_dialog(self):
1212 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1213 address = unicode(text)
1215 if is_valid(address):
1216 self.wallet.addressbook.append(address)
1218 self.update_contacts_tab()
1219 self.update_history_tab()
1220 self.update_completions()
1222 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1224 def show_master_public_key(self):
1225 dialog = QDialog(None)
1227 dialog.setWindowTitle(_("Master Public Key"))
1229 main_text = QTextEdit()
1230 main_text.setText(self.wallet.get_master_public_key())
1231 main_text.setReadOnly(True)
1232 main_text.setMaximumHeight(170)
1233 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1235 ok_button = QPushButton(_("OK"))
1236 ok_button.setDefault(True)
1237 ok_button.clicked.connect(dialog.accept)
1239 main_layout = QGridLayout()
1240 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1242 main_layout.addWidget(main_text, 1, 0)
1243 main_layout.addWidget(qrw, 1, 1 )
1245 vbox = QVBoxLayout()
1246 vbox.addLayout(main_layout)
1247 hbox = QHBoxLayout()
1249 hbox.addWidget(ok_button)
1250 vbox.addLayout(hbox)
1252 dialog.setLayout(vbox)
1257 def show_seed_dialog(self, wallet, parent=None):
1259 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1262 if wallet.use_encryption:
1263 password = parent.password_dialog()
1270 seed = wallet.decode_seed(password)
1272 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1275 self.show_seed(seed)
1278 def show_seed(self, seed):
1279 dialog = QDialog(None)
1281 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1283 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1285 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1287 seed_text = QTextEdit(brainwallet)
1288 seed_text.setReadOnly(True)
1289 seed_text.setMaximumHeight(130)
1291 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1292 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1293 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1294 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1295 label2 = QLabel(msg2)
1296 label2.setWordWrap(True)
1299 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1300 logo.setMaximumWidth(60)
1302 qrw = QRCodeWidget(seed, 4)
1304 ok_button = QPushButton(_("OK"))
1305 ok_button.setDefault(True)
1306 ok_button.clicked.connect(dialog.accept)
1308 grid = QGridLayout()
1309 #main_layout.addWidget(logo, 0, 0)
1311 grid.addWidget(logo, 0, 0)
1312 grid.addWidget(label1, 0, 1)
1314 grid.addWidget(seed_text, 1, 0, 1, 2)
1316 grid.addWidget(qrw, 0, 2, 2, 1)
1318 vbox = QVBoxLayout()
1319 vbox.addLayout(grid)
1320 vbox.addWidget(label2)
1322 hbox = QHBoxLayout()
1324 hbox.addWidget(ok_button)
1325 vbox.addLayout(hbox)
1327 dialog.setLayout(vbox)
1331 def show_qrcode(data, title = "QR code"):
1335 d.setWindowTitle(title)
1336 d.setMinimumSize(270, 300)
1337 vbox = QVBoxLayout()
1338 qrw = QRCodeWidget(data)
1339 vbox.addWidget(qrw, 1)
1340 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1341 hbox = QHBoxLayout()
1345 filename = "qrcode.bmp"
1346 bmp.save_qrcode(qrw.qr, filename)
1347 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1349 b = QPushButton(_("Save"))
1351 b.clicked.connect(print_qr)
1353 b = QPushButton(_("Close"))
1355 b.clicked.connect(d.accept)
1358 vbox.addLayout(hbox)
1362 def view_private_key(self,address):
1363 if not address: return
1364 if self.wallet.use_encryption:
1365 password = self.password_dialog()
1372 pk = self.wallet.get_private_key(address, password)
1373 except BaseException, e:
1374 self.show_message(str(e))
1377 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1380 def sign_message(self,address):
1381 if not address: return
1384 d.setWindowTitle(_('Sign Message'))
1385 d.setMinimumSize(410, 290)
1387 tab_widget = QTabWidget()
1389 layout = QGridLayout(tab)
1391 sign_address = QLineEdit()
1393 sign_address.setText(address)
1394 layout.addWidget(QLabel(_('Address')), 1, 0)
1395 layout.addWidget(sign_address, 1, 1)
1397 sign_message = QTextEdit()
1398 layout.addWidget(QLabel(_('Message')), 2, 0)
1399 layout.addWidget(sign_message, 2, 1)
1400 layout.setRowStretch(2,3)
1402 sign_signature = QTextEdit()
1403 layout.addWidget(QLabel(_('Signature')), 3, 0)
1404 layout.addWidget(sign_signature, 3, 1)
1405 layout.setRowStretch(3,1)
1408 if self.wallet.use_encryption:
1409 password = self.password_dialog()
1416 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1417 sign_signature.setText(signature)
1418 except BaseException, e:
1419 self.show_message(str(e))
1422 hbox = QHBoxLayout()
1423 b = QPushButton(_("Sign"))
1425 b.clicked.connect(do_sign)
1426 b = QPushButton(_("Close"))
1427 b.clicked.connect(d.accept)
1429 layout.addLayout(hbox, 4, 1)
1430 tab_widget.addTab(tab, _("Sign"))
1434 layout = QGridLayout(tab)
1436 verify_address = QLineEdit()
1437 layout.addWidget(QLabel(_('Address')), 1, 0)
1438 layout.addWidget(verify_address, 1, 1)
1440 verify_message = QTextEdit()
1441 layout.addWidget(QLabel(_('Message')), 2, 0)
1442 layout.addWidget(verify_message, 2, 1)
1443 layout.setRowStretch(2,3)
1445 verify_signature = QTextEdit()
1446 layout.addWidget(QLabel(_('Signature')), 3, 0)
1447 layout.addWidget(verify_signature, 3, 1)
1448 layout.setRowStretch(3,1)
1452 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1453 self.show_message(_("Signature verified"))
1454 except BaseException, e:
1455 self.show_message(str(e))
1458 hbox = QHBoxLayout()
1459 b = QPushButton(_("Verify"))
1460 b.clicked.connect(do_verify)
1462 b = QPushButton(_("Close"))
1463 b.clicked.connect(d.accept)
1465 layout.addLayout(hbox, 4, 1)
1466 tab_widget.addTab(tab, _("Verify"))
1468 vbox = QVBoxLayout()
1469 vbox.addWidget(tab_widget)
1476 def question(self, msg):
1477 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1479 def show_message(self, msg):
1480 QMessageBox.information(self, _('Message'), msg, _('OK'))
1482 def password_dialog(self ):
1489 vbox = QVBoxLayout()
1490 msg = _('Please enter your password')
1491 vbox.addWidget(QLabel(msg))
1493 grid = QGridLayout()
1495 grid.addWidget(QLabel(_('Password')), 1, 0)
1496 grid.addWidget(pw, 1, 1)
1497 vbox.addLayout(grid)
1499 vbox.addLayout(ok_cancel_buttons(d))
1502 if not d.exec_(): return
1503 return unicode(pw.text())
1510 def change_password_dialog( wallet, parent=None ):
1513 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1521 new_pw = QLineEdit()
1522 new_pw.setEchoMode(2)
1523 conf_pw = QLineEdit()
1524 conf_pw.setEchoMode(2)
1526 vbox = QVBoxLayout()
1528 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1529 +_('To disable wallet encryption, enter an empty new password.')) \
1530 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1532 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1533 +_("Leave these fields empty if you want to disable encryption.")
1534 vbox.addWidget(QLabel(msg))
1536 grid = QGridLayout()
1539 if wallet.use_encryption:
1540 grid.addWidget(QLabel(_('Password')), 1, 0)
1541 grid.addWidget(pw, 1, 1)
1543 grid.addWidget(QLabel(_('New Password')), 2, 0)
1544 grid.addWidget(new_pw, 2, 1)
1546 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1547 grid.addWidget(conf_pw, 3, 1)
1548 vbox.addLayout(grid)
1550 vbox.addLayout(ok_cancel_buttons(d))
1553 if not d.exec_(): return
1555 password = unicode(pw.text()) if wallet.use_encryption else None
1556 new_password = unicode(new_pw.text())
1557 new_password2 = unicode(conf_pw.text())
1560 seed = wallet.decode_seed(password)
1562 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1565 if new_password != new_password2:
1566 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1567 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1569 wallet.update_password(seed, password, new_password)
1572 def seed_dialog(wallet, parent=None):
1576 vbox = QVBoxLayout()
1577 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1578 vbox.addWidget(QLabel(msg))
1580 grid = QGridLayout()
1583 seed_e = QLineEdit()
1584 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1585 grid.addWidget(seed_e, 1, 1)
1589 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1590 grid.addWidget(gap_e, 2, 1)
1591 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1592 vbox.addLayout(grid)
1594 vbox.addLayout(ok_cancel_buttons(d))
1597 if not d.exec_(): return
1600 gap = int(unicode(gap_e.text()))
1602 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1606 seed = str(seed_e.text())
1609 print_error("Warning: Not hex, trying decode")
1611 seed = mnemonic.mn_decode( seed.split(' ') )
1613 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1617 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1622 def generate_transaction_information_widget(self, tx):
1623 tabs = QTabWidget(self)
1626 grid_ui = QGridLayout(tab1)
1627 grid_ui.setColumnStretch(0,1)
1628 tabs.addTab(tab1, _('Outputs') )
1630 tree_widget = MyTreeWidget(self)
1631 tree_widget.setColumnCount(2)
1632 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1633 tree_widget.setColumnWidth(0, 300)
1634 tree_widget.setColumnWidth(1, 50)
1636 for output in tx.d["outputs"]:
1637 item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
1638 tree_widget.addTopLevelItem(item)
1640 tree_widget.setMaximumHeight(100)
1642 grid_ui.addWidget(tree_widget)
1645 grid_ui = QGridLayout(tab2)
1646 grid_ui.setColumnStretch(0,1)
1647 tabs.addTab(tab2, _('Inputs') )
1649 tree_widget = MyTreeWidget(self)
1650 tree_widget.setColumnCount(2)
1651 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1653 for input_line in tx.inputs:
1654 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1655 tree_widget.addTopLevelItem(item)
1657 tree_widget.setMaximumHeight(100)
1659 grid_ui.addWidget(tree_widget)
1663 def tx_dict_from_text(self, txt):
1665 tx_dict = json.loads(str(txt))
1666 assert "hex" in tx_dict.keys()
1667 assert "complete" in tx_dict.keys()
1668 if not tx_dict["complete"]:
1669 assert "input_info" in tx_dict.keys()
1671 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1676 def read_tx_from_file(self):
1677 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1681 with open(fileName, "r") as f:
1682 file_content = f.read()
1683 except (ValueError, IOError, os.error), reason:
1684 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1686 return self.tx_dict_from_text(file_content)
1689 def sign_raw_transaction(self, tx, input_info):
1690 if self.wallet.use_encryption:
1691 password = self.password_dialog()
1698 self.wallet.signrawtransaction(tx, input_info, [], password)
1700 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1702 with open(fileName, "w+") as f:
1703 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1704 self.show_message(_("Transaction saved succesfully"))
1705 except BaseException, e:
1706 self.show_message(str(e))
1709 def create_sign_transaction_window(self, tx_dict):
1710 tx = Transaction(tx_dict["hex"])
1712 dialog = QDialog(self)
1713 dialog.setMinimumWidth(500)
1714 dialog.setWindowTitle(_('Sign unsigned transaction'))
1717 vbox = QVBoxLayout()
1718 dialog.setLayout(vbox)
1719 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1721 if tx_dict["complete"] == True:
1722 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1724 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1725 vbox.addLayout(ok_cancel_buttons(dialog))
1726 input_info = json.loads(tx_dict["input_info"])
1729 self.sign_raw_transaction(tx, input_info)
1733 def do_sign_from_text(self):
1734 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1737 tx_dict = self.tx_dict_from_text(unicode(txt))
1739 self.create_sign_transaction_window(tx_dict)
1742 def do_sign_from_file(self):
1743 tx_dict = self.read_tx_from_file()
1745 self.create_sign_transaction_window(tx_dict)
1748 def send_raw_transaction(self, raw_tx):
1749 result, result_message = self.wallet.sendtx( raw_tx )
1751 self.show_message("Transaction succesfully sent: %s" % (result_message))
1753 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1756 def create_send_transaction_window(self, tx_dict):
1757 tx = Transaction(tx_dict["hex"])
1759 dialog = QDialog(self)
1760 dialog.setMinimumWidth(500)
1761 dialog.setWindowTitle(_('Send raw transaction'))
1764 vbox = QVBoxLayout()
1765 dialog.setLayout(vbox)
1766 vbox.addWidget( self.generate_transaction_information_widget(tx))
1768 if tx_dict["complete"] == False:
1769 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1771 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1772 vbox.addLayout(ok_cancel_buttons(dialog))
1775 self.send_raw_transaction(tx_dict["hex"])
1778 def do_send_from_file(self):
1779 tx_dict = self.read_tx_from_file()
1781 self.create_send_transaction_window(tx_dict)
1784 def do_send_from_text(self):
1785 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1788 tx_dict = self.tx_dict_from_text(unicode(txt))
1790 self.create_send_transaction_window(tx_dict)
1793 def do_export_privkeys(self):
1794 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.")))
1796 if self.wallet.use_encryption:
1797 password = self.password_dialog()
1803 select_export = _('Select file to export your private keys to')
1804 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1806 with open(fileName, "w+") as csvfile:
1807 transaction = csv.writer(csvfile)
1808 transaction.writerow(["address", "private_key"])
1811 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1812 transaction.writerow(["%34s"%addr,pk])
1814 self.show_message(_("Private keys exported."))
1816 except (IOError, os.error), reason:
1817 export_error_label = _("Electrum was unable to produce a private key-export.")
1818 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1820 except BaseException, e:
1821 self.show_message(str(e))
1825 def do_import_labels(self):
1826 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1827 if not labelsFile: return
1829 f = open(labelsFile, 'r')
1832 for key, value in json.loads(data).items():
1833 self.wallet.labels[key] = value
1835 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1836 except (IOError, os.error), reason:
1837 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1841 def do_export_labels(self):
1842 labels = self.wallet.labels
1844 labelsFile = util.user_dir() + '/labels.dat'
1845 f = open(labelsFile, 'w+')
1846 json.dump(labels, f)
1848 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1849 except (IOError, os.error), reason:
1850 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1852 def do_export_history(self):
1853 from gui_lite import csv_transaction
1854 csv_transaction(self.wallet)
1856 def do_import_privkey(self):
1857 if not self.wallet.imported_keys:
1858 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1859 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1860 + _('Are you sure you understand what you are doing?'), 3, 4)
1863 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1865 sec = str(text).strip()
1866 if self.wallet.use_encryption:
1867 password = self.password_dialog()
1873 addr = self.wallet.import_key(sec, password)
1875 QMessageBox.critical(None, _("Unable to import key"), "error")
1877 QMessageBox.information(None, _("Key imported"), addr)
1878 self.update_receive_tab()
1879 self.update_history_tab()
1880 except BaseException as e:
1881 QMessageBox.critical(None, _("Unable to import key"), str(e))
1883 def settings_dialog(self):
1885 d.setWindowTitle(_('Electrum Settings'))
1887 vbox = QVBoxLayout()
1889 tabs = QTabWidget(self)
1890 vbox.addWidget(tabs)
1893 grid_ui = QGridLayout(tab1)
1894 grid_ui.setColumnStretch(0,1)
1895 tabs.addTab(tab1, _('Display') )
1897 nz_label = QLabel(_('Display zeros'))
1898 grid_ui.addWidget(nz_label, 3, 0)
1900 nz_e.setText("%d"% self.wallet.num_zeros)
1901 grid_ui.addWidget(nz_e, 3, 1)
1902 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1903 grid_ui.addWidget(HelpButton(msg), 3, 2)
1904 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1905 if not self.config.is_modifiable('num_zeros'):
1906 for w in [nz_e, nz_label]: w.setEnabled(False)
1908 lang_label=QLabel(_('Language') + ':')
1909 grid_ui.addWidget(lang_label , 8, 0)
1910 lang_combo = QComboBox()
1911 from i18n import languages
1912 lang_combo.addItems(languages.values())
1914 index = languages.keys().index(self.config.get("language",''))
1917 lang_combo.setCurrentIndex(index)
1918 grid_ui.addWidget(lang_combo, 8, 1)
1919 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1920 if not self.config.is_modifiable('language'):
1921 for w in [lang_combo, lang_label]: w.setEnabled(False)
1923 currencies = self.exchanger.get_currencies()
1924 currencies.insert(0, "None")
1926 cur_label=QLabel(_('Currency') + ':')
1927 grid_ui.addWidget(cur_label , 9, 0)
1928 cur_combo = QComboBox()
1929 cur_combo.addItems(currencies)
1931 index = currencies.index(self.config.get('currency', "None"))
1934 cur_combo.setCurrentIndex(index)
1935 grid_ui.addWidget(cur_combo, 9, 1)
1936 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1938 view_label=QLabel(_('Receive Tab') + ':')
1939 grid_ui.addWidget(view_label , 10, 0)
1940 view_combo = QComboBox()
1941 view_combo.addItems([_('Simple'), _('Advanced')])
1942 view_combo.setCurrentIndex(self.expert_mode)
1943 grid_ui.addWidget(view_combo, 10, 1)
1944 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1945 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1946 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1948 grid_ui.addWidget(HelpButton(hh), 10, 2)
1952 grid_wallet = QGridLayout(tab2)
1953 grid_wallet.setColumnStretch(0,1)
1954 tabs.addTab(tab2, _('Wallet') )
1956 fee_label = QLabel(_('Transaction fee'))
1957 grid_wallet.addWidget(fee_label, 0, 0)
1959 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1960 grid_wallet.addWidget(fee_e, 0, 1)
1961 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1962 + _('Recommended value') + ': 0.001'
1963 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1964 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1965 if not self.config.is_modifiable('fee'):
1966 for w in [fee_e, fee_label]: w.setEnabled(False)
1968 usechange_label = QLabel(_('Use change addresses'))
1969 grid_wallet.addWidget(usechange_label, 1, 0)
1970 usechange_combo = QComboBox()
1971 usechange_combo.addItems([_('Yes'), _('No')])
1972 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1973 grid_wallet.addWidget(usechange_combo, 1, 1)
1974 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1975 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1977 gap_label = QLabel(_('Gap limit'))
1978 grid_wallet.addWidget(gap_label, 2, 0)
1980 gap_e.setText("%d"% self.wallet.gap_limit)
1981 grid_wallet.addWidget(gap_e, 2, 1)
1982 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1983 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1984 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1985 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1986 + _('Warning') + ': ' \
1987 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1988 + _('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'
1989 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1990 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1991 if not self.config.is_modifiable('gap_limit'):
1992 for w in [gap_e, gap_label]: w.setEnabled(False)
1994 grid_wallet.setRowStretch(3,1)
1999 grid_io = QGridLayout(tab3)
2000 grid_io.setColumnStretch(0,1)
2001 tabs.addTab(tab3, _('Import/Export') )
2003 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2004 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2005 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2006 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2008 grid_io.addWidget(QLabel(_('History')), 2, 0)
2009 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2010 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2012 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2014 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2015 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2016 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2018 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2019 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2020 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2021 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2022 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2024 grid_io.setRowStretch(4,1)
2027 grid_raw = QGridLayout(tab4)
2028 grid_raw.setColumnStretch(0,1)
2029 tabs.addTab(tab4, _('Raw tx') ) # move this to wallet tab
2031 if self.wallet.seed:
2032 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2033 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2034 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2035 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2037 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2038 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2039 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2040 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2041 grid_raw.setRowStretch(3,1)
2045 grid_plugins = QGridLayout(tab5)
2046 grid_plugins.setColumnStretch(0,1)
2047 tabs.addTab(tab5, _('Plugins') )
2048 def mk_toggle(cb, p):
2049 return lambda: cb.setChecked(p.toggle(self))
2050 for i, p in enumerate(self.plugins):
2052 name, description = p.get_info()
2053 cb = QCheckBox(name)
2054 cb.setChecked(p.is_enabled())
2055 cb.stateChanged.connect(mk_toggle(cb,p))
2056 grid_plugins.addWidget(cb, i, 0)
2057 grid_plugins.addWidget(HelpButton(description), i, 2)
2059 print_msg("Error: cannot display plugin", p)
2060 traceback.print_exc(file=sys.stdout)
2062 grid_plugins.setRowStretch(i+1,1)
2064 vbox.addLayout(ok_cancel_buttons(d))
2068 if not d.exec_(): return
2070 fee = unicode(fee_e.text())
2072 fee = int( 100000000 * Decimal(fee) )
2074 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2077 if self.wallet.fee != fee:
2078 self.wallet.fee = fee
2081 nz = unicode(nz_e.text())
2086 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2089 if self.wallet.num_zeros != nz:
2090 self.wallet.num_zeros = nz
2091 self.config.set_key('num_zeros', nz, True)
2092 self.update_history_tab()
2093 self.update_receive_tab()
2095 usechange_result = usechange_combo.currentIndex() == 0
2096 if self.wallet.use_change != usechange_result:
2097 self.wallet.use_change = usechange_result
2098 self.config.set_key('use_change', self.wallet.use_change, True)
2101 n = int(gap_e.text())
2103 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2106 if self.wallet.gap_limit != n:
2107 r = self.wallet.change_gap_limit(n)
2109 self.update_receive_tab()
2110 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2112 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2114 need_restart = False
2116 lang_request = languages.keys()[lang_combo.currentIndex()]
2117 if lang_request != self.config.get('language'):
2118 self.config.set_key("language", lang_request, True)
2121 cur_request = str(currencies[cur_combo.currentIndex()])
2122 if cur_request != self.config.get('currency', "None"):
2123 self.config.set_key('currency', cur_request, True)
2124 self.update_wallet()
2127 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2129 self.receive_tab_set_mode(view_combo.currentIndex())
2133 def network_dialog(wallet, parent=None):
2134 interface = wallet.interface
2136 if interface.is_connected:
2137 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2139 status = _("Not connected")
2140 server = interface.server
2143 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2144 server = interface.server
2146 plist, servers_list = interface.get_servers_list()
2150 d.setWindowTitle(_('Server'))
2151 d.setMinimumSize(375, 20)
2153 vbox = QVBoxLayout()
2156 hbox = QHBoxLayout()
2158 l.setPixmap(QPixmap(":icons/network.png"))
2161 hbox.addWidget(QLabel(status))
2163 vbox.addLayout(hbox)
2167 grid = QGridLayout()
2169 vbox.addLayout(grid)
2172 server_protocol = QComboBox()
2173 server_host = QLineEdit()
2174 server_host.setFixedWidth(200)
2175 server_port = QLineEdit()
2176 server_port.setFixedWidth(60)
2178 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2179 protocol_letters = 'thsg'
2180 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2181 server_protocol.addItems(protocol_names)
2183 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2184 grid.addWidget(server_protocol, 0, 1)
2185 grid.addWidget(server_host, 0, 2)
2186 grid.addWidget(server_port, 0, 3)
2188 def change_protocol(p):
2189 protocol = protocol_letters[p]
2190 host = unicode(server_host.text())
2191 pp = plist.get(host,DEFAULT_PORTS)
2192 if protocol not in pp.keys():
2193 protocol = pp.keys()[0]
2195 server_host.setText( host )
2196 server_port.setText( port )
2198 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2200 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2201 servers_list_widget = QTreeWidget(parent)
2202 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2203 servers_list_widget.setMaximumHeight(150)
2204 servers_list_widget.setColumnWidth(0, 240)
2205 for _host in servers_list.keys():
2206 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2207 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2209 def change_server(host, protocol=None):
2210 pp = plist.get(host,DEFAULT_PORTS)
2212 port = pp.get(protocol)
2213 if not port: protocol = None
2216 if 't' in pp.keys():
2218 port = pp.get(protocol)
2220 protocol = pp.keys()[0]
2221 port = pp.get(protocol)
2223 server_host.setText( host )
2224 server_port.setText( port )
2225 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2227 if not plist: return
2228 for p in protocol_letters:
2229 i = protocol_letters.index(p)
2230 j = server_protocol.model().index(i,0)
2231 if p not in pp.keys():
2232 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2234 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2238 host, port, protocol = server.split(':')
2239 change_server(host,protocol)
2241 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2242 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2244 if not wallet.config.is_modifiable('server'):
2245 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2248 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2249 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2250 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2251 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2254 proxy_mode = QComboBox()
2255 proxy_host = QLineEdit()
2256 proxy_host.setFixedWidth(200)
2257 proxy_port = QLineEdit()
2258 proxy_port.setFixedWidth(60)
2259 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2261 def check_for_disable(index = False):
2262 if proxy_mode.currentText() != 'NONE':
2263 proxy_host.setEnabled(True)
2264 proxy_port.setEnabled(True)
2266 proxy_host.setEnabled(False)
2267 proxy_port.setEnabled(False)
2270 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2272 if not wallet.config.is_modifiable('proxy'):
2273 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2275 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2276 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2277 proxy_host.setText(proxy_config.get("host"))
2278 proxy_port.setText(proxy_config.get("port"))
2280 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2281 grid.addWidget(proxy_mode, 2, 1)
2282 grid.addWidget(proxy_host, 2, 2)
2283 grid.addWidget(proxy_port, 2, 3)
2286 vbox.addLayout(ok_cancel_buttons(d))
2289 if not d.exec_(): return
2291 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2292 if proxy_mode.currentText() != 'NONE':
2293 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2297 wallet.config.set_key("proxy", proxy, True)
2298 wallet.config.set_key("server", server, True)
2299 interface.set_server(server, proxy)
2300 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2303 def closeEvent(self, event):
2305 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2306 self.save_column_widths()
2307 self.config.set_key("column-widths", self.column_widths, True)
2308 self.config.set_key("console-history",self.console.history[-50:])
2314 def __init__(self, wallet, config, app=None):
2315 self.wallet = wallet
2316 self.config = config
2318 self.app = QApplication(sys.argv)
2321 def restore_or_create(self):
2322 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2323 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2324 if r==2: return None
2325 return 'restore' if r==1 else 'create'
2327 def seed_dialog(self):
2328 return ElectrumWindow.seed_dialog( self.wallet )
2330 def network_dialog(self):
2331 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2334 def show_seed(self):
2335 ElectrumWindow.show_seed_dialog(self.wallet)
2338 def password_dialog(self):
2339 ElectrumWindow.change_password_dialog(self.wallet)
2342 def restore_wallet(self):
2343 wallet = self.wallet
2344 # wait until we are connected, because the user might have selected another server
2345 if not wallet.interface.is_connected:
2346 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2347 waiting_dialog(waiting)
2349 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2350 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2352 wallet.set_up_to_date(False)
2353 wallet.interface.poke('synchronizer')
2354 waiting_dialog(waiting)
2355 if wallet.is_found():
2356 print_error( "Recovery successful" )
2358 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2365 w = ElectrumWindow(self.wallet, self.config)
2366 if url: w.set_url(url)