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.create_status_bar()
278 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
279 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
280 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
281 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
283 self.expert_mode = config.get('classic_expert_mode', False)
285 set_language(config.get('language'))
287 self.funds_error = False
288 self.completions = QStringListModel()
290 self.tabs = tabs = QTabWidget(self)
291 self.column_widths = self.config.get("column-widths", default_column_widths )
292 tabs.addTab(self.create_history_tab(), _('History') )
293 tabs.addTab(self.create_send_tab(), _('Send') )
294 tabs.addTab(self.create_receive_tab(), _('Receive') )
295 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
296 tabs.addTab(self.create_console_tab(), _('Console') )
297 tabs.setMinimumSize(600, 400)
298 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
299 self.setCentralWidget(tabs)
301 g = self.config.get("winpos-qt",[100, 100, 840, 400])
302 self.setGeometry(g[0], g[1], g[2], g[3])
303 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
304 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
305 self.setWindowTitle( title )
307 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
308 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
309 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
310 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
312 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
313 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
314 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
315 self.history_list.setFocus(True)
317 self.exchanger = exchange_rate.Exchanger(self)
318 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
320 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
321 if platform.system() == 'Windows':
322 n = 3 if self.wallet.seed else 2
323 tabs.setCurrentIndex (n)
324 tabs.setCurrentIndex (0)
326 # set initial message
327 self.console.showMessage(self.wallet.banner)
331 def init_plugins(self):
333 if os.path.exists("plugins"):
334 fp, pathname, description = imp.find_module('plugins')
335 imp.load_module('electrum_plugins', fp, pathname, description)
336 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
337 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
339 import electrum_plugins
340 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
341 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
343 self.plugin_hooks = {}
344 for p in self.plugins:
348 print_msg("Error:cannot initialize plugin",p)
349 traceback.print_exc(file=sys.stdout)
351 def set_hook(self, name, callback):
352 h = self.plugin_hooks.get(name, [])
354 self.plugin_hooks[name] = h
356 def unset_hook(self, name, callback):
357 h = self.plugin_hooks.get(name,[])
358 if callback in h: h.remove(callback)
359 self.plugin_hooks[name] = h
361 def run_hook(self, name, args):
362 for cb in self.plugin_hooks.get(name,[]):
367 QMainWindow.close(self)
368 self.run_hook('close_main_window', (self,))
370 def connect_slots(self, sender):
371 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
372 self.previous_payto_e=''
374 def timer_actions(self):
375 self.run_hook('timer_actions', (self,))
377 if self.payto_e.hasFocus():
379 r = unicode( self.payto_e.text() )
380 if r != self.previous_payto_e:
381 self.previous_payto_e = r
383 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
385 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
389 s = r + ' <' + to_address + '>'
390 self.payto_e.setText(s)
394 def update_status(self):
395 if self.wallet.interface and self.wallet.interface.is_connected:
396 if not self.wallet.up_to_date:
397 text = _("Synchronizing...")
398 icon = QIcon(":icons/status_waiting.png")
400 c, u = self.wallet.get_balance()
401 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
402 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
403 text += self.create_quote_text(Decimal(c+u)/100000000)
404 icon = QIcon(":icons/status_connected.png")
406 text = _("Not connected")
407 icon = QIcon(":icons/status_disconnected.png")
409 self.status_text = text
410 self.statusBar().showMessage(text)
411 self.status_button.setIcon( icon )
413 def update_wallet(self):
415 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
416 self.update_history_tab()
417 self.update_receive_tab()
418 self.update_contacts_tab()
419 self.update_completions()
422 def create_quote_text(self, btc_balance):
423 quote_currency = self.config.get("currency", "None")
424 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
425 if quote_balance is None:
428 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
431 def create_history_tab(self):
432 self.history_list = l = MyTreeWidget(self)
434 for i,width in enumerate(self.column_widths['history']):
435 l.setColumnWidth(i, width)
436 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
437 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
438 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
440 l.setContextMenuPolicy(Qt.CustomContextMenu)
441 l.customContextMenuRequested.connect(self.create_history_menu)
445 def create_history_menu(self, position):
446 self.history_list.selectedIndexes()
447 item = self.history_list.currentItem()
449 tx_hash = str(item.data(0, Qt.UserRole).toString())
450 if not tx_hash: return
452 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
453 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
454 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
455 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
458 def show_tx_details(self, tx):
459 dialog = QDialog(self)
461 dialog.setWindowTitle(_("Transaction Details"))
463 dialog.setLayout(vbox)
464 dialog.setMinimumSize(600,300)
467 if tx_hash in self.wallet.transactions.keys():
468 is_mine, v, fee = self.wallet.get_tx_value(tx)
469 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
471 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
477 vbox.addWidget(QLabel("Transaction ID:"))
478 e = QLineEdit(tx_hash)
482 vbox.addWidget(QLabel("Date: %s"%time_str))
483 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
486 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
487 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
489 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
490 vbox.addWidget(QLabel("Transaction fee: unknown"))
492 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
494 vbox.addWidget( self.generate_transaction_information_widget(tx) )
496 ok_button = QPushButton(_("Close"))
497 ok_button.setDefault(True)
498 ok_button.clicked.connect(dialog.accept)
502 hbox.addWidget(ok_button)
506 def tx_label_clicked(self, item, column):
507 if column==2 and item.isSelected():
509 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
510 self.history_list.editItem( item, column )
511 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
514 def tx_label_changed(self, item, column):
518 tx_hash = str(item.data(0, Qt.UserRole).toString())
519 tx = self.wallet.transactions.get(tx_hash)
520 s = self.wallet.labels.get(tx_hash)
521 text = unicode( item.text(2) )
523 self.wallet.labels[tx_hash] = text
525 self.run_hook('label_changed',(self, str(tx_hash), text))
527 item.setForeground(2, QBrush(QColor('black')))
529 if s: self.wallet.labels.pop(tx_hash)
530 text = self.wallet.get_default_label(tx_hash)
531 item.setText(2, text)
532 item.setForeground(2, QBrush(QColor('gray')))
536 def edit_label(self, is_recv):
537 l = self.receive_list if is_recv else self.contacts_list
538 item = l.currentItem()
539 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
540 l.editItem( item, 1 )
541 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
545 def address_label_clicked(self, item, column, l, column_addr, column_label):
546 if column == column_label and item.isSelected():
547 addr = unicode( item.text(column_addr) )
548 label = unicode( item.text(column_label) )
549 if label in self.wallet.aliases.keys():
551 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
552 l.editItem( item, column )
553 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
556 def address_label_changed(self, item, column, l, column_addr, column_label):
557 if column == column_label:
558 addr = unicode( item.text(column_addr) )
559 text = unicode( item.text(column_label) )
563 if text not in self.wallet.aliases.keys():
564 old_addr = self.wallet.labels.get(text)
566 self.wallet.labels[addr] = text
568 self.run_hook('label_changed',(self, addr, text))
570 print_error("Error: This is one of your aliases")
571 label = self.wallet.labels.get(addr,'')
572 item.setText(column_label, QString(label))
574 s = self.wallet.labels.get(addr)
576 self.wallet.labels.pop(addr)
580 self.update_history_tab()
581 self.update_completions()
583 self.current_item_changed(item)
585 self.run_hook('item_changed',(self, item, column))
588 def current_item_changed(self, a):
589 self.run_hook('current_item_changed',(self, a))
593 def update_history_tab(self):
595 self.history_list.clear()
596 for item in self.wallet.get_tx_history():
597 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
600 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
606 icon = QIcon(":icons/unconfirmed.png")
608 icon = QIcon(":icons/clock%d.png"%conf)
610 icon = QIcon(":icons/confirmed.png")
613 icon = QIcon(":icons/unconfirmed.png")
615 if value is not None:
616 v_str = format_satoshis(value, True, self.wallet.num_zeros)
620 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
623 label, is_default_label = self.wallet.get_label(tx_hash)
625 label = _('Pruned transaction outputs')
626 is_default_label = False
628 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
629 item.setFont(2, QFont(MONOSPACE_FONT))
630 item.setFont(3, QFont(MONOSPACE_FONT))
631 item.setFont(4, QFont(MONOSPACE_FONT))
633 item.setForeground(3, QBrush(QColor("#BC1E1E")))
635 item.setData(0, Qt.UserRole, tx_hash)
636 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
638 item.setForeground(2, QBrush(QColor('grey')))
640 item.setIcon(0, icon)
641 self.history_list.insertTopLevelItem(0,item)
644 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
647 def create_send_tab(self):
652 grid.setColumnMinimumWidth(3,300)
653 grid.setColumnStretch(5,1)
655 self.payto_e = QLineEdit()
656 grid.addWidget(QLabel(_('Pay to')), 1, 0)
657 grid.addWidget(self.payto_e, 1, 1, 1, 3)
659 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)
661 completer = QCompleter()
662 completer.setCaseSensitivity(False)
663 self.payto_e.setCompleter(completer)
664 completer.setModel(self.completions)
666 self.message_e = QLineEdit()
667 grid.addWidget(QLabel(_('Description')), 2, 0)
668 grid.addWidget(self.message_e, 2, 1, 1, 3)
669 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)
671 self.amount_e = QLineEdit()
672 grid.addWidget(QLabel(_('Amount')), 3, 0)
673 grid.addWidget(self.amount_e, 3, 1, 1, 2)
674 grid.addWidget(HelpButton(
675 _('Amount to be sent.') + '\n\n' \
676 + _('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)
678 self.fee_e = QLineEdit()
679 grid.addWidget(QLabel(_('Fee')), 4, 0)
680 grid.addWidget(self.fee_e, 4, 1, 1, 2)
681 grid.addWidget(HelpButton(
682 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
683 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
684 + _('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)
687 b = EnterButton(_("Send"), self.do_send)
689 b = EnterButton(_("Create unsigned transaction"), self.do_send)
690 grid.addWidget(b, 6, 1)
692 b = EnterButton(_("Clear"),self.do_clear)
693 grid.addWidget(b, 6, 2)
695 self.payto_sig = QLabel('')
696 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
698 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
699 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
708 def entry_changed( is_fee ):
709 self.funds_error = False
710 amount = numbify(self.amount_e)
711 fee = numbify(self.fee_e)
712 if not is_fee: fee = None
715 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
717 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
720 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
721 text = self.status_text
724 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
725 self.funds_error = True
726 text = _( "Not enough funds" )
728 self.statusBar().showMessage(text)
729 self.amount_e.setPalette(palette)
730 self.fee_e.setPalette(palette)
732 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
733 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
735 self.run_hook('create_send_tab',(self,grid))
739 def update_completions(self):
741 for addr,label in self.wallet.labels.items():
742 if addr in self.wallet.addressbook:
743 l.append( label + ' <' + addr + '>')
744 l = l + self.wallet.aliases.keys()
746 self.completions.setStringList(l)
750 return lambda s, *args: s.do_protect(func, args)
754 def do_send(self, password):
756 label = unicode( self.message_e.text() )
757 r = unicode( self.payto_e.text() )
761 m1 = re.match(ALIAS_REGEXP, r)
762 # label or alias, with address in brackets
763 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
766 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
770 to_address = m2.group(2)
774 if not is_valid(to_address):
775 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
779 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
781 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
784 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
786 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
790 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
791 except BaseException, e:
792 self.show_message(str(e))
795 self.run_hook('send_tx', (self.wallet, self, tx))
798 self.wallet.labels[tx.hash()] = label
801 h = self.wallet.send_tx(tx)
802 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
803 status, msg = self.wallet.receive_tx( h )
805 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
807 self.update_contacts_tab()
809 QMessageBox.warning(self, _('Error'), msg, _('OK'))
811 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
813 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
814 with open(fileName,'w') as f:
815 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
816 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
818 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
823 def set_url(self, url):
824 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
825 self.tabs.setCurrentIndex(1)
826 label = self.wallet.labels.get(payto)
827 m_addr = label + ' <'+ payto+'>' if label else payto
828 self.payto_e.setText(m_addr)
830 self.message_e.setText(message)
831 self.amount_e.setText(amount)
833 self.set_frozen(self.payto_e,True)
834 self.set_frozen(self.amount_e,True)
835 self.set_frozen(self.message_e,True)
836 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
838 self.payto_sig.setVisible(False)
841 self.payto_sig.setVisible(False)
842 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
844 self.set_frozen(e,False)
846 def set_frozen(self,entry,frozen):
848 entry.setReadOnly(True)
849 entry.setFrame(False)
851 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
852 entry.setPalette(palette)
854 entry.setReadOnly(False)
857 palette.setColor(entry.backgroundRole(), QColor('white'))
858 entry.setPalette(palette)
861 def toggle_freeze(self,addr):
863 if addr in self.wallet.frozen_addresses:
864 self.wallet.unfreeze(addr)
866 self.wallet.freeze(addr)
867 self.update_receive_tab()
869 def toggle_priority(self,addr):
871 if addr in self.wallet.prioritized_addresses:
872 self.wallet.unprioritize(addr)
874 self.wallet.prioritize(addr)
875 self.update_receive_tab()
878 def create_list_tab(self, headers):
879 "generic tab creation method"
880 l = MyTreeWidget(self)
881 l.setColumnCount( len(headers) )
882 l.setHeaderLabels( headers )
892 vbox.addWidget(buttons)
897 buttons.setLayout(hbox)
902 def create_receive_tab(self):
903 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
904 l.setContextMenuPolicy(Qt.CustomContextMenu)
905 l.customContextMenuRequested.connect(self.create_receive_menu)
906 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
907 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
908 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
909 self.receive_list = l
910 self.receive_buttons_hbox = hbox
915 def receive_tab_set_mode(self, i):
916 self.save_column_widths()
917 self.expert_mode = (i == 1)
918 self.config.set_key('classic_expert_mode', self.expert_mode, True)
920 self.update_receive_tab()
923 def save_column_widths(self):
924 if not self.expert_mode:
925 widths = [ self.receive_list.columnWidth(0) ]
928 for i in range(self.receive_list.columnCount() -1):
929 widths.append(self.receive_list.columnWidth(i))
930 self.column_widths["receive"][self.expert_mode] = widths
932 self.column_widths["history"] = []
933 for i in range(self.history_list.columnCount() - 1):
934 self.column_widths["history"].append(self.history_list.columnWidth(i))
936 self.column_widths["contacts"] = []
937 for i in range(self.contacts_list.columnCount() - 1):
938 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
941 def create_contacts_tab(self):
942 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
943 l.setContextMenuPolicy(Qt.CustomContextMenu)
944 l.customContextMenuRequested.connect(self.create_contact_menu)
945 for i,width in enumerate(self.column_widths['contacts']):
946 l.setColumnWidth(i, width)
948 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
949 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
950 self.contacts_list = l
951 self.contacts_buttons_hbox = hbox
952 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
957 def delete_imported_key(self, addr):
958 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
959 self.wallet.imported_keys.pop(addr)
960 self.update_receive_tab()
961 self.update_history_tab()
965 def create_receive_menu(self, position):
966 # fixme: this function apparently has a side effect.
967 # if it is not called the menu pops up several times
968 #self.receive_list.selectedIndexes()
970 item = self.receive_list.itemAt(position)
972 addr = unicode(item.text(0))
973 if not is_valid(addr):
974 item.setExpanded(not item.isExpanded())
977 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
978 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
979 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
980 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
981 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
982 if addr in self.wallet.imported_keys:
983 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
986 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
987 menu.addAction(t, lambda: self.toggle_freeze(addr))
988 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
989 menu.addAction(t, lambda: self.toggle_priority(addr))
991 self.run_hook('receive_menu', (self, menu,))
992 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
995 def payto(self, x, is_alias):
1002 label = self.wallet.labels.get(addr)
1003 m_addr = label + ' <' + addr + '>' if label else addr
1004 self.tabs.setCurrentIndex(1)
1005 self.payto_e.setText(m_addr)
1006 self.amount_e.setFocus()
1008 def delete_contact(self, x, is_alias):
1009 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1010 if not is_alias and x in self.wallet.addressbook:
1011 self.wallet.addressbook.remove(x)
1012 if x in self.wallet.labels.keys():
1013 self.wallet.labels.pop(x)
1014 elif is_alias and x in self.wallet.aliases:
1015 self.wallet.aliases.pop(x)
1016 self.update_history_tab()
1017 self.update_contacts_tab()
1018 self.update_completions()
1020 def create_contact_menu(self, position):
1021 # fixme: this function apparently has a side effect.
1022 # if it is not called the menu pops up several times
1023 #self.contacts_list.selectedIndexes()
1025 item = self.contacts_list.itemAt(position)
1027 addr = unicode(item.text(0))
1028 label = unicode(item.text(1))
1029 is_alias = label in self.wallet.aliases.keys()
1030 x = label if is_alias else addr
1032 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1033 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1034 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1036 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1038 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1039 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1040 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1043 def update_receive_item(self, item):
1044 item.setFont(0, QFont(MONOSPACE_FONT))
1045 address = str(item.data(0,0).toString())
1046 label = self.wallet.labels.get(address,'')
1047 item.setData(1,0,label)
1049 self.run_hook('update_receive_item', (self, address, item))
1051 c, u = self.wallet.get_addr_balance(address)
1052 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1053 item.setData(2,0,balance)
1055 if self.expert_mode:
1056 if address in self.wallet.frozen_addresses:
1057 item.setBackgroundColor(0, QColor('lightblue'))
1058 elif address in self.wallet.prioritized_addresses:
1059 item.setBackgroundColor(0, QColor('lightgreen'))
1062 def update_receive_tab(self):
1063 l = self.receive_list
1066 l.setColumnHidden(2, not self.expert_mode)
1067 l.setColumnHidden(3, not self.expert_mode)
1068 if not self.expert_mode:
1069 width = self.column_widths['receive'][0][0]
1070 l.setColumnWidth(0, width)
1072 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1073 l.setColumnWidth(i, width)
1076 for k, account in self.wallet.accounts.items():
1077 name = account.get('name',str(k))
1078 c,u = self.wallet.get_account_balance(k)
1079 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1080 l.addTopLevelItem(account_item)
1081 account_item.setExpanded(True)
1084 for is_change in [0,1]:
1085 name = "Receiving" if not is_change else "Change"
1086 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1087 account_item.addChild(seq_item)
1088 if not is_change: seq_item.setExpanded(True)
1092 for address in account[is_change]:
1093 h = self.wallet.history.get(address,[])
1098 if gap > self.wallet.gap_limit:
1103 num_tx = '*' if h == ['*'] else "%d"%len(h)
1104 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1105 self.update_receive_item(item)
1107 item.setBackgroundColor(1, QColor('red'))
1108 seq_item.addChild(item)
1110 if self.wallet.imported_keys:
1111 c,u = self.wallet.get_imported_balance()
1112 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1113 l.addTopLevelItem(account_item)
1114 account_item.setExpanded(True)
1115 for address in self.wallet.imported_keys.keys():
1116 item = QTreeWidgetItem( [ address, '', '', ''] )
1117 self.update_receive_item(item)
1118 account_item.addChild(item)
1121 # we use column 1 because column 0 may be hidden
1122 l.setCurrentItem(l.topLevelItem(0),1)
1124 def show_contact_details(self, m):
1125 a = self.wallet.aliases.get(m)
1127 if a[0] in self.wallet.authorities.keys():
1128 s = self.wallet.authorities.get(a[0])
1131 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1132 QMessageBox.information(self, 'Alias', msg, 'OK')
1134 def update_contacts_tab(self):
1136 l = self.contacts_list
1140 for alias, v in self.wallet.aliases.items():
1142 alias_targets.append(target)
1143 item = QTreeWidgetItem( [ target, alias, '-'] )
1144 item.setBackgroundColor(0, QColor('lightgray'))
1145 l.addTopLevelItem(item)
1147 for address in self.wallet.addressbook:
1148 if address in alias_targets: continue
1149 label = self.wallet.labels.get(address,'')
1151 for tx in self.wallet.transactions.values():
1152 if address in map(lambda x: x[0], tx.outputs): n += 1
1154 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1155 item.setFont(0, QFont(MONOSPACE_FONT))
1156 l.addTopLevelItem(item)
1158 l.setCurrentItem(l.topLevelItem(0))
1161 def create_console_tab(self):
1162 from qt_console import Console
1163 self.console = console = Console()
1164 self.console.history = self.config.get("console-history",[])
1165 self.console.history_index = len(self.console.history)
1167 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1168 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1170 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1172 def mkfunc(f, method):
1173 return lambda *args: apply( f, (method, args, self.password_dialog ))
1175 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1176 methods[m] = mkfunc(c._run, m)
1178 console.updateNamespace(methods)
1182 def create_status_bar(self):
1183 self.status_text = ""
1185 sb.setFixedHeight(35)
1186 qtVersion = qVersion()
1188 update_notification = UpdateLabel(self.config)
1189 if(update_notification.new_version):
1190 sb.addPermanentWidget(update_notification)
1192 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1193 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1194 if self.wallet.seed:
1195 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1196 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1197 if self.wallet.seed:
1198 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1199 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1200 sb.addPermanentWidget( self.status_button )
1202 self.run_hook('create_status_bar', (sb,))
1204 self.setStatusBar(sb)
1208 self.config.set_key('gui', 'lite', True)
1211 self.lite.mini.show()
1213 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1214 self.lite.main(None)
1216 def new_contact_dialog(self):
1217 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1218 address = unicode(text)
1220 if is_valid(address):
1221 self.wallet.addressbook.append(address)
1223 self.update_contacts_tab()
1224 self.update_history_tab()
1225 self.update_completions()
1227 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1229 def show_master_public_key(self):
1230 dialog = QDialog(self)
1232 dialog.setWindowTitle(_("Master Public Key"))
1234 main_text = QTextEdit()
1235 main_text.setText(self.wallet.get_master_public_key())
1236 main_text.setReadOnly(True)
1237 main_text.setMaximumHeight(170)
1238 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1240 ok_button = QPushButton(_("OK"))
1241 ok_button.setDefault(True)
1242 ok_button.clicked.connect(dialog.accept)
1244 main_layout = QGridLayout()
1245 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1247 main_layout.addWidget(main_text, 1, 0)
1248 main_layout.addWidget(qrw, 1, 1 )
1250 vbox = QVBoxLayout()
1251 vbox.addLayout(main_layout)
1252 hbox = QHBoxLayout()
1254 hbox.addWidget(ok_button)
1255 vbox.addLayout(hbox)
1257 dialog.setLayout(vbox)
1262 def show_seed_dialog(self, password):
1263 if not self.wallet.seed:
1264 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1267 seed = self.wallet.decode_seed(password)
1269 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1271 self.show_seed(seed, self)
1275 def show_seed(self, seed, parent=None):
1276 dialog = QDialog(parent)
1278 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1280 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1282 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1284 seed_text = QTextEdit(brainwallet)
1285 seed_text.setReadOnly(True)
1286 seed_text.setMaximumHeight(130)
1288 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1289 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1290 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1291 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1292 label2 = QLabel(msg2)
1293 label2.setWordWrap(True)
1296 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1297 logo.setMaximumWidth(60)
1299 qrw = QRCodeWidget(seed)
1301 ok_button = QPushButton(_("OK"))
1302 ok_button.setDefault(True)
1303 ok_button.clicked.connect(dialog.accept)
1305 grid = QGridLayout()
1306 #main_layout.addWidget(logo, 0, 0)
1308 grid.addWidget(logo, 0, 0)
1309 grid.addWidget(label1, 0, 1)
1311 grid.addWidget(seed_text, 1, 0, 1, 2)
1313 grid.addWidget(qrw, 0, 2, 2, 1)
1315 vbox = QVBoxLayout()
1316 vbox.addLayout(grid)
1317 vbox.addWidget(label2)
1319 hbox = QHBoxLayout()
1321 hbox.addWidget(ok_button)
1322 vbox.addLayout(hbox)
1324 dialog.setLayout(vbox)
1327 def show_qrcode(self, data, title = "QR code"):
1331 d.setWindowTitle(title)
1332 d.setMinimumSize(270, 300)
1333 vbox = QVBoxLayout()
1334 qrw = QRCodeWidget(data)
1335 vbox.addWidget(qrw, 1)
1336 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1337 hbox = QHBoxLayout()
1341 filename = "qrcode.bmp"
1342 bmp.save_qrcode(qrw.qr, filename)
1343 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1345 b = QPushButton(_("Save"))
1347 b.clicked.connect(print_qr)
1349 b = QPushButton(_("Close"))
1351 b.clicked.connect(d.accept)
1354 vbox.addLayout(hbox)
1359 def do_protect(self, func, args):
1360 if self.wallet.use_encryption:
1361 password = self.password_dialog()
1367 if args != (False,):
1368 args = (self,) + args + (password,)
1370 args = (self,password)
1375 def show_private_key(self, address, password):
1376 if not address: return
1378 pk = self.wallet.get_private_key(address, password)
1379 except BaseException, e:
1380 self.show_message(str(e))
1382 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1386 def do_sign(self, address, message, signature, password):
1388 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1389 signature.setText(sig)
1390 except BaseException, e:
1391 self.show_message(str(e))
1393 def sign_message(self, address):
1394 if not address: return
1397 d.setWindowTitle(_('Sign Message'))
1398 d.setMinimumSize(410, 290)
1400 tab_widget = QTabWidget()
1402 layout = QGridLayout(tab)
1404 sign_address = QLineEdit()
1406 sign_address.setText(address)
1407 layout.addWidget(QLabel(_('Address')), 1, 0)
1408 layout.addWidget(sign_address, 1, 1)
1410 sign_message = QTextEdit()
1411 layout.addWidget(QLabel(_('Message')), 2, 0)
1412 layout.addWidget(sign_message, 2, 1)
1413 layout.setRowStretch(2,3)
1415 sign_signature = QTextEdit()
1416 layout.addWidget(QLabel(_('Signature')), 3, 0)
1417 layout.addWidget(sign_signature, 3, 1)
1418 layout.setRowStretch(3,1)
1421 hbox = QHBoxLayout()
1422 b = QPushButton(_("Sign"))
1424 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1425 b = QPushButton(_("Close"))
1426 b.clicked.connect(d.accept)
1428 layout.addLayout(hbox, 4, 1)
1429 tab_widget.addTab(tab, _("Sign"))
1433 layout = QGridLayout(tab)
1435 verify_address = QLineEdit()
1436 layout.addWidget(QLabel(_('Address')), 1, 0)
1437 layout.addWidget(verify_address, 1, 1)
1439 verify_message = QTextEdit()
1440 layout.addWidget(QLabel(_('Message')), 2, 0)
1441 layout.addWidget(verify_message, 2, 1)
1442 layout.setRowStretch(2,3)
1444 verify_signature = QTextEdit()
1445 layout.addWidget(QLabel(_('Signature')), 3, 0)
1446 layout.addWidget(verify_signature, 3, 1)
1447 layout.setRowStretch(3,1)
1451 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1452 self.show_message(_("Signature verified"))
1453 except BaseException, e:
1454 self.show_message(str(e))
1457 hbox = QHBoxLayout()
1458 b = QPushButton(_("Verify"))
1459 b.clicked.connect(do_verify)
1461 b = QPushButton(_("Close"))
1462 b.clicked.connect(d.accept)
1464 layout.addLayout(hbox, 4, 1)
1465 tab_widget.addTab(tab, _("Verify"))
1467 vbox = QVBoxLayout()
1468 vbox.addWidget(tab_widget)
1475 def question(self, msg):
1476 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1478 def show_message(self, msg):
1479 QMessageBox.information(self, _('Message'), msg, _('OK'))
1481 def password_dialog(self ):
1488 vbox = QVBoxLayout()
1489 msg = _('Please enter your password')
1490 vbox.addWidget(QLabel(msg))
1492 grid = QGridLayout()
1494 grid.addWidget(QLabel(_('Password')), 1, 0)
1495 grid.addWidget(pw, 1, 1)
1496 vbox.addLayout(grid)
1498 vbox.addLayout(ok_cancel_buttons(d))
1501 if not d.exec_(): return
1502 return unicode(pw.text())
1509 def change_password_dialog( wallet, parent=None ):
1512 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1520 new_pw = QLineEdit()
1521 new_pw.setEchoMode(2)
1522 conf_pw = QLineEdit()
1523 conf_pw.setEchoMode(2)
1525 vbox = QVBoxLayout()
1527 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1528 +_('To disable wallet encryption, enter an empty new password.')) \
1529 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1531 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1532 +_("Leave these fields empty if you want to disable encryption.")
1533 vbox.addWidget(QLabel(msg))
1535 grid = QGridLayout()
1538 if wallet.use_encryption:
1539 grid.addWidget(QLabel(_('Password')), 1, 0)
1540 grid.addWidget(pw, 1, 1)
1542 grid.addWidget(QLabel(_('New Password')), 2, 0)
1543 grid.addWidget(new_pw, 2, 1)
1545 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1546 grid.addWidget(conf_pw, 3, 1)
1547 vbox.addLayout(grid)
1549 vbox.addLayout(ok_cancel_buttons(d))
1552 if not d.exec_(): return
1554 password = unicode(pw.text()) if wallet.use_encryption else None
1555 new_password = unicode(new_pw.text())
1556 new_password2 = unicode(conf_pw.text())
1559 seed = wallet.decode_seed(password)
1561 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1564 if new_password != new_password2:
1565 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1566 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1568 wallet.update_password(seed, password, new_password)
1571 def seed_dialog(wallet, parent=None):
1575 vbox = QVBoxLayout()
1576 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1577 vbox.addWidget(QLabel(msg))
1579 grid = QGridLayout()
1582 seed_e = QLineEdit()
1583 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1584 grid.addWidget(seed_e, 1, 1)
1588 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1589 grid.addWidget(gap_e, 2, 1)
1590 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1591 vbox.addLayout(grid)
1593 vbox.addLayout(ok_cancel_buttons(d))
1596 if not d.exec_(): return
1599 gap = int(unicode(gap_e.text()))
1601 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1605 seed = str(seed_e.text())
1608 print_error("Warning: Not hex, trying decode")
1610 seed = mnemonic.mn_decode( seed.split(' ') )
1612 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1616 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1621 def generate_transaction_information_widget(self, tx):
1622 tabs = QTabWidget(self)
1625 grid_ui = QGridLayout(tab1)
1626 grid_ui.setColumnStretch(0,1)
1627 tabs.addTab(tab1, _('Outputs') )
1629 tree_widget = MyTreeWidget(self)
1630 tree_widget.setColumnCount(2)
1631 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1632 tree_widget.setColumnWidth(0, 300)
1633 tree_widget.setColumnWidth(1, 50)
1635 for address, value in tx.outputs:
1636 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1637 tree_widget.addTopLevelItem(item)
1639 tree_widget.setMaximumHeight(100)
1641 grid_ui.addWidget(tree_widget)
1644 grid_ui = QGridLayout(tab2)
1645 grid_ui.setColumnStretch(0,1)
1646 tabs.addTab(tab2, _('Inputs') )
1648 tree_widget = MyTreeWidget(self)
1649 tree_widget.setColumnCount(2)
1650 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1652 for input_line in tx.inputs:
1653 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1654 tree_widget.addTopLevelItem(item)
1656 tree_widget.setMaximumHeight(100)
1658 grid_ui.addWidget(tree_widget)
1662 def tx_dict_from_text(self, txt):
1664 tx_dict = json.loads(str(txt))
1665 assert "hex" in tx_dict.keys()
1666 assert "complete" in tx_dict.keys()
1667 if not tx_dict["complete"]:
1668 assert "input_info" in tx_dict.keys()
1670 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1675 def read_tx_from_file(self):
1676 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1680 with open(fileName, "r") as f:
1681 file_content = f.read()
1682 except (ValueError, IOError, os.error), reason:
1683 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1685 return self.tx_dict_from_text(file_content)
1689 def sign_raw_transaction(self, tx, input_info, password):
1691 self.wallet.signrawtransaction(tx, input_info, [], password)
1693 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1695 with open(fileName, "w+") as f:
1696 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1697 self.show_message(_("Transaction saved succesfully"))
1698 except BaseException, e:
1699 self.show_message(str(e))
1702 def create_sign_transaction_window(self, tx_dict):
1703 tx = Transaction(tx_dict["hex"])
1705 dialog = QDialog(self)
1706 dialog.setMinimumWidth(500)
1707 dialog.setWindowTitle(_('Sign unsigned transaction'))
1710 vbox = QVBoxLayout()
1711 dialog.setLayout(vbox)
1712 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1714 if tx_dict["complete"] == True:
1715 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1717 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1718 vbox.addLayout(ok_cancel_buttons(dialog))
1719 input_info = json.loads(tx_dict["input_info"])
1722 self.sign_raw_transaction(tx, input_info)
1726 def do_sign_from_text(self):
1727 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1730 tx_dict = self.tx_dict_from_text(unicode(txt))
1732 self.create_sign_transaction_window(tx_dict)
1735 def do_sign_from_file(self):
1736 tx_dict = self.read_tx_from_file()
1738 self.create_sign_transaction_window(tx_dict)
1741 def send_raw_transaction(self, raw_tx):
1742 result, result_message = self.wallet.sendtx( raw_tx )
1744 self.show_message("Transaction succesfully sent: %s" % (result_message))
1746 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1749 def create_send_transaction_window(self, tx_dict):
1750 tx = Transaction(tx_dict["hex"])
1752 dialog = QDialog(self)
1753 dialog.setMinimumWidth(500)
1754 dialog.setWindowTitle(_('Send raw transaction'))
1757 vbox = QVBoxLayout()
1758 dialog.setLayout(vbox)
1759 vbox.addWidget( self.generate_transaction_information_widget(tx))
1761 if tx_dict["complete"] == False:
1762 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1764 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1765 vbox.addLayout(ok_cancel_buttons(dialog))
1768 self.send_raw_transaction(tx_dict["hex"])
1771 def do_send_from_file(self):
1772 tx_dict = self.read_tx_from_file()
1774 self.create_send_transaction_window(tx_dict)
1777 def do_send_from_text(self):
1778 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1781 tx_dict = self.tx_dict_from_text(unicode(txt))
1783 self.create_send_transaction_window(tx_dict)
1787 def do_export_privkeys(self, password):
1788 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.")))
1791 select_export = _('Select file to export your private keys to')
1792 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1794 with open(fileName, "w+") as csvfile:
1795 transaction = csv.writer(csvfile)
1796 transaction.writerow(["address", "private_key"])
1799 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1800 transaction.writerow(["%34s"%addr,pk])
1802 self.show_message(_("Private keys exported."))
1804 except (IOError, os.error), reason:
1805 export_error_label = _("Electrum was unable to produce a private key-export.")
1806 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1808 except BaseException, e:
1809 self.show_message(str(e))
1813 def do_import_labels(self):
1814 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1815 if not labelsFile: return
1817 f = open(labelsFile, 'r')
1820 for key, value in json.loads(data).items():
1821 self.wallet.labels[key] = value
1823 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1824 except (IOError, os.error), reason:
1825 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1828 def do_export_labels(self):
1829 labels = self.wallet.labels
1831 labelsFile = util.user_dir() + '/labels.dat'
1832 f = open(labelsFile, 'w+')
1833 json.dump(labels, f)
1835 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1836 except (IOError, os.error), reason:
1837 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1840 def do_export_history(self):
1841 from gui_lite import csv_transaction
1842 csv_transaction(self.wallet)
1846 def do_import_privkey(self, password):
1847 if not self.wallet.imported_keys:
1848 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1849 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1850 + _('Are you sure you understand what you are doing?'), 3, 4)
1853 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1855 sec = str(text).strip()
1857 addr = self.wallet.import_key(sec, password)
1859 QMessageBox.critical(None, _("Unable to import key"), "error")
1861 QMessageBox.information(None, _("Key imported"), addr)
1862 self.update_receive_tab()
1863 self.update_history_tab()
1864 except BaseException as e:
1865 QMessageBox.critical(None, _("Unable to import key"), str(e))
1868 def settings_dialog(self):
1870 d.setWindowTitle(_('Electrum Settings'))
1872 vbox = QVBoxLayout()
1874 tabs = QTabWidget(self)
1875 self.settings_tab = tabs
1876 vbox.addWidget(tabs)
1879 grid_ui = QGridLayout(tab1)
1880 grid_ui.setColumnStretch(0,1)
1881 tabs.addTab(tab1, _('Display') )
1883 nz_label = QLabel(_('Display zeros'))
1884 grid_ui.addWidget(nz_label, 3, 0)
1886 nz_e.setText("%d"% self.wallet.num_zeros)
1887 grid_ui.addWidget(nz_e, 3, 1)
1888 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1889 grid_ui.addWidget(HelpButton(msg), 3, 2)
1890 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1891 if not self.config.is_modifiable('num_zeros'):
1892 for w in [nz_e, nz_label]: w.setEnabled(False)
1894 lang_label=QLabel(_('Language') + ':')
1895 grid_ui.addWidget(lang_label , 8, 0)
1896 lang_combo = QComboBox()
1897 from i18n import languages
1898 lang_combo.addItems(languages.values())
1900 index = languages.keys().index(self.config.get("language",''))
1903 lang_combo.setCurrentIndex(index)
1904 grid_ui.addWidget(lang_combo, 8, 1)
1905 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1906 if not self.config.is_modifiable('language'):
1907 for w in [lang_combo, lang_label]: w.setEnabled(False)
1909 currencies = self.exchanger.get_currencies()
1910 currencies.insert(0, "None")
1912 cur_label=QLabel(_('Currency') + ':')
1913 grid_ui.addWidget(cur_label , 9, 0)
1914 cur_combo = QComboBox()
1915 cur_combo.addItems(currencies)
1917 index = currencies.index(self.config.get('currency', "None"))
1920 cur_combo.setCurrentIndex(index)
1921 grid_ui.addWidget(cur_combo, 9, 1)
1922 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1924 view_label=QLabel(_('Receive Tab') + ':')
1925 grid_ui.addWidget(view_label , 10, 0)
1926 view_combo = QComboBox()
1927 view_combo.addItems([_('Simple'), _('Advanced')])
1928 view_combo.setCurrentIndex(self.expert_mode)
1929 grid_ui.addWidget(view_combo, 10, 1)
1930 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1931 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1932 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1934 grid_ui.addWidget(HelpButton(hh), 10, 2)
1938 grid_wallet = QGridLayout(tab2)
1939 grid_wallet.setColumnStretch(0,1)
1940 tabs.addTab(tab2, _('Wallet') )
1942 fee_label = QLabel(_('Transaction fee'))
1943 grid_wallet.addWidget(fee_label, 0, 0)
1945 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1946 grid_wallet.addWidget(fee_e, 0, 1)
1947 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1948 + _('Recommended value') + ': 0.001'
1949 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1950 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1951 if not self.config.is_modifiable('fee'):
1952 for w in [fee_e, fee_label]: w.setEnabled(False)
1954 usechange_label = QLabel(_('Use change addresses'))
1955 grid_wallet.addWidget(usechange_label, 1, 0)
1956 usechange_combo = QComboBox()
1957 usechange_combo.addItems([_('Yes'), _('No')])
1958 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1959 grid_wallet.addWidget(usechange_combo, 1, 1)
1960 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1961 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1963 gap_label = QLabel(_('Gap limit'))
1964 grid_wallet.addWidget(gap_label, 2, 0)
1966 gap_e.setText("%d"% self.wallet.gap_limit)
1967 grid_wallet.addWidget(gap_e, 2, 1)
1968 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1969 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1970 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1971 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1972 + _('Warning') + ': ' \
1973 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1974 + _('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'
1975 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1976 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1977 if not self.config.is_modifiable('gap_limit'):
1978 for w in [gap_e, gap_label]: w.setEnabled(False)
1980 grid_wallet.setRowStretch(3,1)
1985 grid_io = QGridLayout(tab3)
1986 grid_io.setColumnStretch(0,1)
1987 tabs.addTab(tab3, _('Import/Export') )
1989 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1990 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1991 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1992 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1994 grid_io.addWidget(QLabel(_('History')), 2, 0)
1995 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1996 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1998 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2000 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2001 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2002 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2004 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2005 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2006 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2007 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2008 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2010 grid_io.setRowStretch(4,1)
2013 grid_raw = QGridLayout(tab4)
2014 grid_raw.setColumnStretch(0,1)
2015 tabs.addTab(tab4, _('Raw tx') ) # move this to wallet tab
2017 if self.wallet.seed:
2018 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2019 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2020 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2021 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2023 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2024 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2025 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2026 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2027 grid_raw.setRowStretch(3,1)
2032 grid_plugins = QGridLayout(tab5)
2033 grid_plugins.setColumnStretch(0,1)
2034 tabs.addTab(tab5, _('Plugins') )
2035 def mk_toggle(cb, p):
2036 return lambda: cb.setChecked(p.toggle(self))
2037 for i, p in enumerate(self.plugins):
2039 name, description = p.get_info()
2040 cb = QCheckBox(name)
2041 cb.setChecked(p.is_enabled())
2042 cb.stateChanged.connect(mk_toggle(cb,p))
2043 grid_plugins.addWidget(cb, i, 0)
2044 grid_plugins.addWidget(HelpButton(description), i, 2)
2046 print_msg("Error: cannot display plugin", p)
2047 traceback.print_exc(file=sys.stdout)
2048 grid_plugins.setRowStretch(i+1,1)
2050 self.run_hook('create_settings_tab', (self,tabs,))
2052 vbox.addLayout(ok_cancel_buttons(d))
2056 if not d.exec_(): return
2058 fee = unicode(fee_e.text())
2060 fee = int( 100000000 * Decimal(fee) )
2062 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2065 if self.wallet.fee != fee:
2066 self.wallet.fee = fee
2069 nz = unicode(nz_e.text())
2074 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2077 if self.wallet.num_zeros != nz:
2078 self.wallet.num_zeros = nz
2079 self.config.set_key('num_zeros', nz, True)
2080 self.update_history_tab()
2081 self.update_receive_tab()
2083 usechange_result = usechange_combo.currentIndex() == 0
2084 if self.wallet.use_change != usechange_result:
2085 self.wallet.use_change = usechange_result
2086 self.config.set_key('use_change', self.wallet.use_change, True)
2089 n = int(gap_e.text())
2091 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2094 if self.wallet.gap_limit != n:
2095 r = self.wallet.change_gap_limit(n)
2097 self.update_receive_tab()
2098 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2100 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2102 need_restart = False
2104 lang_request = languages.keys()[lang_combo.currentIndex()]
2105 if lang_request != self.config.get('language'):
2106 self.config.set_key("language", lang_request, True)
2109 cur_request = str(currencies[cur_combo.currentIndex()])
2110 if cur_request != self.config.get('currency', "None"):
2111 self.config.set_key('currency', cur_request, True)
2112 self.update_wallet()
2115 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2117 self.receive_tab_set_mode(view_combo.currentIndex())
2121 def network_dialog(wallet, parent=None):
2122 interface = wallet.interface
2124 if interface.is_connected:
2125 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2127 status = _("Not connected")
2128 server = interface.server
2131 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2132 server = interface.server
2134 plist, servers_list = interface.get_servers_list()
2138 d.setWindowTitle(_('Server'))
2139 d.setMinimumSize(375, 20)
2141 vbox = QVBoxLayout()
2144 hbox = QHBoxLayout()
2146 l.setPixmap(QPixmap(":icons/network.png"))
2149 hbox.addWidget(QLabel(status))
2151 vbox.addLayout(hbox)
2155 grid = QGridLayout()
2157 vbox.addLayout(grid)
2160 server_protocol = QComboBox()
2161 server_host = QLineEdit()
2162 server_host.setFixedWidth(200)
2163 server_port = QLineEdit()
2164 server_port.setFixedWidth(60)
2166 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2167 protocol_letters = 'thsg'
2168 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2169 server_protocol.addItems(protocol_names)
2171 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2172 grid.addWidget(server_protocol, 0, 1)
2173 grid.addWidget(server_host, 0, 2)
2174 grid.addWidget(server_port, 0, 3)
2176 def change_protocol(p):
2177 protocol = protocol_letters[p]
2178 host = unicode(server_host.text())
2179 pp = plist.get(host,DEFAULT_PORTS)
2180 if protocol not in pp.keys():
2181 protocol = pp.keys()[0]
2183 server_host.setText( host )
2184 server_port.setText( port )
2186 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2188 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2189 servers_list_widget = QTreeWidget(parent)
2190 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2191 servers_list_widget.setMaximumHeight(150)
2192 servers_list_widget.setColumnWidth(0, 240)
2193 for _host in servers_list.keys():
2194 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2195 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2197 def change_server(host, protocol=None):
2198 pp = plist.get(host,DEFAULT_PORTS)
2200 port = pp.get(protocol)
2201 if not port: protocol = None
2204 if 't' in pp.keys():
2206 port = pp.get(protocol)
2208 protocol = pp.keys()[0]
2209 port = pp.get(protocol)
2211 server_host.setText( host )
2212 server_port.setText( port )
2213 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2215 if not plist: return
2216 for p in protocol_letters:
2217 i = protocol_letters.index(p)
2218 j = server_protocol.model().index(i,0)
2219 if p not in pp.keys():
2220 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2222 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2226 host, port, protocol = server.split(':')
2227 change_server(host,protocol)
2229 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2230 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2232 if not wallet.config.is_modifiable('server'):
2233 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2236 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2237 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2238 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2239 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2242 proxy_mode = QComboBox()
2243 proxy_host = QLineEdit()
2244 proxy_host.setFixedWidth(200)
2245 proxy_port = QLineEdit()
2246 proxy_port.setFixedWidth(60)
2247 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2249 def check_for_disable(index = False):
2250 if proxy_mode.currentText() != 'NONE':
2251 proxy_host.setEnabled(True)
2252 proxy_port.setEnabled(True)
2254 proxy_host.setEnabled(False)
2255 proxy_port.setEnabled(False)
2258 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2260 if not wallet.config.is_modifiable('proxy'):
2261 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2263 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2264 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2265 proxy_host.setText(proxy_config.get("host"))
2266 proxy_port.setText(proxy_config.get("port"))
2268 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2269 grid.addWidget(proxy_mode, 2, 1)
2270 grid.addWidget(proxy_host, 2, 2)
2271 grid.addWidget(proxy_port, 2, 3)
2274 vbox.addLayout(ok_cancel_buttons(d))
2277 if not d.exec_(): return
2279 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2280 if proxy_mode.currentText() != 'NONE':
2281 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2285 wallet.config.set_key("proxy", proxy, True)
2286 wallet.config.set_key("server", server, True)
2287 interface.set_server(server, proxy)
2288 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2291 def closeEvent(self, event):
2293 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2294 self.save_column_widths()
2295 self.config.set_key("column-widths", self.column_widths, True)
2296 self.config.set_key("console-history",self.console.history[-50:])
2302 def __init__(self, wallet, config, app=None):
2303 self.wallet = wallet
2304 self.config = config
2306 self.app = QApplication(sys.argv)
2309 def restore_or_create(self):
2310 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2311 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2312 if r==2: return None
2313 return 'restore' if r==1 else 'create'
2315 def seed_dialog(self):
2316 return ElectrumWindow.seed_dialog( self.wallet )
2318 def network_dialog(self):
2319 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2322 def show_seed(self):
2323 ElectrumWindow.show_seed(self.wallet.seed)
2326 def password_dialog(self):
2327 if self.wallet.seed:
2328 ElectrumWindow.change_password_dialog(self.wallet)
2331 def restore_wallet(self):
2332 wallet = self.wallet
2333 # wait until we are connected, because the user might have selected another server
2334 if not wallet.interface.is_connected:
2335 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2336 waiting_dialog(waiting)
2338 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2339 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2341 wallet.set_up_to_date(False)
2342 wallet.interface.poke('synchronizer')
2343 waiting_dialog(waiting)
2344 if wallet.is_found():
2345 print_error( "Recovery successful" )
2347 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2354 w = ElectrumWindow(self.wallet, self.config)
2355 if url: w.set_url(url)