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:
229 def waiting_dialog(f):
235 w.setWindowTitle('Electrum')
245 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
250 def ok_cancel_buttons(dialog, ok_label=_("OK") ):
253 b = QPushButton(_("Cancel"))
255 b.clicked.connect(dialog.reject)
256 b = QPushButton(ok_label)
258 b.clicked.connect(dialog.accept)
263 def text_dialog(parent, title, label, ok_label):
264 dialog = QDialog(parent)
265 dialog.setMinimumWidth(500)
266 dialog.setWindowTitle(title)
270 l.addWidget(QLabel(label))
273 l.addLayout(ok_cancel_buttons(dialog, ok_label))
275 return unicode(txt.toPlainText())
279 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
280 "receive":[[370],[370,200,130]] }
282 class ElectrumWindow(QMainWindow):
284 def __init__(self, wallet, config):
285 QMainWindow.__init__(self)
291 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
292 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
293 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
294 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
296 self.expert_mode = config.get('classic_expert_mode', False)
298 set_language(config.get('language'))
300 self.funds_error = False
301 self.completions = QStringListModel()
303 self.tabs = tabs = QTabWidget(self)
304 self.column_widths = self.config.get("column-widths", default_column_widths )
305 tabs.addTab(self.create_history_tab(), _('History') )
306 tabs.addTab(self.create_send_tab(), _('Send') )
307 tabs.addTab(self.create_receive_tab(), _('Receive') )
308 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
309 tabs.addTab(self.create_console_tab(), _('Console') )
310 tabs.setMinimumSize(600, 400)
311 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
312 self.setCentralWidget(tabs)
313 self.create_status_bar()
315 g = self.config.get("winpos-qt",[100, 100, 840, 400])
316 self.setGeometry(g[0], g[1], g[2], g[3])
317 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
318 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
319 self.setWindowTitle( title )
321 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
322 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
323 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
324 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
326 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
327 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
328 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
329 self.history_list.setFocus(True)
331 self.exchanger = exchange_rate.Exchanger(self)
332 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
334 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
335 if platform.system() == 'Windows':
336 n = 3 if self.wallet.seed else 2
337 tabs.setCurrentIndex (n)
338 tabs.setCurrentIndex (0)
340 # set initial message
341 self.console.showMessage(self.wallet.banner)
343 # plugins that need to change the GUI do it here
344 self.run_hook('init')
348 def init_plugins(self):
349 import imp, pkgutil, __builtin__
350 if __builtin__.use_local_modules:
351 fp, pathname, description = imp.find_module('plugins')
352 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
353 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
354 imp.load_module('electrum_plugins', fp, pathname, description)
355 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
357 import electrum_plugins
358 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
359 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
361 self.plugin_hooks = {}
362 for p in self.plugins:
366 print_msg("Error:cannot initialize plugin",p)
367 traceback.print_exc(file=sys.stdout)
369 def set_hook(self, name, callback):
370 h = self.plugin_hooks.get(name, [])
372 self.plugin_hooks[name] = h
374 def unset_hook(self, name, callback):
375 h = self.plugin_hooks.get(name,[])
376 if callback in h: h.remove(callback)
377 self.plugin_hooks[name] = h
379 def run_hook(self, name, *args):
380 args = (self,) + args
381 for cb in self.plugin_hooks.get(name,[]):
385 def set_label(self, name, text = None):
387 old_text = self.wallet.labels.get(name)
390 self.wallet.labels[name] = text
394 self.wallet.labels.pop(name)
396 self.run_hook('set_label', name, text, changed)
400 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
401 def getOpenFileName(self, title, filter = None):
402 directory = self.config.get('io_dir', os.path.expanduser('~'))
403 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
404 if fileName and directory != os.path.dirname(fileName):
405 self.config.set_key('io_dir', os.path.dirname(fileName), True)
408 def getSaveFileName(self, title, filename, filter = None):
409 directory = self.config.get('io_dir', os.path.expanduser('~'))
410 path = os.path.join( directory, filename )
411 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
412 if fileName and directory != os.path.dirname(fileName):
413 self.config.set_key('io_dir', os.path.dirname(fileName), True)
419 QMainWindow.close(self)
420 self.run_hook('close_main_window')
422 def connect_slots(self, sender):
423 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
424 self.previous_payto_e=''
426 def timer_actions(self):
427 self.run_hook('timer_actions')
429 if self.payto_e.hasFocus():
431 r = unicode( self.payto_e.text() )
432 if r != self.previous_payto_e:
433 self.previous_payto_e = r
435 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
437 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
441 s = r + ' <' + to_address + '>'
442 self.payto_e.setText(s)
446 def update_status(self):
447 if self.wallet.interface and self.wallet.interface.is_connected:
448 if not self.wallet.up_to_date:
449 text = _("Synchronizing...")
450 icon = QIcon(":icons/status_waiting.png")
452 c, u = self.wallet.get_balance()
453 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
454 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
455 text += self.create_quote_text(Decimal(c+u)/100000000)
456 icon = QIcon(":icons/status_connected.png")
458 text = _("Not connected")
459 icon = QIcon(":icons/status_disconnected.png")
461 self.status_text = text
462 self.statusBar().showMessage(text)
463 self.status_button.setIcon( icon )
465 def update_wallet(self):
467 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
468 self.update_history_tab()
469 self.update_receive_tab()
470 self.update_contacts_tab()
471 self.update_completions()
474 def create_quote_text(self, btc_balance):
475 quote_currency = self.config.get("currency", "None")
476 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
477 if quote_balance is None:
480 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
483 def create_history_tab(self):
484 self.history_list = l = MyTreeWidget(self)
486 for i,width in enumerate(self.column_widths['history']):
487 l.setColumnWidth(i, width)
488 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
489 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
490 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
492 l.setContextMenuPolicy(Qt.CustomContextMenu)
493 l.customContextMenuRequested.connect(self.create_history_menu)
497 def create_history_menu(self, position):
498 self.history_list.selectedIndexes()
499 item = self.history_list.currentItem()
501 tx_hash = str(item.data(0, Qt.UserRole).toString())
502 if not tx_hash: return
504 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
505 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
506 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
507 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
510 def show_tx_details(self, tx):
511 dialog = QDialog(self)
513 dialog.setWindowTitle(_("Transaction Details"))
515 dialog.setLayout(vbox)
516 dialog.setMinimumSize(600,300)
519 if tx_hash in self.wallet.transactions.keys():
520 is_mine, v, fee = self.wallet.get_tx_value(tx)
521 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
523 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
529 vbox.addWidget(QLabel("Transaction ID:"))
530 e = QLineEdit(tx_hash)
534 vbox.addWidget(QLabel("Date: %s"%time_str))
535 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
538 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
539 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
541 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
542 vbox.addWidget(QLabel("Transaction fee: unknown"))
544 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
546 vbox.addWidget( self.generate_transaction_information_widget(tx) )
548 ok_button = QPushButton(_("Close"))
549 ok_button.setDefault(True)
550 ok_button.clicked.connect(dialog.accept)
554 hbox.addWidget(ok_button)
558 def tx_label_clicked(self, item, column):
559 if column==2 and item.isSelected():
561 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
562 self.history_list.editItem( item, column )
563 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
566 def tx_label_changed(self, item, column):
570 tx_hash = str(item.data(0, Qt.UserRole).toString())
571 tx = self.wallet.transactions.get(tx_hash)
572 text = unicode( item.text(2) )
573 self.set_label(tx_hash, text)
575 item.setForeground(2, QBrush(QColor('black')))
577 text = self.wallet.get_default_label(tx_hash)
578 item.setText(2, text)
579 item.setForeground(2, QBrush(QColor('gray')))
583 def edit_label(self, is_recv):
584 l = self.receive_list if is_recv else self.contacts_list
585 item = l.currentItem()
586 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
587 l.editItem( item, 1 )
588 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
592 def address_label_clicked(self, item, column, l, column_addr, column_label):
593 if column == column_label and item.isSelected():
594 addr = unicode( item.text(column_addr) )
595 label = unicode( item.text(column_label) )
596 if label in self.wallet.aliases.keys():
598 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
599 l.editItem( item, column )
600 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
603 def address_label_changed(self, item, column, l, column_addr, column_label):
605 if column == column_label:
606 addr = unicode( item.text(column_addr) )
607 text = unicode( item.text(column_label) )
610 if text in self.wallet.aliases.keys():
611 print_error("Error: This is one of your aliases")
612 label = self.wallet.labels.get(addr,'')
613 item.setText(column_label, QString(label))
615 changed = self.set_label(addr, text)
617 self.update_history_tab()
618 self.update_completions()
620 self.current_item_changed(item)
622 self.run_hook('item_changed', item, column)
625 def current_item_changed(self, a):
626 self.run_hook('current_item_changed', a)
630 def update_history_tab(self):
632 self.history_list.clear()
633 for item in self.wallet.get_tx_history():
634 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
637 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
643 icon = QIcon(":icons/unconfirmed.png")
645 icon = QIcon(":icons/clock%d.png"%conf)
647 icon = QIcon(":icons/confirmed.png")
650 icon = QIcon(":icons/unconfirmed.png")
652 if value is not None:
653 v_str = format_satoshis(value, True, self.wallet.num_zeros)
657 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
660 label, is_default_label = self.wallet.get_label(tx_hash)
662 label = _('Pruned transaction outputs')
663 is_default_label = False
665 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
666 item.setFont(2, QFont(MONOSPACE_FONT))
667 item.setFont(3, QFont(MONOSPACE_FONT))
668 item.setFont(4, QFont(MONOSPACE_FONT))
670 item.setForeground(3, QBrush(QColor("#BC1E1E")))
672 item.setData(0, Qt.UserRole, tx_hash)
673 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
675 item.setForeground(2, QBrush(QColor('grey')))
677 item.setIcon(0, icon)
678 self.history_list.insertTopLevelItem(0,item)
681 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
684 def create_send_tab(self):
689 grid.setColumnMinimumWidth(3,300)
690 grid.setColumnStretch(5,1)
692 self.payto_e = QLineEdit()
693 grid.addWidget(QLabel(_('Pay to')), 1, 0)
694 grid.addWidget(self.payto_e, 1, 1, 1, 3)
696 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)
698 completer = QCompleter()
699 completer.setCaseSensitivity(False)
700 self.payto_e.setCompleter(completer)
701 completer.setModel(self.completions)
703 self.message_e = QLineEdit()
704 grid.addWidget(QLabel(_('Description')), 2, 0)
705 grid.addWidget(self.message_e, 2, 1, 1, 3)
706 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)
708 self.amount_e = QLineEdit()
709 grid.addWidget(QLabel(_('Amount')), 3, 0)
710 grid.addWidget(self.amount_e, 3, 1, 1, 2)
711 grid.addWidget(HelpButton(
712 _('Amount to be sent.') + '\n\n' \
713 + _('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)
715 self.fee_e = QLineEdit()
716 grid.addWidget(QLabel(_('Fee')), 4, 0)
717 grid.addWidget(self.fee_e, 4, 1, 1, 2)
718 grid.addWidget(HelpButton(
719 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
720 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
721 + _('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)
724 b = EnterButton(_("Send"), self.do_send)
726 b = EnterButton(_("Create unsigned transaction"), self.do_send)
727 grid.addWidget(b, 6, 1)
729 b = EnterButton(_("Clear"),self.do_clear)
730 grid.addWidget(b, 6, 2)
732 self.payto_sig = QLabel('')
733 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
735 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
736 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
745 def entry_changed( is_fee ):
746 self.funds_error = False
747 amount = numbify(self.amount_e)
748 fee = numbify(self.fee_e)
749 if not is_fee: fee = None
752 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
754 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
757 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
758 text = self.status_text
761 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
762 self.funds_error = True
763 text = _( "Not enough funds" )
765 self.statusBar().showMessage(text)
766 self.amount_e.setPalette(palette)
767 self.fee_e.setPalette(palette)
769 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
770 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
772 self.run_hook('create_send_tab', grid)
776 def update_completions(self):
778 for addr,label in self.wallet.labels.items():
779 if addr in self.wallet.addressbook:
780 l.append( label + ' <' + addr + '>')
781 l = l + self.wallet.aliases.keys()
783 self.completions.setStringList(l)
787 return lambda s, *args: s.do_protect(func, args)
791 def do_send(self, password):
793 label = unicode( self.message_e.text() )
794 r = unicode( self.payto_e.text() )
798 m1 = re.match(ALIAS_REGEXP, r)
799 # label or alias, with address in brackets
800 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
803 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
807 to_address = m2.group(2)
811 if not is_valid(to_address):
812 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
816 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
818 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
821 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
823 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
827 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
828 except BaseException, e:
829 self.show_message(str(e))
832 self.run_hook('send_tx', tx)
835 self.set_label(tx.hash(), label)
838 h = self.wallet.send_tx(tx)
839 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
840 status, msg = self.wallet.receive_tx( h )
842 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
844 self.update_contacts_tab()
846 QMessageBox.warning(self, _('Error'), msg, _('OK'))
848 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
850 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
851 with open(fileName,'w') as f:
852 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
853 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
855 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
860 def set_url(self, url):
861 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
862 self.tabs.setCurrentIndex(1)
863 label = self.wallet.labels.get(payto)
864 m_addr = label + ' <'+ payto+'>' if label else payto
865 self.payto_e.setText(m_addr)
867 self.message_e.setText(message)
868 self.amount_e.setText(amount)
870 self.set_frozen(self.payto_e,True)
871 self.set_frozen(self.amount_e,True)
872 self.set_frozen(self.message_e,True)
873 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
875 self.payto_sig.setVisible(False)
878 self.payto_sig.setVisible(False)
879 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
881 self.set_frozen(e,False)
883 def set_frozen(self,entry,frozen):
885 entry.setReadOnly(True)
886 entry.setFrame(False)
888 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
889 entry.setPalette(palette)
891 entry.setReadOnly(False)
894 palette.setColor(entry.backgroundRole(), QColor('white'))
895 entry.setPalette(palette)
898 def toggle_freeze(self,addr):
900 if addr in self.wallet.frozen_addresses:
901 self.wallet.unfreeze(addr)
903 self.wallet.freeze(addr)
904 self.update_receive_tab()
906 def toggle_priority(self,addr):
908 if addr in self.wallet.prioritized_addresses:
909 self.wallet.unprioritize(addr)
911 self.wallet.prioritize(addr)
912 self.update_receive_tab()
915 def create_list_tab(self, headers):
916 "generic tab creation method"
917 l = MyTreeWidget(self)
918 l.setColumnCount( len(headers) )
919 l.setHeaderLabels( headers )
929 vbox.addWidget(buttons)
934 buttons.setLayout(hbox)
939 def create_receive_tab(self):
940 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
941 l.setContextMenuPolicy(Qt.CustomContextMenu)
942 l.customContextMenuRequested.connect(self.create_receive_menu)
943 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
944 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
945 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
946 self.receive_list = l
947 self.receive_buttons_hbox = hbox
952 def receive_tab_set_mode(self, i):
953 self.save_column_widths()
954 self.expert_mode = (i == 1)
955 self.config.set_key('classic_expert_mode', self.expert_mode, True)
957 self.update_receive_tab()
960 def save_column_widths(self):
961 if not self.expert_mode:
962 widths = [ self.receive_list.columnWidth(0) ]
965 for i in range(self.receive_list.columnCount() -1):
966 widths.append(self.receive_list.columnWidth(i))
967 self.column_widths["receive"][self.expert_mode] = widths
969 self.column_widths["history"] = []
970 for i in range(self.history_list.columnCount() - 1):
971 self.column_widths["history"].append(self.history_list.columnWidth(i))
973 self.column_widths["contacts"] = []
974 for i in range(self.contacts_list.columnCount() - 1):
975 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
978 def create_contacts_tab(self):
979 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
980 l.setContextMenuPolicy(Qt.CustomContextMenu)
981 l.customContextMenuRequested.connect(self.create_contact_menu)
982 for i,width in enumerate(self.column_widths['contacts']):
983 l.setColumnWidth(i, width)
985 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
986 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
987 self.contacts_list = l
988 self.contacts_buttons_hbox = hbox
989 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
994 def delete_imported_key(self, addr):
995 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
996 self.wallet.imported_keys.pop(addr)
997 self.update_receive_tab()
998 self.update_history_tab()
1002 def create_receive_menu(self, position):
1003 # fixme: this function apparently has a side effect.
1004 # if it is not called the menu pops up several times
1005 #self.receive_list.selectedIndexes()
1007 item = self.receive_list.itemAt(position)
1009 addr = unicode(item.text(0))
1010 if not is_valid(addr):
1011 item.setExpanded(not item.isExpanded())
1014 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1015 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1016 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1017 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1018 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1019 if addr in self.wallet.imported_keys:
1020 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1022 if self.expert_mode:
1023 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1024 menu.addAction(t, lambda: self.toggle_freeze(addr))
1025 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1026 menu.addAction(t, lambda: self.toggle_priority(addr))
1028 self.run_hook('receive_menu', menu)
1029 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1032 def payto(self, x, is_alias):
1039 label = self.wallet.labels.get(addr)
1040 m_addr = label + ' <' + addr + '>' if label else addr
1041 self.tabs.setCurrentIndex(1)
1042 self.payto_e.setText(m_addr)
1043 self.amount_e.setFocus()
1045 def delete_contact(self, x, is_alias):
1046 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1047 if not is_alias and x in self.wallet.addressbook:
1048 self.wallet.addressbook.remove(x)
1049 self.set_label(x, None)
1050 elif is_alias and x in self.wallet.aliases:
1051 self.wallet.aliases.pop(x)
1052 self.update_history_tab()
1053 self.update_contacts_tab()
1054 self.update_completions()
1056 def create_contact_menu(self, position):
1057 # fixme: this function apparently has a side effect.
1058 # if it is not called the menu pops up several times
1059 #self.contacts_list.selectedIndexes()
1061 item = self.contacts_list.itemAt(position)
1063 addr = unicode(item.text(0))
1064 label = unicode(item.text(1))
1065 is_alias = label in self.wallet.aliases.keys()
1066 x = label if is_alias else addr
1068 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1069 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1070 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1072 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1074 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1075 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1076 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1079 def update_receive_item(self, item):
1080 item.setFont(0, QFont(MONOSPACE_FONT))
1081 address = str(item.data(0,0).toString())
1082 label = self.wallet.labels.get(address,'')
1083 item.setData(1,0,label)
1085 self.run_hook('update_receive_item', address, item)
1087 c, u = self.wallet.get_addr_balance(address)
1088 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1089 item.setData(2,0,balance)
1091 if self.expert_mode:
1092 if address in self.wallet.frozen_addresses:
1093 item.setBackgroundColor(0, QColor('lightblue'))
1094 elif address in self.wallet.prioritized_addresses:
1095 item.setBackgroundColor(0, QColor('lightgreen'))
1098 def update_receive_tab(self):
1099 l = self.receive_list
1102 l.setColumnHidden(2, not self.expert_mode)
1103 l.setColumnHidden(3, not self.expert_mode)
1104 if not self.expert_mode:
1105 width = self.column_widths['receive'][0][0]
1106 l.setColumnWidth(0, width)
1108 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1109 l.setColumnWidth(i, width)
1112 for k, account in self.wallet.accounts.items():
1113 name = account.get('name',str(k))
1114 c,u = self.wallet.get_account_balance(k)
1115 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1116 l.addTopLevelItem(account_item)
1117 account_item.setExpanded(True)
1120 for is_change in [0,1]:
1121 name = "Receiving" if not is_change else "Change"
1122 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1123 account_item.addChild(seq_item)
1124 if not is_change: seq_item.setExpanded(True)
1128 for address in account[is_change]:
1129 h = self.wallet.history.get(address,[])
1134 if gap > self.wallet.gap_limit:
1139 num_tx = '*' if h == ['*'] else "%d"%len(h)
1140 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1141 self.update_receive_item(item)
1143 item.setBackgroundColor(1, QColor('red'))
1144 seq_item.addChild(item)
1146 if self.wallet.imported_keys:
1147 c,u = self.wallet.get_imported_balance()
1148 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1149 l.addTopLevelItem(account_item)
1150 account_item.setExpanded(True)
1151 for address in self.wallet.imported_keys.keys():
1152 item = QTreeWidgetItem( [ address, '', '', ''] )
1153 self.update_receive_item(item)
1154 account_item.addChild(item)
1157 # we use column 1 because column 0 may be hidden
1158 l.setCurrentItem(l.topLevelItem(0),1)
1160 def show_contact_details(self, m):
1161 a = self.wallet.aliases.get(m)
1163 if a[0] in self.wallet.authorities.keys():
1164 s = self.wallet.authorities.get(a[0])
1167 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1168 QMessageBox.information(self, 'Alias', msg, 'OK')
1170 def update_contacts_tab(self):
1172 l = self.contacts_list
1176 for alias, v in self.wallet.aliases.items():
1178 alias_targets.append(target)
1179 item = QTreeWidgetItem( [ target, alias, '-'] )
1180 item.setBackgroundColor(0, QColor('lightgray'))
1181 l.addTopLevelItem(item)
1183 for address in self.wallet.addressbook:
1184 if address in alias_targets: continue
1185 label = self.wallet.labels.get(address,'')
1187 for tx in self.wallet.transactions.values():
1188 if address in map(lambda x: x[0], tx.outputs): n += 1
1190 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1191 item.setFont(0, QFont(MONOSPACE_FONT))
1192 l.addTopLevelItem(item)
1194 l.setCurrentItem(l.topLevelItem(0))
1197 def create_console_tab(self):
1198 from qt_console import Console
1199 self.console = console = Console()
1200 self.console.history = self.config.get("console-history",[])
1201 self.console.history_index = len(self.console.history)
1203 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1204 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1206 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1208 def mkfunc(f, method):
1209 return lambda *args: apply( f, (method, args, self.password_dialog ))
1211 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1212 methods[m] = mkfunc(c._run, m)
1214 console.updateNamespace(methods)
1218 def create_status_bar(self):
1219 self.status_text = ""
1221 sb.setFixedHeight(35)
1222 qtVersion = qVersion()
1224 update_notification = UpdateLabel(self.config)
1225 if(update_notification.new_version):
1226 sb.addPermanentWidget(update_notification)
1228 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1229 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1230 if self.wallet.seed:
1231 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1232 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1233 if self.wallet.seed:
1234 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1235 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1236 sb.addPermanentWidget( self.status_button )
1238 self.setStatusBar(sb)
1242 self.config.set_key('gui', 'lite', True)
1245 self.lite.mini.show()
1247 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1248 self.lite.main(None)
1250 def new_contact_dialog(self):
1251 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1252 address = unicode(text)
1254 if is_valid(address):
1255 self.wallet.addressbook.append(address)
1257 self.update_contacts_tab()
1258 self.update_history_tab()
1259 self.update_completions()
1261 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1263 def show_master_public_key(self):
1264 dialog = QDialog(self)
1266 dialog.setWindowTitle(_("Master Public Key"))
1268 main_text = QTextEdit()
1269 main_text.setText(self.wallet.get_master_public_key())
1270 main_text.setReadOnly(True)
1271 main_text.setMaximumHeight(170)
1272 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1274 ok_button = QPushButton(_("OK"))
1275 ok_button.setDefault(True)
1276 ok_button.clicked.connect(dialog.accept)
1278 main_layout = QGridLayout()
1279 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1281 main_layout.addWidget(main_text, 1, 0)
1282 main_layout.addWidget(qrw, 1, 1 )
1284 vbox = QVBoxLayout()
1285 vbox.addLayout(main_layout)
1286 hbox = QHBoxLayout()
1288 hbox.addWidget(ok_button)
1289 vbox.addLayout(hbox)
1291 dialog.setLayout(vbox)
1296 def show_seed_dialog(self, password):
1297 if not self.wallet.seed:
1298 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1301 seed = self.wallet.decode_seed(password)
1303 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1305 self.show_seed(seed, self)
1309 def show_seed(self, seed, parent=None):
1310 dialog = QDialog(parent)
1312 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1314 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1316 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1318 seed_text = QTextEdit(brainwallet)
1319 seed_text.setReadOnly(True)
1320 seed_text.setMaximumHeight(130)
1322 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1323 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1324 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1325 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1326 label2 = QLabel(msg2)
1327 label2.setWordWrap(True)
1330 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1331 logo.setMaximumWidth(60)
1333 qrw = QRCodeWidget(seed)
1335 ok_button = QPushButton(_("OK"))
1336 ok_button.setDefault(True)
1337 ok_button.clicked.connect(dialog.accept)
1339 grid = QGridLayout()
1340 #main_layout.addWidget(logo, 0, 0)
1342 grid.addWidget(logo, 0, 0)
1343 grid.addWidget(label1, 0, 1)
1345 grid.addWidget(seed_text, 1, 0, 1, 2)
1347 grid.addWidget(qrw, 0, 2, 2, 1)
1349 vbox = QVBoxLayout()
1350 vbox.addLayout(grid)
1351 vbox.addWidget(label2)
1353 hbox = QHBoxLayout()
1355 hbox.addWidget(ok_button)
1356 vbox.addLayout(hbox)
1358 dialog.setLayout(vbox)
1361 def show_qrcode(self, data, title = "QR code"):
1365 d.setWindowTitle(title)
1366 d.setMinimumSize(270, 300)
1367 vbox = QVBoxLayout()
1368 qrw = QRCodeWidget(data)
1369 vbox.addWidget(qrw, 1)
1370 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1371 hbox = QHBoxLayout()
1375 filename = "qrcode.bmp"
1376 bmp.save_qrcode(qrw.qr, filename)
1377 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1379 b = QPushButton(_("Save"))
1381 b.clicked.connect(print_qr)
1383 b = QPushButton(_("Close"))
1385 b.clicked.connect(d.accept)
1388 vbox.addLayout(hbox)
1393 def do_protect(self, func, args):
1394 if self.wallet.use_encryption:
1395 password = self.password_dialog()
1401 if args != (False,):
1402 args = (self,) + args + (password,)
1404 args = (self,password)
1409 def show_private_key(self, address, password):
1410 if not address: return
1412 pk = self.wallet.get_private_key(address, password)
1413 except BaseException, e:
1414 self.show_message(str(e))
1416 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1420 def do_sign(self, address, message, signature, password):
1422 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1423 signature.setText(sig)
1424 except BaseException, e:
1425 self.show_message(str(e))
1427 def sign_message(self, address):
1428 if not address: return
1431 d.setWindowTitle(_('Sign Message'))
1432 d.setMinimumSize(410, 290)
1434 tab_widget = QTabWidget()
1436 layout = QGridLayout(tab)
1438 sign_address = QLineEdit()
1440 sign_address.setText(address)
1441 layout.addWidget(QLabel(_('Address')), 1, 0)
1442 layout.addWidget(sign_address, 1, 1)
1444 sign_message = QTextEdit()
1445 layout.addWidget(QLabel(_('Message')), 2, 0)
1446 layout.addWidget(sign_message, 2, 1)
1447 layout.setRowStretch(2,3)
1449 sign_signature = QTextEdit()
1450 layout.addWidget(QLabel(_('Signature')), 3, 0)
1451 layout.addWidget(sign_signature, 3, 1)
1452 layout.setRowStretch(3,1)
1455 hbox = QHBoxLayout()
1456 b = QPushButton(_("Sign"))
1458 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1459 b = QPushButton(_("Close"))
1460 b.clicked.connect(d.accept)
1462 layout.addLayout(hbox, 4, 1)
1463 tab_widget.addTab(tab, _("Sign"))
1467 layout = QGridLayout(tab)
1469 verify_address = QLineEdit()
1470 layout.addWidget(QLabel(_('Address')), 1, 0)
1471 layout.addWidget(verify_address, 1, 1)
1473 verify_message = QTextEdit()
1474 layout.addWidget(QLabel(_('Message')), 2, 0)
1475 layout.addWidget(verify_message, 2, 1)
1476 layout.setRowStretch(2,3)
1478 verify_signature = QTextEdit()
1479 layout.addWidget(QLabel(_('Signature')), 3, 0)
1480 layout.addWidget(verify_signature, 3, 1)
1481 layout.setRowStretch(3,1)
1485 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1486 self.show_message(_("Signature verified"))
1487 except BaseException, e:
1488 self.show_message(str(e))
1491 hbox = QHBoxLayout()
1492 b = QPushButton(_("Verify"))
1493 b.clicked.connect(do_verify)
1495 b = QPushButton(_("Close"))
1496 b.clicked.connect(d.accept)
1498 layout.addLayout(hbox, 4, 1)
1499 tab_widget.addTab(tab, _("Verify"))
1501 vbox = QVBoxLayout()
1502 vbox.addWidget(tab_widget)
1509 def question(self, msg):
1510 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1512 def show_message(self, msg):
1513 QMessageBox.information(self, _('Message'), msg, _('OK'))
1515 def password_dialog(self ):
1522 vbox = QVBoxLayout()
1523 msg = _('Please enter your password')
1524 vbox.addWidget(QLabel(msg))
1526 grid = QGridLayout()
1528 grid.addWidget(QLabel(_('Password')), 1, 0)
1529 grid.addWidget(pw, 1, 1)
1530 vbox.addLayout(grid)
1532 vbox.addLayout(ok_cancel_buttons(d))
1535 if not d.exec_(): return
1536 return unicode(pw.text())
1543 def change_password_dialog( wallet, parent=None ):
1546 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1554 new_pw = QLineEdit()
1555 new_pw.setEchoMode(2)
1556 conf_pw = QLineEdit()
1557 conf_pw.setEchoMode(2)
1559 vbox = QVBoxLayout()
1561 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1562 +_('To disable wallet encryption, enter an empty new password.')) \
1563 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1565 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1566 +_("Leave these fields empty if you want to disable encryption.")
1567 vbox.addWidget(QLabel(msg))
1569 grid = QGridLayout()
1572 if wallet.use_encryption:
1573 grid.addWidget(QLabel(_('Password')), 1, 0)
1574 grid.addWidget(pw, 1, 1)
1576 grid.addWidget(QLabel(_('New Password')), 2, 0)
1577 grid.addWidget(new_pw, 2, 1)
1579 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1580 grid.addWidget(conf_pw, 3, 1)
1581 vbox.addLayout(grid)
1583 vbox.addLayout(ok_cancel_buttons(d))
1586 if not d.exec_(): return
1588 password = unicode(pw.text()) if wallet.use_encryption else None
1589 new_password = unicode(new_pw.text())
1590 new_password2 = unicode(conf_pw.text())
1593 seed = wallet.decode_seed(password)
1595 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1598 if new_password != new_password2:
1599 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1600 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1602 wallet.update_password(seed, password, new_password)
1605 def seed_dialog(wallet, parent=None):
1609 vbox = QVBoxLayout()
1610 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1611 vbox.addWidget(QLabel(msg))
1613 grid = QGridLayout()
1616 seed_e = QLineEdit()
1617 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1618 grid.addWidget(seed_e, 1, 1)
1619 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1623 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1624 grid.addWidget(gap_e, 2, 1)
1625 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1626 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1627 vbox.addLayout(grid)
1629 vbox.addLayout(ok_cancel_buttons(d))
1632 if not d.exec_(): return
1635 gap = int(unicode(gap_e.text()))
1637 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1641 seed = str(seed_e.text())
1644 print_error("Warning: Not hex, trying decode")
1646 seed = mnemonic.mn_decode( seed.split(' ') )
1648 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1652 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1657 def generate_transaction_information_widget(self, tx):
1658 tabs = QTabWidget(self)
1661 grid_ui = QGridLayout(tab1)
1662 grid_ui.setColumnStretch(0,1)
1663 tabs.addTab(tab1, _('Outputs') )
1665 tree_widget = MyTreeWidget(self)
1666 tree_widget.setColumnCount(2)
1667 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1668 tree_widget.setColumnWidth(0, 300)
1669 tree_widget.setColumnWidth(1, 50)
1671 for address, value in tx.outputs:
1672 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1673 tree_widget.addTopLevelItem(item)
1675 tree_widget.setMaximumHeight(100)
1677 grid_ui.addWidget(tree_widget)
1680 grid_ui = QGridLayout(tab2)
1681 grid_ui.setColumnStretch(0,1)
1682 tabs.addTab(tab2, _('Inputs') )
1684 tree_widget = MyTreeWidget(self)
1685 tree_widget.setColumnCount(2)
1686 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1688 for input_line in tx.inputs:
1689 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1690 tree_widget.addTopLevelItem(item)
1692 tree_widget.setMaximumHeight(100)
1694 grid_ui.addWidget(tree_widget)
1698 def tx_dict_from_text(self, txt):
1700 tx_dict = json.loads(str(txt))
1701 assert "hex" in tx_dict.keys()
1702 assert "complete" in tx_dict.keys()
1703 if not tx_dict["complete"]:
1704 assert "input_info" in tx_dict.keys()
1706 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1711 def read_tx_from_file(self):
1712 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1716 with open(fileName, "r") as f:
1717 file_content = f.read()
1718 except (ValueError, IOError, os.error), reason:
1719 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1721 return self.tx_dict_from_text(file_content)
1725 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1727 self.wallet.signrawtransaction(tx, input_info, [], password)
1729 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1731 with open(fileName, "w+") as f:
1732 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1733 self.show_message(_("Transaction saved successfully"))
1736 except BaseException, e:
1737 self.show_message(str(e))
1740 def send_raw_transaction(self, raw_tx, dialog = ""):
1741 result, result_message = self.wallet.sendtx( raw_tx )
1743 self.show_message("Transaction successfully sent: %s" % (result_message))
1747 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1749 def do_process_from_text(self):
1750 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1753 tx_dict = self.tx_dict_from_text(text)
1755 self.create_process_transaction_window(tx_dict)
1757 def do_process_from_file(self):
1758 tx_dict = self.read_tx_from_file()
1760 self.create_process_transaction_window(tx_dict)
1762 def create_process_transaction_window(self, tx_dict):
1763 tx = Transaction(tx_dict["hex"])
1765 dialog = QDialog(self)
1766 dialog.setMinimumWidth(500)
1767 dialog.setWindowTitle(_('Process raw transaction'))
1773 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1774 l.addWidget(QLabel(_("Actions")), 4,0)
1776 if tx_dict["complete"] == False:
1777 l.addWidget(QLabel(_("Unsigned")), 3,1)
1778 if self.wallet.seed :
1779 b = QPushButton("Sign transaction")
1780 input_info = json.loads(tx_dict["input_info"])
1781 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1782 l.addWidget(b, 4, 1)
1784 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1786 l.addWidget(QLabel(_("Signed")), 3,1)
1787 b = QPushButton("Broadcast transaction")
1788 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1791 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1792 cancelButton = QPushButton(_("Cancel"))
1793 cancelButton.clicked.connect(lambda: dialog.done(0))
1794 l.addWidget(cancelButton, 4,2)
1800 def do_export_privkeys(self, password):
1801 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.")))
1804 select_export = _('Select file to export your private keys to')
1805 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1807 with open(fileName, "w+") as csvfile:
1808 transaction = csv.writer(csvfile)
1809 transaction.writerow(["address", "private_key"])
1812 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1813 transaction.writerow(["%34s"%addr,pk])
1815 self.show_message(_("Private keys exported."))
1817 except (IOError, os.error), reason:
1818 export_error_label = _("Electrum was unable to produce a private key-export.")
1819 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1821 except BaseException, e:
1822 self.show_message(str(e))
1826 def do_import_labels(self):
1827 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1828 if not labelsFile: return
1830 f = open(labelsFile, 'r')
1833 for key, value in json.loads(data).items():
1834 self.wallet.labels[key] = value
1836 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1837 except (IOError, os.error), reason:
1838 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1841 def do_export_labels(self):
1842 labels = self.wallet.labels
1844 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1846 with open(fileName, 'w+') as f:
1847 json.dump(labels, f)
1848 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1849 except (IOError, os.error), reason:
1850 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1853 def do_export_history(self):
1854 from gui_lite import csv_transaction
1855 csv_transaction(self.wallet)
1859 def do_import_privkey(self, password):
1860 if not self.wallet.imported_keys:
1861 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1862 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1863 + _('Are you sure you understand what you are doing?'), 3, 4)
1866 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1869 text = str(text).split()
1874 addr = self.wallet.import_key(key, password)
1875 except BaseException as e:
1881 addrlist.append(addr)
1883 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1885 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1886 self.update_receive_tab()
1887 self.update_history_tab()
1890 def settings_dialog(self):
1892 d.setWindowTitle(_('Electrum Settings'))
1894 vbox = QVBoxLayout()
1896 tabs = QTabWidget(self)
1897 vbox.addWidget(tabs)
1900 grid_ui = QGridLayout(tab1)
1901 grid_ui.setColumnStretch(0,1)
1902 tabs.addTab(tab1, _('Display') )
1904 nz_label = QLabel(_('Display zeros'))
1905 grid_ui.addWidget(nz_label, 0, 0)
1907 nz_e.setText("%d"% self.wallet.num_zeros)
1908 grid_ui.addWidget(nz_e, 0, 1)
1909 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1910 grid_ui.addWidget(HelpButton(msg), 0, 2)
1911 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1912 if not self.config.is_modifiable('num_zeros'):
1913 for w in [nz_e, nz_label]: w.setEnabled(False)
1915 lang_label=QLabel(_('Language') + ':')
1916 grid_ui.addWidget(lang_label, 1, 0)
1917 lang_combo = QComboBox()
1918 from i18n import languages
1919 lang_combo.addItems(languages.values())
1921 index = languages.keys().index(self.config.get("language",''))
1924 lang_combo.setCurrentIndex(index)
1925 grid_ui.addWidget(lang_combo, 1, 1)
1926 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1927 if not self.config.is_modifiable('language'):
1928 for w in [lang_combo, lang_label]: w.setEnabled(False)
1930 currencies = self.exchanger.get_currencies()
1931 currencies.insert(0, "None")
1933 cur_label=QLabel(_('Currency') + ':')
1934 grid_ui.addWidget(cur_label , 2, 0)
1935 cur_combo = QComboBox()
1936 cur_combo.addItems(currencies)
1938 index = currencies.index(self.config.get('currency', "None"))
1941 cur_combo.setCurrentIndex(index)
1942 grid_ui.addWidget(cur_combo, 2, 1)
1943 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1945 view_label=QLabel(_('Receive Tab') + ':')
1946 grid_ui.addWidget(view_label , 3, 0)
1947 view_combo = QComboBox()
1948 view_combo.addItems([_('Simple'), _('Advanced')])
1949 view_combo.setCurrentIndex(self.expert_mode)
1950 grid_ui.addWidget(view_combo, 3, 1)
1951 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1952 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1953 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1955 grid_ui.addWidget(HelpButton(hh), 3, 2)
1956 grid_ui.setRowStretch(4,1)
1960 grid_wallet = QGridLayout(tab2)
1961 grid_wallet.setColumnStretch(0,1)
1962 tabs.addTab(tab2, _('Wallet') )
1964 fee_label = QLabel(_('Transaction fee'))
1965 grid_wallet.addWidget(fee_label, 0, 0)
1967 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1968 grid_wallet.addWidget(fee_e, 0, 2)
1969 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1970 + _('Recommended value') + ': 0.001'
1971 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1972 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1973 if not self.config.is_modifiable('fee'):
1974 for w in [fee_e, fee_label]: w.setEnabled(False)
1976 usechange_label = QLabel(_('Use change addresses'))
1977 grid_wallet.addWidget(usechange_label, 1, 0)
1978 usechange_combo = QComboBox()
1979 usechange_combo.addItems([_('Yes'), _('No')])
1980 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1981 grid_wallet.addWidget(usechange_combo, 1, 2)
1982 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1983 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1985 gap_label = QLabel(_('Gap limit'))
1986 grid_wallet.addWidget(gap_label, 2, 0)
1988 gap_e.setText("%d"% self.wallet.gap_limit)
1989 grid_wallet.addWidget(gap_e, 2, 2)
1990 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1991 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1992 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1993 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1994 + _('Warning') + ': ' \
1995 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1996 + _('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'
1997 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1998 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1999 if not self.config.is_modifiable('gap_limit'):
2000 for w in [gap_e, gap_label]: w.setEnabled(False)
2002 grid_wallet.setRowStretch(3,1)
2007 grid_io = QGridLayout(tab3)
2008 grid_io.setColumnStretch(0,1)
2009 tabs.addTab(tab3, _('Import/Export') )
2011 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2012 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2013 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2014 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2016 grid_io.addWidget(QLabel(_('History')), 2, 0)
2017 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2018 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2020 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2022 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2023 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2024 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2026 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2027 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2028 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2029 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2030 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2033 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2034 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2035 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2036 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2038 grid_io.setRowStretch(5,1)
2043 tab5 = QScrollArea()
2044 grid_plugins = QGridLayout(tab5)
2045 grid_plugins.setColumnStretch(0,1)
2046 tabs.addTab(tab5, _('Plugins') )
2047 def mk_toggle(cb, p):
2048 return lambda: cb.setChecked(p.toggle(self))
2049 for i, p in enumerate(self.plugins):
2051 name, description = p.get_info()
2052 cb = QCheckBox(name)
2053 cb.setDisabled(not p.is_available())
2054 cb.setChecked(p.is_enabled())
2055 cb.clicked.connect(mk_toggle(cb,p))
2056 grid_plugins.addWidget(cb, i, 0)
2057 grid_plugins.addWidget(HelpButton(description), i, 1)
2059 print_msg("Error: cannot display plugin", p)
2060 traceback.print_exc(file=sys.stdout)
2061 grid_plugins.setRowStretch(i+1,1)
2063 vbox.addLayout(ok_cancel_buttons(d))
2067 if not d.exec_(): return
2069 fee = unicode(fee_e.text())
2071 fee = int( 100000000 * Decimal(fee) )
2073 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2076 if self.wallet.fee != fee:
2077 self.wallet.fee = fee
2080 nz = unicode(nz_e.text())
2085 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2088 if self.wallet.num_zeros != nz:
2089 self.wallet.num_zeros = nz
2090 self.config.set_key('num_zeros', nz, True)
2091 self.update_history_tab()
2092 self.update_receive_tab()
2094 usechange_result = usechange_combo.currentIndex() == 0
2095 if self.wallet.use_change != usechange_result:
2096 self.wallet.use_change = usechange_result
2097 self.config.set_key('use_change', self.wallet.use_change, True)
2100 n = int(gap_e.text())
2102 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2105 if self.wallet.gap_limit != n:
2106 r = self.wallet.change_gap_limit(n)
2108 self.update_receive_tab()
2109 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2111 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2113 need_restart = False
2115 lang_request = languages.keys()[lang_combo.currentIndex()]
2116 if lang_request != self.config.get('language'):
2117 self.config.set_key("language", lang_request, True)
2120 cur_request = str(currencies[cur_combo.currentIndex()])
2121 if cur_request != self.config.get('currency', "None"):
2122 self.config.set_key('currency', cur_request, True)
2123 self.update_wallet()
2126 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2128 self.receive_tab_set_mode(view_combo.currentIndex())
2132 def network_dialog(wallet, parent=None):
2133 interface = wallet.interface
2135 if interface.is_connected:
2136 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2138 status = _("Not connected")
2139 server = interface.server
2142 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2143 server = interface.server
2145 plist, servers_list = interface.get_servers_list()
2149 d.setWindowTitle(_('Server'))
2150 d.setMinimumSize(375, 20)
2152 vbox = QVBoxLayout()
2155 hbox = QHBoxLayout()
2157 l.setPixmap(QPixmap(":icons/network.png"))
2160 hbox.addWidget(QLabel(status))
2162 vbox.addLayout(hbox)
2166 grid = QGridLayout()
2168 vbox.addLayout(grid)
2171 server_protocol = QComboBox()
2172 server_host = QLineEdit()
2173 server_host.setFixedWidth(200)
2174 server_port = QLineEdit()
2175 server_port.setFixedWidth(60)
2177 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2178 protocol_letters = 'thsg'
2179 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2180 server_protocol.addItems(protocol_names)
2182 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2183 grid.addWidget(server_protocol, 0, 1)
2184 grid.addWidget(server_host, 0, 2)
2185 grid.addWidget(server_port, 0, 3)
2187 def change_protocol(p):
2188 protocol = protocol_letters[p]
2189 host = unicode(server_host.text())
2190 pp = plist.get(host,DEFAULT_PORTS)
2191 if protocol not in pp.keys():
2192 protocol = pp.keys()[0]
2194 server_host.setText( host )
2195 server_port.setText( port )
2197 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2199 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2200 servers_list_widget = QTreeWidget(parent)
2201 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2202 servers_list_widget.setMaximumHeight(150)
2203 servers_list_widget.setColumnWidth(0, 240)
2204 for _host in servers_list.keys():
2205 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2206 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2208 def change_server(host, protocol=None):
2209 pp = plist.get(host,DEFAULT_PORTS)
2211 port = pp.get(protocol)
2212 if not port: protocol = None
2215 if 't' in pp.keys():
2217 port = pp.get(protocol)
2219 protocol = pp.keys()[0]
2220 port = pp.get(protocol)
2222 server_host.setText( host )
2223 server_port.setText( port )
2224 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2226 if not plist: return
2227 for p in protocol_letters:
2228 i = protocol_letters.index(p)
2229 j = server_protocol.model().index(i,0)
2230 if p not in pp.keys():
2231 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2233 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2237 host, port, protocol = server.split(':')
2238 change_server(host,protocol)
2240 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2241 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2243 if not wallet.config.is_modifiable('server'):
2244 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2247 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2248 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2249 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2250 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2253 proxy_mode = QComboBox()
2254 proxy_host = QLineEdit()
2255 proxy_host.setFixedWidth(200)
2256 proxy_port = QLineEdit()
2257 proxy_port.setFixedWidth(60)
2258 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2260 def check_for_disable(index = False):
2261 if proxy_mode.currentText() != 'NONE':
2262 proxy_host.setEnabled(True)
2263 proxy_port.setEnabled(True)
2265 proxy_host.setEnabled(False)
2266 proxy_port.setEnabled(False)
2269 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2271 if not wallet.config.is_modifiable('proxy'):
2272 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2274 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2275 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2276 proxy_host.setText(proxy_config.get("host"))
2277 proxy_port.setText(proxy_config.get("port"))
2279 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2280 grid.addWidget(proxy_mode, 2, 1)
2281 grid.addWidget(proxy_host, 2, 2)
2282 grid.addWidget(proxy_port, 2, 3)
2285 vbox.addLayout(ok_cancel_buttons(d))
2288 if not d.exec_(): return
2290 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2291 if proxy_mode.currentText() != 'NONE':
2292 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2296 wallet.config.set_key("proxy", proxy, True)
2297 wallet.config.set_key("server", server, True)
2298 interface.set_server(server, proxy)
2299 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2302 def closeEvent(self, event):
2304 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2305 self.save_column_widths()
2306 self.config.set_key("column-widths", self.column_widths, True)
2307 self.config.set_key("console-history",self.console.history[-50:])
2313 def __init__(self, wallet, config, app=None):
2314 self.wallet = wallet
2315 self.config = config
2317 self.app = QApplication(sys.argv)
2320 def restore_or_create(self):
2321 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2322 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2323 if r==2: return None
2324 return 'restore' if r==1 else 'create'
2326 def seed_dialog(self):
2327 return ElectrumWindow.seed_dialog( self.wallet )
2329 def network_dialog(self):
2330 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2333 def show_seed(self):
2334 ElectrumWindow.show_seed(self.wallet.seed)
2337 def password_dialog(self):
2338 if self.wallet.seed:
2339 ElectrumWindow.change_password_dialog(self.wallet)
2342 def restore_wallet(self):
2343 wallet = self.wallet
2344 # wait until we are connected, because the user might have selected another server
2345 if not wallet.interface.is_connected:
2346 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2347 waiting_dialog(waiting)
2349 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2350 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2352 wallet.set_up_to_date(False)
2353 wallet.interface.poke('synchronizer')
2354 waiting_dialog(waiting)
2355 if wallet.is_found():
2356 print_error( "Recovery successful" )
2358 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2365 w = ElectrumWindow(self.wallet, self.config)
2366 if url: w.set_url(url)