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):
558 if column == column_label:
559 addr = unicode( item.text(column_addr) )
560 text = unicode( item.text(column_label) )
564 if text not in self.wallet.aliases.keys():
565 old_addr = self.wallet.labels.get(text)
567 self.wallet.labels[addr] = text
569 self.run_hook('label_changed',(self, addr, text))
571 print_error("Error: This is one of your aliases")
572 label = self.wallet.labels.get(addr,'')
573 item.setText(column_label, QString(label))
575 s = self.wallet.labels.get(addr)
577 self.wallet.labels.pop(addr)
581 self.update_history_tab()
582 self.update_completions()
584 self.current_item_changed(item)
586 self.run_hook('item_changed',(self, item, column))
589 def current_item_changed(self, a):
590 self.run_hook('current_item_changed',(self, a))
594 def update_history_tab(self):
596 self.history_list.clear()
597 for item in self.wallet.get_tx_history():
598 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
601 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
607 icon = QIcon(":icons/unconfirmed.png")
609 icon = QIcon(":icons/clock%d.png"%conf)
611 icon = QIcon(":icons/confirmed.png")
614 icon = QIcon(":icons/unconfirmed.png")
616 if value is not None:
617 v_str = format_satoshis(value, True, self.wallet.num_zeros)
621 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
624 label, is_default_label = self.wallet.get_label(tx_hash)
626 label = _('Pruned transaction outputs')
627 is_default_label = False
629 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
630 item.setFont(2, QFont(MONOSPACE_FONT))
631 item.setFont(3, QFont(MONOSPACE_FONT))
632 item.setFont(4, QFont(MONOSPACE_FONT))
634 item.setForeground(3, QBrush(QColor("#BC1E1E")))
636 item.setData(0, Qt.UserRole, tx_hash)
637 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
639 item.setForeground(2, QBrush(QColor('grey')))
641 item.setIcon(0, icon)
642 self.history_list.insertTopLevelItem(0,item)
645 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
648 def create_send_tab(self):
653 grid.setColumnMinimumWidth(3,300)
654 grid.setColumnStretch(5,1)
656 self.payto_e = QLineEdit()
657 grid.addWidget(QLabel(_('Pay to')), 1, 0)
658 grid.addWidget(self.payto_e, 1, 1, 1, 3)
660 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)
662 completer = QCompleter()
663 completer.setCaseSensitivity(False)
664 self.payto_e.setCompleter(completer)
665 completer.setModel(self.completions)
667 self.message_e = QLineEdit()
668 grid.addWidget(QLabel(_('Description')), 2, 0)
669 grid.addWidget(self.message_e, 2, 1, 1, 3)
670 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)
672 self.amount_e = QLineEdit()
673 grid.addWidget(QLabel(_('Amount')), 3, 0)
674 grid.addWidget(self.amount_e, 3, 1, 1, 2)
675 grid.addWidget(HelpButton(
676 _('Amount to be sent.') + '\n\n' \
677 + _('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)
679 self.fee_e = QLineEdit()
680 grid.addWidget(QLabel(_('Fee')), 4, 0)
681 grid.addWidget(self.fee_e, 4, 1, 1, 2)
682 grid.addWidget(HelpButton(
683 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
684 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
685 + _('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)
688 b = EnterButton(_("Send"), self.do_send)
690 b = EnterButton(_("Create unsigned transaction"), self.do_send)
691 grid.addWidget(b, 6, 1)
693 b = EnterButton(_("Clear"),self.do_clear)
694 grid.addWidget(b, 6, 2)
696 self.payto_sig = QLabel('')
697 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
699 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
700 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
709 def entry_changed( is_fee ):
710 self.funds_error = False
711 amount = numbify(self.amount_e)
712 fee = numbify(self.fee_e)
713 if not is_fee: fee = None
716 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
718 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
721 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
722 text = self.status_text
725 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
726 self.funds_error = True
727 text = _( "Not enough funds" )
729 self.statusBar().showMessage(text)
730 self.amount_e.setPalette(palette)
731 self.fee_e.setPalette(palette)
733 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
734 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
736 self.run_hook('create_send_tab',(self,grid))
740 def update_completions(self):
742 for addr,label in self.wallet.labels.items():
743 if addr in self.wallet.addressbook:
744 l.append( label + ' <' + addr + '>')
745 l = l + self.wallet.aliases.keys()
747 self.completions.setStringList(l)
751 return lambda s, *args: s.do_protect(func, args)
755 def do_send(self, password):
757 label = unicode( self.message_e.text() )
758 r = unicode( self.payto_e.text() )
762 m1 = re.match(ALIAS_REGEXP, r)
763 # label or alias, with address in brackets
764 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
767 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
771 to_address = m2.group(2)
775 if not is_valid(to_address):
776 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
780 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
782 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
785 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
787 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
791 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
792 except BaseException, e:
793 self.show_message(str(e))
796 self.run_hook('send_tx', (self.wallet, self, tx))
799 self.wallet.labels[tx.hash()] = label
802 h = self.wallet.send_tx(tx)
803 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
804 status, msg = self.wallet.receive_tx( h )
806 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
808 self.update_contacts_tab()
810 QMessageBox.warning(self, _('Error'), msg, _('OK'))
812 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
814 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
815 with open(fileName,'w') as f:
816 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
817 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
819 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
824 def set_url(self, url):
825 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
826 self.tabs.setCurrentIndex(1)
827 label = self.wallet.labels.get(payto)
828 m_addr = label + ' <'+ payto+'>' if label else payto
829 self.payto_e.setText(m_addr)
831 self.message_e.setText(message)
832 self.amount_e.setText(amount)
834 self.set_frozen(self.payto_e,True)
835 self.set_frozen(self.amount_e,True)
836 self.set_frozen(self.message_e,True)
837 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
839 self.payto_sig.setVisible(False)
842 self.payto_sig.setVisible(False)
843 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
845 self.set_frozen(e,False)
847 def set_frozen(self,entry,frozen):
849 entry.setReadOnly(True)
850 entry.setFrame(False)
852 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
853 entry.setPalette(palette)
855 entry.setReadOnly(False)
858 palette.setColor(entry.backgroundRole(), QColor('white'))
859 entry.setPalette(palette)
862 def toggle_freeze(self,addr):
864 if addr in self.wallet.frozen_addresses:
865 self.wallet.unfreeze(addr)
867 self.wallet.freeze(addr)
868 self.update_receive_tab()
870 def toggle_priority(self,addr):
872 if addr in self.wallet.prioritized_addresses:
873 self.wallet.unprioritize(addr)
875 self.wallet.prioritize(addr)
876 self.update_receive_tab()
879 def create_list_tab(self, headers):
880 "generic tab creation method"
881 l = MyTreeWidget(self)
882 l.setColumnCount( len(headers) )
883 l.setHeaderLabels( headers )
893 vbox.addWidget(buttons)
898 buttons.setLayout(hbox)
903 def create_receive_tab(self):
904 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
905 l.setContextMenuPolicy(Qt.CustomContextMenu)
906 l.customContextMenuRequested.connect(self.create_receive_menu)
907 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
908 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
909 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
910 self.receive_list = l
911 self.receive_buttons_hbox = hbox
916 def receive_tab_set_mode(self, i):
917 self.save_column_widths()
918 self.expert_mode = (i == 1)
919 self.config.set_key('classic_expert_mode', self.expert_mode, True)
921 self.update_receive_tab()
924 def save_column_widths(self):
925 if not self.expert_mode:
926 widths = [ self.receive_list.columnWidth(0) ]
929 for i in range(self.receive_list.columnCount() -1):
930 widths.append(self.receive_list.columnWidth(i))
931 self.column_widths["receive"][self.expert_mode] = widths
933 self.column_widths["history"] = []
934 for i in range(self.history_list.columnCount() - 1):
935 self.column_widths["history"].append(self.history_list.columnWidth(i))
937 self.column_widths["contacts"] = []
938 for i in range(self.contacts_list.columnCount() - 1):
939 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
942 def create_contacts_tab(self):
943 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
944 l.setContextMenuPolicy(Qt.CustomContextMenu)
945 l.customContextMenuRequested.connect(self.create_contact_menu)
946 for i,width in enumerate(self.column_widths['contacts']):
947 l.setColumnWidth(i, width)
949 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
950 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
951 self.contacts_list = l
952 self.contacts_buttons_hbox = hbox
953 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
958 def delete_imported_key(self, addr):
959 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
960 self.wallet.imported_keys.pop(addr)
961 self.update_receive_tab()
962 self.update_history_tab()
966 def create_receive_menu(self, position):
967 # fixme: this function apparently has a side effect.
968 # if it is not called the menu pops up several times
969 #self.receive_list.selectedIndexes()
971 item = self.receive_list.itemAt(position)
973 addr = unicode(item.text(0))
974 if not is_valid(addr):
975 item.setExpanded(not item.isExpanded())
978 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
979 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
980 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
981 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
982 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
983 if addr in self.wallet.imported_keys:
984 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
987 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
988 menu.addAction(t, lambda: self.toggle_freeze(addr))
989 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
990 menu.addAction(t, lambda: self.toggle_priority(addr))
992 self.run_hook('receive_menu', (self, menu,))
993 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
996 def payto(self, x, is_alias):
1003 label = self.wallet.labels.get(addr)
1004 m_addr = label + ' <' + addr + '>' if label else addr
1005 self.tabs.setCurrentIndex(1)
1006 self.payto_e.setText(m_addr)
1007 self.amount_e.setFocus()
1009 def delete_contact(self, x, is_alias):
1010 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1011 if not is_alias and x in self.wallet.addressbook:
1012 self.wallet.addressbook.remove(x)
1013 if x in self.wallet.labels.keys():
1014 self.wallet.labels.pop(x)
1015 elif is_alias and x in self.wallet.aliases:
1016 self.wallet.aliases.pop(x)
1017 self.update_history_tab()
1018 self.update_contacts_tab()
1019 self.update_completions()
1021 def create_contact_menu(self, position):
1022 # fixme: this function apparently has a side effect.
1023 # if it is not called the menu pops up several times
1024 #self.contacts_list.selectedIndexes()
1026 item = self.contacts_list.itemAt(position)
1028 addr = unicode(item.text(0))
1029 label = unicode(item.text(1))
1030 is_alias = label in self.wallet.aliases.keys()
1031 x = label if is_alias else addr
1033 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1034 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1035 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1037 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1039 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1040 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1041 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1044 def update_receive_item(self, item):
1045 item.setFont(0, QFont(MONOSPACE_FONT))
1046 address = str(item.data(0,0).toString())
1047 label = self.wallet.labels.get(address,'')
1048 item.setData(1,0,label)
1050 self.run_hook('update_receive_item', (self, address, item))
1052 c, u = self.wallet.get_addr_balance(address)
1053 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1054 item.setData(2,0,balance)
1056 if self.expert_mode:
1057 if address in self.wallet.frozen_addresses:
1058 item.setBackgroundColor(0, QColor('lightblue'))
1059 elif address in self.wallet.prioritized_addresses:
1060 item.setBackgroundColor(0, QColor('lightgreen'))
1063 def update_receive_tab(self):
1064 l = self.receive_list
1067 l.setColumnHidden(2, not self.expert_mode)
1068 l.setColumnHidden(3, not self.expert_mode)
1069 if not self.expert_mode:
1070 width = self.column_widths['receive'][0][0]
1071 l.setColumnWidth(0, width)
1073 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1074 l.setColumnWidth(i, width)
1077 for k, account in self.wallet.accounts.items():
1078 name = account.get('name',str(k))
1079 c,u = self.wallet.get_account_balance(k)
1080 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1081 l.addTopLevelItem(account_item)
1082 account_item.setExpanded(True)
1085 for is_change in [0,1]:
1086 name = "Receiving" if not is_change else "Change"
1087 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1088 account_item.addChild(seq_item)
1089 if not is_change: seq_item.setExpanded(True)
1093 for address in account[is_change]:
1094 h = self.wallet.history.get(address,[])
1099 if gap > self.wallet.gap_limit:
1104 num_tx = '*' if h == ['*'] else "%d"%len(h)
1105 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1106 self.update_receive_item(item)
1108 item.setBackgroundColor(1, QColor('red'))
1109 seq_item.addChild(item)
1111 if self.wallet.imported_keys:
1112 c,u = self.wallet.get_imported_balance()
1113 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1114 l.addTopLevelItem(account_item)
1115 account_item.setExpanded(True)
1116 for address in self.wallet.imported_keys.keys():
1117 item = QTreeWidgetItem( [ address, '', '', ''] )
1118 self.update_receive_item(item)
1119 account_item.addChild(item)
1122 # we use column 1 because column 0 may be hidden
1123 l.setCurrentItem(l.topLevelItem(0),1)
1125 def show_contact_details(self, m):
1126 a = self.wallet.aliases.get(m)
1128 if a[0] in self.wallet.authorities.keys():
1129 s = self.wallet.authorities.get(a[0])
1132 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1133 QMessageBox.information(self, 'Alias', msg, 'OK')
1135 def update_contacts_tab(self):
1137 l = self.contacts_list
1141 for alias, v in self.wallet.aliases.items():
1143 alias_targets.append(target)
1144 item = QTreeWidgetItem( [ target, alias, '-'] )
1145 item.setBackgroundColor(0, QColor('lightgray'))
1146 l.addTopLevelItem(item)
1148 for address in self.wallet.addressbook:
1149 if address in alias_targets: continue
1150 label = self.wallet.labels.get(address,'')
1152 for tx in self.wallet.transactions.values():
1153 if address in map(lambda x: x[0], tx.outputs): n += 1
1155 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1156 item.setFont(0, QFont(MONOSPACE_FONT))
1157 l.addTopLevelItem(item)
1159 l.setCurrentItem(l.topLevelItem(0))
1162 def create_console_tab(self):
1163 from qt_console import Console
1164 self.console = console = Console()
1165 self.console.history = self.config.get("console-history",[])
1166 self.console.history_index = len(self.console.history)
1168 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1169 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1171 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1173 def mkfunc(f, method):
1174 return lambda *args: apply( f, (method, args, self.password_dialog ))
1176 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1177 methods[m] = mkfunc(c._run, m)
1179 console.updateNamespace(methods)
1183 def create_status_bar(self):
1184 self.status_text = ""
1186 sb.setFixedHeight(35)
1187 qtVersion = qVersion()
1189 update_notification = UpdateLabel(self.config)
1190 if(update_notification.new_version):
1191 sb.addPermanentWidget(update_notification)
1193 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1194 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1195 if self.wallet.seed:
1196 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1197 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1198 if self.wallet.seed:
1199 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1200 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1201 sb.addPermanentWidget( self.status_button )
1203 self.run_hook('create_status_bar', (sb,))
1205 self.setStatusBar(sb)
1209 self.config.set_key('gui', 'lite', True)
1212 self.lite.mini.show()
1214 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1215 self.lite.main(None)
1217 def new_contact_dialog(self):
1218 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1219 address = unicode(text)
1221 if is_valid(address):
1222 self.wallet.addressbook.append(address)
1224 self.update_contacts_tab()
1225 self.update_history_tab()
1226 self.update_completions()
1228 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1230 def show_master_public_key(self):
1231 dialog = QDialog(self)
1233 dialog.setWindowTitle(_("Master Public Key"))
1235 main_text = QTextEdit()
1236 main_text.setText(self.wallet.get_master_public_key())
1237 main_text.setReadOnly(True)
1238 main_text.setMaximumHeight(170)
1239 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1241 ok_button = QPushButton(_("OK"))
1242 ok_button.setDefault(True)
1243 ok_button.clicked.connect(dialog.accept)
1245 main_layout = QGridLayout()
1246 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1248 main_layout.addWidget(main_text, 1, 0)
1249 main_layout.addWidget(qrw, 1, 1 )
1251 vbox = QVBoxLayout()
1252 vbox.addLayout(main_layout)
1253 hbox = QHBoxLayout()
1255 hbox.addWidget(ok_button)
1256 vbox.addLayout(hbox)
1258 dialog.setLayout(vbox)
1263 def show_seed_dialog(self, password):
1264 if not self.wallet.seed:
1265 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1268 seed = self.wallet.decode_seed(password)
1270 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1272 self.show_seed(seed, self)
1276 def show_seed(self, seed, parent=None):
1277 dialog = QDialog(parent)
1279 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1281 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1283 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1285 seed_text = QTextEdit(brainwallet)
1286 seed_text.setReadOnly(True)
1287 seed_text.setMaximumHeight(130)
1289 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1290 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1291 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1292 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1293 label2 = QLabel(msg2)
1294 label2.setWordWrap(True)
1297 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1298 logo.setMaximumWidth(60)
1300 qrw = QRCodeWidget(seed)
1302 ok_button = QPushButton(_("OK"))
1303 ok_button.setDefault(True)
1304 ok_button.clicked.connect(dialog.accept)
1306 grid = QGridLayout()
1307 #main_layout.addWidget(logo, 0, 0)
1309 grid.addWidget(logo, 0, 0)
1310 grid.addWidget(label1, 0, 1)
1312 grid.addWidget(seed_text, 1, 0, 1, 2)
1314 grid.addWidget(qrw, 0, 2, 2, 1)
1316 vbox = QVBoxLayout()
1317 vbox.addLayout(grid)
1318 vbox.addWidget(label2)
1320 hbox = QHBoxLayout()
1322 hbox.addWidget(ok_button)
1323 vbox.addLayout(hbox)
1325 dialog.setLayout(vbox)
1328 def show_qrcode(self, data, title = "QR code"):
1332 d.setWindowTitle(title)
1333 d.setMinimumSize(270, 300)
1334 vbox = QVBoxLayout()
1335 qrw = QRCodeWidget(data)
1336 vbox.addWidget(qrw, 1)
1337 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1338 hbox = QHBoxLayout()
1342 filename = "qrcode.bmp"
1343 bmp.save_qrcode(qrw.qr, filename)
1344 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1346 b = QPushButton(_("Save"))
1348 b.clicked.connect(print_qr)
1350 b = QPushButton(_("Close"))
1352 b.clicked.connect(d.accept)
1355 vbox.addLayout(hbox)
1360 def do_protect(self, func, args):
1361 if self.wallet.use_encryption:
1362 password = self.password_dialog()
1368 if args != (False,):
1369 args = (self,) + args + (password,)
1371 args = (self,password)
1376 def show_private_key(self, address, password):
1377 if not address: return
1379 pk = self.wallet.get_private_key(address, password)
1380 except BaseException, e:
1381 self.show_message(str(e))
1383 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1387 def do_sign(self, address, message, signature, password):
1389 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1390 signature.setText(sig)
1391 except BaseException, e:
1392 self.show_message(str(e))
1394 def sign_message(self, address):
1395 if not address: return
1398 d.setWindowTitle(_('Sign Message'))
1399 d.setMinimumSize(410, 290)
1401 tab_widget = QTabWidget()
1403 layout = QGridLayout(tab)
1405 sign_address = QLineEdit()
1407 sign_address.setText(address)
1408 layout.addWidget(QLabel(_('Address')), 1, 0)
1409 layout.addWidget(sign_address, 1, 1)
1411 sign_message = QTextEdit()
1412 layout.addWidget(QLabel(_('Message')), 2, 0)
1413 layout.addWidget(sign_message, 2, 1)
1414 layout.setRowStretch(2,3)
1416 sign_signature = QTextEdit()
1417 layout.addWidget(QLabel(_('Signature')), 3, 0)
1418 layout.addWidget(sign_signature, 3, 1)
1419 layout.setRowStretch(3,1)
1422 hbox = QHBoxLayout()
1423 b = QPushButton(_("Sign"))
1425 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
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 address, value in tx.outputs:
1637 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(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)
1690 def sign_raw_transaction(self, tx, input_info, password):
1692 self.wallet.signrawtransaction(tx, input_info, [], password)
1694 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1696 with open(fileName, "w+") as f:
1697 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1698 self.show_message(_("Transaction saved succesfully"))
1699 except BaseException, e:
1700 self.show_message(str(e))
1703 def create_sign_transaction_window(self, tx_dict):
1704 tx = Transaction(tx_dict["hex"])
1706 dialog = QDialog(self)
1707 dialog.setMinimumWidth(500)
1708 dialog.setWindowTitle(_('Sign unsigned transaction'))
1711 vbox = QVBoxLayout()
1712 dialog.setLayout(vbox)
1713 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1715 if tx_dict["complete"] == True:
1716 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1718 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1719 vbox.addLayout(ok_cancel_buttons(dialog))
1720 input_info = json.loads(tx_dict["input_info"])
1723 self.sign_raw_transaction(tx, input_info)
1727 def do_sign_from_text(self):
1728 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1731 tx_dict = self.tx_dict_from_text(unicode(txt))
1733 self.create_sign_transaction_window(tx_dict)
1736 def do_sign_from_file(self):
1737 tx_dict = self.read_tx_from_file()
1739 self.create_sign_transaction_window(tx_dict)
1742 def send_raw_transaction(self, raw_tx):
1743 result, result_message = self.wallet.sendtx( raw_tx )
1745 self.show_message("Transaction succesfully sent: %s" % (result_message))
1747 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1750 def create_send_transaction_window(self, tx_dict):
1751 tx = Transaction(tx_dict["hex"])
1753 dialog = QDialog(self)
1754 dialog.setMinimumWidth(500)
1755 dialog.setWindowTitle(_('Send raw transaction'))
1758 vbox = QVBoxLayout()
1759 dialog.setLayout(vbox)
1760 vbox.addWidget( self.generate_transaction_information_widget(tx))
1762 if tx_dict["complete"] == False:
1763 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1765 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1766 vbox.addLayout(ok_cancel_buttons(dialog))
1769 self.send_raw_transaction(tx_dict["hex"])
1772 def do_send_from_file(self):
1773 tx_dict = self.read_tx_from_file()
1775 self.create_send_transaction_window(tx_dict)
1778 def do_send_from_text(self):
1779 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1782 tx_dict = self.tx_dict_from_text(unicode(txt))
1784 self.create_send_transaction_window(tx_dict)
1788 def do_export_privkeys(self, password):
1789 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.")))
1792 select_export = _('Select file to export your private keys to')
1793 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1795 with open(fileName, "w+") as csvfile:
1796 transaction = csv.writer(csvfile)
1797 transaction.writerow(["address", "private_key"])
1800 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1801 transaction.writerow(["%34s"%addr,pk])
1803 self.show_message(_("Private keys exported."))
1805 except (IOError, os.error), reason:
1806 export_error_label = _("Electrum was unable to produce a private key-export.")
1807 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1809 except BaseException, e:
1810 self.show_message(str(e))
1814 def do_import_labels(self):
1815 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1816 if not labelsFile: return
1818 f = open(labelsFile, 'r')
1821 for key, value in json.loads(data).items():
1822 self.wallet.labels[key] = value
1824 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1825 except (IOError, os.error), reason:
1826 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1829 def do_export_labels(self):
1830 labels = self.wallet.labels
1832 labelsFile = util.user_dir() + '/labels.dat'
1833 f = open(labelsFile, 'w+')
1834 json.dump(labels, f)
1836 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1837 except (IOError, os.error), reason:
1838 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1841 def do_export_history(self):
1842 from gui_lite import csv_transaction
1843 csv_transaction(self.wallet)
1847 def do_import_privkey(self, password):
1848 if not self.wallet.imported_keys:
1849 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1850 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1851 + _('Are you sure you understand what you are doing?'), 3, 4)
1854 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1856 sec = str(text).strip()
1858 addr = self.wallet.import_key(sec, password)
1860 QMessageBox.critical(None, _("Unable to import key"), "error")
1862 QMessageBox.information(None, _("Key imported"), addr)
1863 self.update_receive_tab()
1864 self.update_history_tab()
1865 except BaseException as e:
1866 QMessageBox.critical(None, _("Unable to import key"), str(e))
1869 def settings_dialog(self):
1871 d.setWindowTitle(_('Electrum Settings'))
1873 vbox = QVBoxLayout()
1875 tabs = QTabWidget(self)
1876 self.settings_tab = tabs
1877 vbox.addWidget(tabs)
1880 grid_ui = QGridLayout(tab1)
1881 grid_ui.setColumnStretch(0,1)
1882 tabs.addTab(tab1, _('Display') )
1884 nz_label = QLabel(_('Display zeros'))
1885 grid_ui.addWidget(nz_label, 3, 0)
1887 nz_e.setText("%d"% self.wallet.num_zeros)
1888 grid_ui.addWidget(nz_e, 3, 1)
1889 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1890 grid_ui.addWidget(HelpButton(msg), 3, 2)
1891 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1892 if not self.config.is_modifiable('num_zeros'):
1893 for w in [nz_e, nz_label]: w.setEnabled(False)
1895 lang_label=QLabel(_('Language') + ':')
1896 grid_ui.addWidget(lang_label , 8, 0)
1897 lang_combo = QComboBox()
1898 from i18n import languages
1899 lang_combo.addItems(languages.values())
1901 index = languages.keys().index(self.config.get("language",''))
1904 lang_combo.setCurrentIndex(index)
1905 grid_ui.addWidget(lang_combo, 8, 1)
1906 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1907 if not self.config.is_modifiable('language'):
1908 for w in [lang_combo, lang_label]: w.setEnabled(False)
1910 currencies = self.exchanger.get_currencies()
1911 currencies.insert(0, "None")
1913 cur_label=QLabel(_('Currency') + ':')
1914 grid_ui.addWidget(cur_label , 9, 0)
1915 cur_combo = QComboBox()
1916 cur_combo.addItems(currencies)
1918 index = currencies.index(self.config.get('currency', "None"))
1921 cur_combo.setCurrentIndex(index)
1922 grid_ui.addWidget(cur_combo, 9, 1)
1923 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1925 view_label=QLabel(_('Receive Tab') + ':')
1926 grid_ui.addWidget(view_label , 10, 0)
1927 view_combo = QComboBox()
1928 view_combo.addItems([_('Simple'), _('Advanced')])
1929 view_combo.setCurrentIndex(self.expert_mode)
1930 grid_ui.addWidget(view_combo, 10, 1)
1931 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1932 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1933 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1935 grid_ui.addWidget(HelpButton(hh), 10, 2)
1939 grid_wallet = QGridLayout(tab2)
1940 grid_wallet.setColumnStretch(0,1)
1941 tabs.addTab(tab2, _('Wallet') )
1943 fee_label = QLabel(_('Transaction fee'))
1944 grid_wallet.addWidget(fee_label, 0, 0)
1946 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1947 grid_wallet.addWidget(fee_e, 0, 1)
1948 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1949 + _('Recommended value') + ': 0.001'
1950 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1951 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1952 if not self.config.is_modifiable('fee'):
1953 for w in [fee_e, fee_label]: w.setEnabled(False)
1955 usechange_label = QLabel(_('Use change addresses'))
1956 grid_wallet.addWidget(usechange_label, 1, 0)
1957 usechange_combo = QComboBox()
1958 usechange_combo.addItems([_('Yes'), _('No')])
1959 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1960 grid_wallet.addWidget(usechange_combo, 1, 1)
1961 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1962 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1964 gap_label = QLabel(_('Gap limit'))
1965 grid_wallet.addWidget(gap_label, 2, 0)
1967 gap_e.setText("%d"% self.wallet.gap_limit)
1968 grid_wallet.addWidget(gap_e, 2, 1)
1969 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1970 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1971 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1972 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1973 + _('Warning') + ': ' \
1974 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1975 + _('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'
1976 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1977 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1978 if not self.config.is_modifiable('gap_limit'):
1979 for w in [gap_e, gap_label]: w.setEnabled(False)
1981 grid_wallet.setRowStretch(3,1)
1986 grid_io = QGridLayout(tab3)
1987 grid_io.setColumnStretch(0,1)
1988 tabs.addTab(tab3, _('Import/Export') )
1990 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1991 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1992 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1993 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1995 grid_io.addWidget(QLabel(_('History')), 2, 0)
1996 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1997 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1999 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2001 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2002 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2003 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2005 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2006 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2007 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2008 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2009 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2011 grid_io.setRowStretch(4,1)
2014 grid_raw = QGridLayout(tab4)
2015 grid_raw.setColumnStretch(0,1)
2016 tabs.addTab(tab4, _('Raw tx') ) # move this to wallet tab
2018 if self.wallet.seed:
2019 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2020 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2021 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2022 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2024 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2025 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2026 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2027 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2028 grid_raw.setRowStretch(3,1)
2033 grid_plugins = QGridLayout(tab5)
2034 grid_plugins.setColumnStretch(0,1)
2035 tabs.addTab(tab5, _('Plugins') )
2036 def mk_toggle(cb, p):
2037 return lambda: cb.setChecked(p.toggle(self))
2038 for i, p in enumerate(self.plugins):
2040 name, description = p.get_info()
2041 cb = QCheckBox(name)
2042 cb.setChecked(p.is_enabled())
2043 cb.stateChanged.connect(mk_toggle(cb,p))
2044 grid_plugins.addWidget(cb, i, 0)
2045 grid_plugins.addWidget(HelpButton(description), i, 2)
2047 print_msg("Error: cannot display plugin", p)
2048 traceback.print_exc(file=sys.stdout)
2049 grid_plugins.setRowStretch(i+1,1)
2051 self.run_hook('create_settings_tab', (self,tabs,))
2053 vbox.addLayout(ok_cancel_buttons(d))
2057 if not d.exec_(): return
2059 fee = unicode(fee_e.text())
2061 fee = int( 100000000 * Decimal(fee) )
2063 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2066 if self.wallet.fee != fee:
2067 self.wallet.fee = fee
2070 nz = unicode(nz_e.text())
2075 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2078 if self.wallet.num_zeros != nz:
2079 self.wallet.num_zeros = nz
2080 self.config.set_key('num_zeros', nz, True)
2081 self.update_history_tab()
2082 self.update_receive_tab()
2084 usechange_result = usechange_combo.currentIndex() == 0
2085 if self.wallet.use_change != usechange_result:
2086 self.wallet.use_change = usechange_result
2087 self.config.set_key('use_change', self.wallet.use_change, True)
2090 n = int(gap_e.text())
2092 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2095 if self.wallet.gap_limit != n:
2096 r = self.wallet.change_gap_limit(n)
2098 self.update_receive_tab()
2099 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2101 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2103 need_restart = False
2105 lang_request = languages.keys()[lang_combo.currentIndex()]
2106 if lang_request != self.config.get('language'):
2107 self.config.set_key("language", lang_request, True)
2110 cur_request = str(currencies[cur_combo.currentIndex()])
2111 if cur_request != self.config.get('currency', "None"):
2112 self.config.set_key('currency', cur_request, True)
2113 self.update_wallet()
2116 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2118 self.receive_tab_set_mode(view_combo.currentIndex())
2122 def network_dialog(wallet, parent=None):
2123 interface = wallet.interface
2125 if interface.is_connected:
2126 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2128 status = _("Not connected")
2129 server = interface.server
2132 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2133 server = interface.server
2135 plist, servers_list = interface.get_servers_list()
2139 d.setWindowTitle(_('Server'))
2140 d.setMinimumSize(375, 20)
2142 vbox = QVBoxLayout()
2145 hbox = QHBoxLayout()
2147 l.setPixmap(QPixmap(":icons/network.png"))
2150 hbox.addWidget(QLabel(status))
2152 vbox.addLayout(hbox)
2156 grid = QGridLayout()
2158 vbox.addLayout(grid)
2161 server_protocol = QComboBox()
2162 server_host = QLineEdit()
2163 server_host.setFixedWidth(200)
2164 server_port = QLineEdit()
2165 server_port.setFixedWidth(60)
2167 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2168 protocol_letters = 'thsg'
2169 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2170 server_protocol.addItems(protocol_names)
2172 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2173 grid.addWidget(server_protocol, 0, 1)
2174 grid.addWidget(server_host, 0, 2)
2175 grid.addWidget(server_port, 0, 3)
2177 def change_protocol(p):
2178 protocol = protocol_letters[p]
2179 host = unicode(server_host.text())
2180 pp = plist.get(host,DEFAULT_PORTS)
2181 if protocol not in pp.keys():
2182 protocol = pp.keys()[0]
2184 server_host.setText( host )
2185 server_port.setText( port )
2187 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2189 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2190 servers_list_widget = QTreeWidget(parent)
2191 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2192 servers_list_widget.setMaximumHeight(150)
2193 servers_list_widget.setColumnWidth(0, 240)
2194 for _host in servers_list.keys():
2195 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2196 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2198 def change_server(host, protocol=None):
2199 pp = plist.get(host,DEFAULT_PORTS)
2201 port = pp.get(protocol)
2202 if not port: protocol = None
2205 if 't' in pp.keys():
2207 port = pp.get(protocol)
2209 protocol = pp.keys()[0]
2210 port = pp.get(protocol)
2212 server_host.setText( host )
2213 server_port.setText( port )
2214 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2216 if not plist: return
2217 for p in protocol_letters:
2218 i = protocol_letters.index(p)
2219 j = server_protocol.model().index(i,0)
2220 if p not in pp.keys():
2221 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2223 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2227 host, port, protocol = server.split(':')
2228 change_server(host,protocol)
2230 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2231 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2233 if not wallet.config.is_modifiable('server'):
2234 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2237 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2238 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2239 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2240 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2243 proxy_mode = QComboBox()
2244 proxy_host = QLineEdit()
2245 proxy_host.setFixedWidth(200)
2246 proxy_port = QLineEdit()
2247 proxy_port.setFixedWidth(60)
2248 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2250 def check_for_disable(index = False):
2251 if proxy_mode.currentText() != 'NONE':
2252 proxy_host.setEnabled(True)
2253 proxy_port.setEnabled(True)
2255 proxy_host.setEnabled(False)
2256 proxy_port.setEnabled(False)
2259 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2261 if not wallet.config.is_modifiable('proxy'):
2262 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2264 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2265 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2266 proxy_host.setText(proxy_config.get("host"))
2267 proxy_port.setText(proxy_config.get("port"))
2269 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2270 grid.addWidget(proxy_mode, 2, 1)
2271 grid.addWidget(proxy_host, 2, 2)
2272 grid.addWidget(proxy_port, 2, 3)
2275 vbox.addLayout(ok_cancel_buttons(d))
2278 if not d.exec_(): return
2280 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2281 if proxy_mode.currentText() != 'NONE':
2282 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2286 wallet.config.set_key("proxy", proxy, True)
2287 wallet.config.set_key("server", server, True)
2288 interface.set_server(server, proxy)
2289 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2292 def closeEvent(self, event):
2294 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2295 self.save_column_widths()
2296 self.config.set_key("column-widths", self.column_widths, True)
2297 self.config.set_key("console-history",self.console.history[-50:])
2303 def __init__(self, wallet, config, app=None):
2304 self.wallet = wallet
2305 self.config = config
2307 self.app = QApplication(sys.argv)
2310 def restore_or_create(self):
2311 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2312 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2313 if r==2: return None
2314 return 'restore' if r==1 else 'create'
2316 def seed_dialog(self):
2317 return ElectrumWindow.seed_dialog( self.wallet )
2319 def network_dialog(self):
2320 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2323 def show_seed(self):
2324 ElectrumWindow.show_seed(self.wallet.seed)
2327 def password_dialog(self):
2328 if self.wallet.seed:
2329 ElectrumWindow.change_password_dialog(self.wallet)
2332 def restore_wallet(self):
2333 wallet = self.wallet
2334 # wait until we are connected, because the user might have selected another server
2335 if not wallet.interface.is_connected:
2336 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2337 waiting_dialog(waiting)
2339 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2340 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2342 wallet.set_up_to_date(False)
2343 wallet.interface.poke('synchronizer')
2344 waiting_dialog(waiting)
2345 if wallet.is_found():
2346 print_error( "Recovery successful" )
2348 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2355 w = ElectrumWindow(self.wallet, self.config)
2356 if url: w.set_url(url)