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):
350 if os.path.exists("plugins"):
351 fp, pathname, description = imp.find_module('plugins')
352 imp.load_module('electrum_plugins', fp, pathname, description)
353 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
354 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
356 import electrum_plugins
357 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
358 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
360 self.plugin_hooks = {}
361 for p in self.plugins:
365 print_msg("Error:cannot initialize plugin",p)
366 traceback.print_exc(file=sys.stdout)
368 def set_hook(self, name, callback):
369 h = self.plugin_hooks.get(name, [])
371 self.plugin_hooks[name] = h
373 def unset_hook(self, name, callback):
374 h = self.plugin_hooks.get(name,[])
375 if callback in h: h.remove(callback)
376 self.plugin_hooks[name] = h
378 def run_hook(self, name, *args):
379 args = (self,) + args
380 for cb in self.plugin_hooks.get(name,[]):
384 def set_label(self, name, text = None):
386 old_text = self.wallet.labels.get(name)
389 self.wallet.labels[name] = text
393 self.wallet.labels.pop(name)
395 self.run_hook('set_label', name, text, changed)
399 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
400 def getOpenFileName(self, title, filter = None):
401 directory = self.config.get('io_dir', os.path.expanduser('~'))
402 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
403 if fileName and directory != os.path.dirname(fileName):
404 self.config.set_key('io_dir', os.path.dirname(fileName), True)
407 def getSaveFileName(self, title, filename, filter = None):
408 directory = self.config.get('io_dir', os.path.expanduser('~'))
409 path = os.path.join( directory, filename )
410 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
411 if fileName and directory != os.path.dirname(fileName):
412 self.config.set_key('io_dir', os.path.dirname(fileName), True)
418 QMainWindow.close(self)
419 self.run_hook('close_main_window')
421 def connect_slots(self, sender):
422 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
423 self.previous_payto_e=''
425 def timer_actions(self):
426 self.run_hook('timer_actions')
428 if self.payto_e.hasFocus():
430 r = unicode( self.payto_e.text() )
431 if r != self.previous_payto_e:
432 self.previous_payto_e = r
434 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
436 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
440 s = r + ' <' + to_address + '>'
441 self.payto_e.setText(s)
445 def update_status(self):
446 if self.wallet.interface and self.wallet.interface.is_connected:
447 if not self.wallet.up_to_date:
448 text = _("Synchronizing...")
449 icon = QIcon(":icons/status_waiting.png")
451 c, u = self.wallet.get_balance()
452 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
453 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
454 text += self.create_quote_text(Decimal(c+u)/100000000)
455 icon = QIcon(":icons/status_connected.png")
457 text = _("Not connected")
458 icon = QIcon(":icons/status_disconnected.png")
460 self.status_text = text
461 self.statusBar().showMessage(text)
462 self.status_button.setIcon( icon )
464 def update_wallet(self):
466 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
467 self.update_history_tab()
468 self.update_receive_tab()
469 self.update_contacts_tab()
470 self.update_completions()
473 def create_quote_text(self, btc_balance):
474 quote_currency = self.config.get("currency", "None")
475 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
476 if quote_balance is None:
479 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
482 def create_history_tab(self):
483 self.history_list = l = MyTreeWidget(self)
485 for i,width in enumerate(self.column_widths['history']):
486 l.setColumnWidth(i, width)
487 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
488 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
489 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
491 l.setContextMenuPolicy(Qt.CustomContextMenu)
492 l.customContextMenuRequested.connect(self.create_history_menu)
496 def create_history_menu(self, position):
497 self.history_list.selectedIndexes()
498 item = self.history_list.currentItem()
500 tx_hash = str(item.data(0, Qt.UserRole).toString())
501 if not tx_hash: return
503 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
504 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
505 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
506 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
509 def show_tx_details(self, tx):
510 dialog = QDialog(self)
512 dialog.setWindowTitle(_("Transaction Details"))
514 dialog.setLayout(vbox)
515 dialog.setMinimumSize(600,300)
518 if tx_hash in self.wallet.transactions.keys():
519 is_mine, v, fee = self.wallet.get_tx_value(tx)
520 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
522 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
528 vbox.addWidget(QLabel("Transaction ID:"))
529 e = QLineEdit(tx_hash)
533 vbox.addWidget(QLabel("Date: %s"%time_str))
534 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
537 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
538 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
540 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
541 vbox.addWidget(QLabel("Transaction fee: unknown"))
543 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
545 vbox.addWidget( self.generate_transaction_information_widget(tx) )
547 ok_button = QPushButton(_("Close"))
548 ok_button.setDefault(True)
549 ok_button.clicked.connect(dialog.accept)
553 hbox.addWidget(ok_button)
557 def tx_label_clicked(self, item, column):
558 if column==2 and item.isSelected():
560 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
561 self.history_list.editItem( item, column )
562 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
565 def tx_label_changed(self, item, column):
569 tx_hash = str(item.data(0, Qt.UserRole).toString())
570 tx = self.wallet.transactions.get(tx_hash)
571 text = unicode( item.text(2) )
572 self.set_label(tx_hash, text)
574 item.setForeground(2, QBrush(QColor('black')))
576 text = self.wallet.get_default_label(tx_hash)
577 item.setText(2, text)
578 item.setForeground(2, QBrush(QColor('gray')))
582 def edit_label(self, is_recv):
583 l = self.receive_list if is_recv else self.contacts_list
584 item = l.currentItem()
585 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
586 l.editItem( item, 1 )
587 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
591 def address_label_clicked(self, item, column, l, column_addr, column_label):
592 if column == column_label and item.isSelected():
593 addr = unicode( item.text(column_addr) )
594 label = unicode( item.text(column_label) )
595 if label in self.wallet.aliases.keys():
597 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
598 l.editItem( item, column )
599 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
602 def address_label_changed(self, item, column, l, column_addr, column_label):
604 if column == column_label:
605 addr = unicode( item.text(column_addr) )
606 text = unicode( item.text(column_label) )
609 if text in self.wallet.aliases.keys():
610 print_error("Error: This is one of your aliases")
611 label = self.wallet.labels.get(addr,'')
612 item.setText(column_label, QString(label))
614 changed = self.set_label(addr, text)
616 self.update_history_tab()
617 self.update_completions()
619 self.current_item_changed(item)
621 self.run_hook('item_changed', item, column)
624 def current_item_changed(self, a):
625 self.run_hook('current_item_changed', a)
629 def update_history_tab(self):
631 self.history_list.clear()
632 for item in self.wallet.get_tx_history():
633 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
636 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
642 icon = QIcon(":icons/unconfirmed.png")
644 icon = QIcon(":icons/clock%d.png"%conf)
646 icon = QIcon(":icons/confirmed.png")
649 icon = QIcon(":icons/unconfirmed.png")
651 if value is not None:
652 v_str = format_satoshis(value, True, self.wallet.num_zeros)
656 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
659 label, is_default_label = self.wallet.get_label(tx_hash)
661 label = _('Pruned transaction outputs')
662 is_default_label = False
664 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
665 item.setFont(2, QFont(MONOSPACE_FONT))
666 item.setFont(3, QFont(MONOSPACE_FONT))
667 item.setFont(4, QFont(MONOSPACE_FONT))
669 item.setForeground(3, QBrush(QColor("#BC1E1E")))
671 item.setData(0, Qt.UserRole, tx_hash)
672 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
674 item.setForeground(2, QBrush(QColor('grey')))
676 item.setIcon(0, icon)
677 self.history_list.insertTopLevelItem(0,item)
680 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
683 def create_send_tab(self):
688 grid.setColumnMinimumWidth(3,300)
689 grid.setColumnStretch(5,1)
691 self.payto_e = QLineEdit()
692 grid.addWidget(QLabel(_('Pay to')), 1, 0)
693 grid.addWidget(self.payto_e, 1, 1, 1, 3)
695 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)
697 completer = QCompleter()
698 completer.setCaseSensitivity(False)
699 self.payto_e.setCompleter(completer)
700 completer.setModel(self.completions)
702 self.message_e = QLineEdit()
703 grid.addWidget(QLabel(_('Description')), 2, 0)
704 grid.addWidget(self.message_e, 2, 1, 1, 3)
705 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)
707 self.amount_e = QLineEdit()
708 grid.addWidget(QLabel(_('Amount')), 3, 0)
709 grid.addWidget(self.amount_e, 3, 1, 1, 2)
710 grid.addWidget(HelpButton(
711 _('Amount to be sent.') + '\n\n' \
712 + _('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)
714 self.fee_e = QLineEdit()
715 grid.addWidget(QLabel(_('Fee')), 4, 0)
716 grid.addWidget(self.fee_e, 4, 1, 1, 2)
717 grid.addWidget(HelpButton(
718 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
719 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
720 + _('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)
723 b = EnterButton(_("Send"), self.do_send)
725 b = EnterButton(_("Create unsigned transaction"), self.do_send)
726 grid.addWidget(b, 6, 1)
728 b = EnterButton(_("Clear"),self.do_clear)
729 grid.addWidget(b, 6, 2)
731 self.payto_sig = QLabel('')
732 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
734 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
735 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
744 def entry_changed( is_fee ):
745 self.funds_error = False
746 amount = numbify(self.amount_e)
747 fee = numbify(self.fee_e)
748 if not is_fee: fee = None
751 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
753 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
756 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
757 text = self.status_text
760 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
761 self.funds_error = True
762 text = _( "Not enough funds" )
764 self.statusBar().showMessage(text)
765 self.amount_e.setPalette(palette)
766 self.fee_e.setPalette(palette)
768 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
769 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
771 self.run_hook('create_send_tab', grid)
775 def update_completions(self):
777 for addr,label in self.wallet.labels.items():
778 if addr in self.wallet.addressbook:
779 l.append( label + ' <' + addr + '>')
780 l = l + self.wallet.aliases.keys()
782 self.completions.setStringList(l)
786 return lambda s, *args: s.do_protect(func, args)
790 def do_send(self, password):
792 label = unicode( self.message_e.text() )
793 r = unicode( self.payto_e.text() )
797 m1 = re.match(ALIAS_REGEXP, r)
798 # label or alias, with address in brackets
799 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
802 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
806 to_address = m2.group(2)
810 if not is_valid(to_address):
811 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
815 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
817 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
820 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
822 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
826 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
827 except BaseException, e:
828 self.show_message(str(e))
831 self.run_hook('send_tx', tx)
834 self.set_label(tx.hash(), label)
837 h = self.wallet.send_tx(tx)
838 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
839 status, msg = self.wallet.receive_tx( h )
841 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
843 self.update_contacts_tab()
845 QMessageBox.warning(self, _('Error'), msg, _('OK'))
847 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
849 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
850 with open(fileName,'w') as f:
851 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
852 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
854 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
859 def set_url(self, url):
860 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
861 self.tabs.setCurrentIndex(1)
862 label = self.wallet.labels.get(payto)
863 m_addr = label + ' <'+ payto+'>' if label else payto
864 self.payto_e.setText(m_addr)
866 self.message_e.setText(message)
867 self.amount_e.setText(amount)
869 self.set_frozen(self.payto_e,True)
870 self.set_frozen(self.amount_e,True)
871 self.set_frozen(self.message_e,True)
872 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
874 self.payto_sig.setVisible(False)
877 self.payto_sig.setVisible(False)
878 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
880 self.set_frozen(e,False)
882 def set_frozen(self,entry,frozen):
884 entry.setReadOnly(True)
885 entry.setFrame(False)
887 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
888 entry.setPalette(palette)
890 entry.setReadOnly(False)
893 palette.setColor(entry.backgroundRole(), QColor('white'))
894 entry.setPalette(palette)
897 def toggle_freeze(self,addr):
899 if addr in self.wallet.frozen_addresses:
900 self.wallet.unfreeze(addr)
902 self.wallet.freeze(addr)
903 self.update_receive_tab()
905 def toggle_priority(self,addr):
907 if addr in self.wallet.prioritized_addresses:
908 self.wallet.unprioritize(addr)
910 self.wallet.prioritize(addr)
911 self.update_receive_tab()
914 def create_list_tab(self, headers):
915 "generic tab creation method"
916 l = MyTreeWidget(self)
917 l.setColumnCount( len(headers) )
918 l.setHeaderLabels( headers )
928 vbox.addWidget(buttons)
933 buttons.setLayout(hbox)
938 def create_receive_tab(self):
939 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
940 l.setContextMenuPolicy(Qt.CustomContextMenu)
941 l.customContextMenuRequested.connect(self.create_receive_menu)
942 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
943 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
944 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
945 self.receive_list = l
946 self.receive_buttons_hbox = hbox
951 def receive_tab_set_mode(self, i):
952 self.save_column_widths()
953 self.expert_mode = (i == 1)
954 self.config.set_key('classic_expert_mode', self.expert_mode, True)
956 self.update_receive_tab()
959 def save_column_widths(self):
960 if not self.expert_mode:
961 widths = [ self.receive_list.columnWidth(0) ]
964 for i in range(self.receive_list.columnCount() -1):
965 widths.append(self.receive_list.columnWidth(i))
966 self.column_widths["receive"][self.expert_mode] = widths
968 self.column_widths["history"] = []
969 for i in range(self.history_list.columnCount() - 1):
970 self.column_widths["history"].append(self.history_list.columnWidth(i))
972 self.column_widths["contacts"] = []
973 for i in range(self.contacts_list.columnCount() - 1):
974 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
977 def create_contacts_tab(self):
978 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
979 l.setContextMenuPolicy(Qt.CustomContextMenu)
980 l.customContextMenuRequested.connect(self.create_contact_menu)
981 for i,width in enumerate(self.column_widths['contacts']):
982 l.setColumnWidth(i, width)
984 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
985 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
986 self.contacts_list = l
987 self.contacts_buttons_hbox = hbox
988 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
993 def delete_imported_key(self, addr):
994 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
995 self.wallet.imported_keys.pop(addr)
996 self.update_receive_tab()
997 self.update_history_tab()
1001 def create_receive_menu(self, position):
1002 # fixme: this function apparently has a side effect.
1003 # if it is not called the menu pops up several times
1004 #self.receive_list.selectedIndexes()
1006 item = self.receive_list.itemAt(position)
1008 addr = unicode(item.text(0))
1009 if not is_valid(addr):
1010 item.setExpanded(not item.isExpanded())
1013 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1014 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1015 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1016 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1017 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1018 if addr in self.wallet.imported_keys:
1019 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1021 if self.expert_mode:
1022 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1023 menu.addAction(t, lambda: self.toggle_freeze(addr))
1024 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1025 menu.addAction(t, lambda: self.toggle_priority(addr))
1027 self.run_hook('receive_menu', menu)
1028 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1031 def payto(self, x, is_alias):
1038 label = self.wallet.labels.get(addr)
1039 m_addr = label + ' <' + addr + '>' if label else addr
1040 self.tabs.setCurrentIndex(1)
1041 self.payto_e.setText(m_addr)
1042 self.amount_e.setFocus()
1044 def delete_contact(self, x, is_alias):
1045 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1046 if not is_alias and x in self.wallet.addressbook:
1047 self.wallet.addressbook.remove(x)
1048 self.set_label(x, None)
1049 elif is_alias and x in self.wallet.aliases:
1050 self.wallet.aliases.pop(x)
1051 self.update_history_tab()
1052 self.update_contacts_tab()
1053 self.update_completions()
1055 def create_contact_menu(self, position):
1056 # fixme: this function apparently has a side effect.
1057 # if it is not called the menu pops up several times
1058 #self.contacts_list.selectedIndexes()
1060 item = self.contacts_list.itemAt(position)
1062 addr = unicode(item.text(0))
1063 label = unicode(item.text(1))
1064 is_alias = label in self.wallet.aliases.keys()
1065 x = label if is_alias else addr
1067 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1068 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1069 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1071 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1073 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1074 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1075 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1078 def update_receive_item(self, item):
1079 item.setFont(0, QFont(MONOSPACE_FONT))
1080 address = str(item.data(0,0).toString())
1081 label = self.wallet.labels.get(address,'')
1082 item.setData(1,0,label)
1084 self.run_hook('update_receive_item', address, item)
1086 c, u = self.wallet.get_addr_balance(address)
1087 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1088 item.setData(2,0,balance)
1090 if self.expert_mode:
1091 if address in self.wallet.frozen_addresses:
1092 item.setBackgroundColor(0, QColor('lightblue'))
1093 elif address in self.wallet.prioritized_addresses:
1094 item.setBackgroundColor(0, QColor('lightgreen'))
1097 def update_receive_tab(self):
1098 l = self.receive_list
1101 l.setColumnHidden(2, not self.expert_mode)
1102 l.setColumnHidden(3, not self.expert_mode)
1103 if not self.expert_mode:
1104 width = self.column_widths['receive'][0][0]
1105 l.setColumnWidth(0, width)
1107 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1108 l.setColumnWidth(i, width)
1111 for k, account in self.wallet.accounts.items():
1112 name = account.get('name',str(k))
1113 c,u = self.wallet.get_account_balance(k)
1114 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1115 l.addTopLevelItem(account_item)
1116 account_item.setExpanded(True)
1119 for is_change in [0,1]:
1120 name = "Receiving" if not is_change else "Change"
1121 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1122 account_item.addChild(seq_item)
1123 if not is_change: seq_item.setExpanded(True)
1127 for address in account[is_change]:
1128 h = self.wallet.history.get(address,[])
1133 if gap > self.wallet.gap_limit:
1138 num_tx = '*' if h == ['*'] else "%d"%len(h)
1139 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1140 self.update_receive_item(item)
1142 item.setBackgroundColor(1, QColor('red'))
1143 seq_item.addChild(item)
1145 if self.wallet.imported_keys:
1146 c,u = self.wallet.get_imported_balance()
1147 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1148 l.addTopLevelItem(account_item)
1149 account_item.setExpanded(True)
1150 for address in self.wallet.imported_keys.keys():
1151 item = QTreeWidgetItem( [ address, '', '', ''] )
1152 self.update_receive_item(item)
1153 account_item.addChild(item)
1156 # we use column 1 because column 0 may be hidden
1157 l.setCurrentItem(l.topLevelItem(0),1)
1159 def show_contact_details(self, m):
1160 a = self.wallet.aliases.get(m)
1162 if a[0] in self.wallet.authorities.keys():
1163 s = self.wallet.authorities.get(a[0])
1166 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1167 QMessageBox.information(self, 'Alias', msg, 'OK')
1169 def update_contacts_tab(self):
1171 l = self.contacts_list
1175 for alias, v in self.wallet.aliases.items():
1177 alias_targets.append(target)
1178 item = QTreeWidgetItem( [ target, alias, '-'] )
1179 item.setBackgroundColor(0, QColor('lightgray'))
1180 l.addTopLevelItem(item)
1182 for address in self.wallet.addressbook:
1183 if address in alias_targets: continue
1184 label = self.wallet.labels.get(address,'')
1186 for tx in self.wallet.transactions.values():
1187 if address in map(lambda x: x[0], tx.outputs): n += 1
1189 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1190 item.setFont(0, QFont(MONOSPACE_FONT))
1191 l.addTopLevelItem(item)
1193 l.setCurrentItem(l.topLevelItem(0))
1196 def create_console_tab(self):
1197 from qt_console import Console
1198 self.console = console = Console()
1199 self.console.history = self.config.get("console-history",[])
1200 self.console.history_index = len(self.console.history)
1202 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1203 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1205 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1207 def mkfunc(f, method):
1208 return lambda *args: apply( f, (method, args, self.password_dialog ))
1210 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1211 methods[m] = mkfunc(c._run, m)
1213 console.updateNamespace(methods)
1217 def create_status_bar(self):
1218 self.status_text = ""
1220 sb.setFixedHeight(35)
1221 qtVersion = qVersion()
1223 update_notification = UpdateLabel(self.config)
1224 if(update_notification.new_version):
1225 sb.addPermanentWidget(update_notification)
1227 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1228 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1229 if self.wallet.seed:
1230 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1231 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1232 if self.wallet.seed:
1233 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1234 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1235 sb.addPermanentWidget( self.status_button )
1237 self.setStatusBar(sb)
1241 self.config.set_key('gui', 'lite', True)
1244 self.lite.mini.show()
1246 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1247 self.lite.main(None)
1249 def new_contact_dialog(self):
1250 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1251 address = unicode(text)
1253 if is_valid(address):
1254 self.wallet.addressbook.append(address)
1256 self.update_contacts_tab()
1257 self.update_history_tab()
1258 self.update_completions()
1260 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1262 def show_master_public_key(self):
1263 dialog = QDialog(self)
1265 dialog.setWindowTitle(_("Master Public Key"))
1267 main_text = QTextEdit()
1268 main_text.setText(self.wallet.get_master_public_key())
1269 main_text.setReadOnly(True)
1270 main_text.setMaximumHeight(170)
1271 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1273 ok_button = QPushButton(_("OK"))
1274 ok_button.setDefault(True)
1275 ok_button.clicked.connect(dialog.accept)
1277 main_layout = QGridLayout()
1278 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1280 main_layout.addWidget(main_text, 1, 0)
1281 main_layout.addWidget(qrw, 1, 1 )
1283 vbox = QVBoxLayout()
1284 vbox.addLayout(main_layout)
1285 hbox = QHBoxLayout()
1287 hbox.addWidget(ok_button)
1288 vbox.addLayout(hbox)
1290 dialog.setLayout(vbox)
1295 def show_seed_dialog(self, password):
1296 if not self.wallet.seed:
1297 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1300 seed = self.wallet.decode_seed(password)
1302 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1304 self.show_seed(seed, self)
1308 def show_seed(self, seed, parent=None):
1309 dialog = QDialog(parent)
1311 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1313 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1315 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1317 seed_text = QTextEdit(brainwallet)
1318 seed_text.setReadOnly(True)
1319 seed_text.setMaximumHeight(130)
1321 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1322 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1323 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1324 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1325 label2 = QLabel(msg2)
1326 label2.setWordWrap(True)
1329 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1330 logo.setMaximumWidth(60)
1332 qrw = QRCodeWidget(seed)
1334 ok_button = QPushButton(_("OK"))
1335 ok_button.setDefault(True)
1336 ok_button.clicked.connect(dialog.accept)
1338 grid = QGridLayout()
1339 #main_layout.addWidget(logo, 0, 0)
1341 grid.addWidget(logo, 0, 0)
1342 grid.addWidget(label1, 0, 1)
1344 grid.addWidget(seed_text, 1, 0, 1, 2)
1346 grid.addWidget(qrw, 0, 2, 2, 1)
1348 vbox = QVBoxLayout()
1349 vbox.addLayout(grid)
1350 vbox.addWidget(label2)
1352 hbox = QHBoxLayout()
1354 hbox.addWidget(ok_button)
1355 vbox.addLayout(hbox)
1357 dialog.setLayout(vbox)
1360 def show_qrcode(self, data, title = "QR code"):
1364 d.setWindowTitle(title)
1365 d.setMinimumSize(270, 300)
1366 vbox = QVBoxLayout()
1367 qrw = QRCodeWidget(data)
1368 vbox.addWidget(qrw, 1)
1369 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1370 hbox = QHBoxLayout()
1374 filename = "qrcode.bmp"
1375 bmp.save_qrcode(qrw.qr, filename)
1376 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1378 b = QPushButton(_("Save"))
1380 b.clicked.connect(print_qr)
1382 b = QPushButton(_("Close"))
1384 b.clicked.connect(d.accept)
1387 vbox.addLayout(hbox)
1392 def do_protect(self, func, args):
1393 if self.wallet.use_encryption:
1394 password = self.password_dialog()
1400 if args != (False,):
1401 args = (self,) + args + (password,)
1403 args = (self,password)
1408 def show_private_key(self, address, password):
1409 if not address: return
1411 pk = self.wallet.get_private_key(address, password)
1412 except BaseException, e:
1413 self.show_message(str(e))
1415 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1419 def do_sign(self, address, message, signature, password):
1421 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1422 signature.setText(sig)
1423 except BaseException, e:
1424 self.show_message(str(e))
1426 def sign_message(self, address):
1427 if not address: return
1430 d.setWindowTitle(_('Sign Message'))
1431 d.setMinimumSize(410, 290)
1433 tab_widget = QTabWidget()
1435 layout = QGridLayout(tab)
1437 sign_address = QLineEdit()
1439 sign_address.setText(address)
1440 layout.addWidget(QLabel(_('Address')), 1, 0)
1441 layout.addWidget(sign_address, 1, 1)
1443 sign_message = QTextEdit()
1444 layout.addWidget(QLabel(_('Message')), 2, 0)
1445 layout.addWidget(sign_message, 2, 1)
1446 layout.setRowStretch(2,3)
1448 sign_signature = QTextEdit()
1449 layout.addWidget(QLabel(_('Signature')), 3, 0)
1450 layout.addWidget(sign_signature, 3, 1)
1451 layout.setRowStretch(3,1)
1454 hbox = QHBoxLayout()
1455 b = QPushButton(_("Sign"))
1457 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1458 b = QPushButton(_("Close"))
1459 b.clicked.connect(d.accept)
1461 layout.addLayout(hbox, 4, 1)
1462 tab_widget.addTab(tab, _("Sign"))
1466 layout = QGridLayout(tab)
1468 verify_address = QLineEdit()
1469 layout.addWidget(QLabel(_('Address')), 1, 0)
1470 layout.addWidget(verify_address, 1, 1)
1472 verify_message = QTextEdit()
1473 layout.addWidget(QLabel(_('Message')), 2, 0)
1474 layout.addWidget(verify_message, 2, 1)
1475 layout.setRowStretch(2,3)
1477 verify_signature = QTextEdit()
1478 layout.addWidget(QLabel(_('Signature')), 3, 0)
1479 layout.addWidget(verify_signature, 3, 1)
1480 layout.setRowStretch(3,1)
1484 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1485 self.show_message(_("Signature verified"))
1486 except BaseException, e:
1487 self.show_message(str(e))
1490 hbox = QHBoxLayout()
1491 b = QPushButton(_("Verify"))
1492 b.clicked.connect(do_verify)
1494 b = QPushButton(_("Close"))
1495 b.clicked.connect(d.accept)
1497 layout.addLayout(hbox, 4, 1)
1498 tab_widget.addTab(tab, _("Verify"))
1500 vbox = QVBoxLayout()
1501 vbox.addWidget(tab_widget)
1508 def question(self, msg):
1509 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1511 def show_message(self, msg):
1512 QMessageBox.information(self, _('Message'), msg, _('OK'))
1514 def password_dialog(self ):
1521 vbox = QVBoxLayout()
1522 msg = _('Please enter your password')
1523 vbox.addWidget(QLabel(msg))
1525 grid = QGridLayout()
1527 grid.addWidget(QLabel(_('Password')), 1, 0)
1528 grid.addWidget(pw, 1, 1)
1529 vbox.addLayout(grid)
1531 vbox.addLayout(ok_cancel_buttons(d))
1534 if not d.exec_(): return
1535 return unicode(pw.text())
1542 def change_password_dialog( wallet, parent=None ):
1545 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1553 new_pw = QLineEdit()
1554 new_pw.setEchoMode(2)
1555 conf_pw = QLineEdit()
1556 conf_pw.setEchoMode(2)
1558 vbox = QVBoxLayout()
1560 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1561 +_('To disable wallet encryption, enter an empty new password.')) \
1562 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1564 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1565 +_("Leave these fields empty if you want to disable encryption.")
1566 vbox.addWidget(QLabel(msg))
1568 grid = QGridLayout()
1571 if wallet.use_encryption:
1572 grid.addWidget(QLabel(_('Password')), 1, 0)
1573 grid.addWidget(pw, 1, 1)
1575 grid.addWidget(QLabel(_('New Password')), 2, 0)
1576 grid.addWidget(new_pw, 2, 1)
1578 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1579 grid.addWidget(conf_pw, 3, 1)
1580 vbox.addLayout(grid)
1582 vbox.addLayout(ok_cancel_buttons(d))
1585 if not d.exec_(): return
1587 password = unicode(pw.text()) if wallet.use_encryption else None
1588 new_password = unicode(new_pw.text())
1589 new_password2 = unicode(conf_pw.text())
1592 seed = wallet.decode_seed(password)
1594 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1597 if new_password != new_password2:
1598 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1599 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1601 wallet.update_password(seed, password, new_password)
1604 def seed_dialog(wallet, parent=None):
1608 vbox = QVBoxLayout()
1609 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1610 vbox.addWidget(QLabel(msg))
1612 grid = QGridLayout()
1615 seed_e = QLineEdit()
1616 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1617 grid.addWidget(seed_e, 1, 1)
1618 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1622 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1623 grid.addWidget(gap_e, 2, 1)
1624 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1625 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1626 vbox.addLayout(grid)
1628 vbox.addLayout(ok_cancel_buttons(d))
1631 if not d.exec_(): return
1634 gap = int(unicode(gap_e.text()))
1636 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1640 seed = str(seed_e.text())
1643 print_error("Warning: Not hex, trying decode")
1645 seed = mnemonic.mn_decode( seed.split(' ') )
1647 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1651 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1656 def generate_transaction_information_widget(self, tx):
1657 tabs = QTabWidget(self)
1660 grid_ui = QGridLayout(tab1)
1661 grid_ui.setColumnStretch(0,1)
1662 tabs.addTab(tab1, _('Outputs') )
1664 tree_widget = MyTreeWidget(self)
1665 tree_widget.setColumnCount(2)
1666 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1667 tree_widget.setColumnWidth(0, 300)
1668 tree_widget.setColumnWidth(1, 50)
1670 for address, value in tx.outputs:
1671 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1672 tree_widget.addTopLevelItem(item)
1674 tree_widget.setMaximumHeight(100)
1676 grid_ui.addWidget(tree_widget)
1679 grid_ui = QGridLayout(tab2)
1680 grid_ui.setColumnStretch(0,1)
1681 tabs.addTab(tab2, _('Inputs') )
1683 tree_widget = MyTreeWidget(self)
1684 tree_widget.setColumnCount(2)
1685 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1687 for input_line in tx.inputs:
1688 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1689 tree_widget.addTopLevelItem(item)
1691 tree_widget.setMaximumHeight(100)
1693 grid_ui.addWidget(tree_widget)
1697 def tx_dict_from_text(self, txt):
1699 tx_dict = json.loads(str(txt))
1700 assert "hex" in tx_dict.keys()
1701 assert "complete" in tx_dict.keys()
1702 if not tx_dict["complete"]:
1703 assert "input_info" in tx_dict.keys()
1705 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1710 def read_tx_from_file(self):
1711 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1715 with open(fileName, "r") as f:
1716 file_content = f.read()
1717 except (ValueError, IOError, os.error), reason:
1718 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1720 return self.tx_dict_from_text(file_content)
1724 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1726 self.wallet.signrawtransaction(tx, input_info, [], password)
1728 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1730 with open(fileName, "w+") as f:
1731 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1732 self.show_message(_("Transaction saved successfully"))
1735 except BaseException, e:
1736 self.show_message(str(e))
1739 def send_raw_transaction(self, raw_tx, dialog = ""):
1740 result, result_message = self.wallet.sendtx( raw_tx )
1742 self.show_message("Transaction successfully sent: %s" % (result_message))
1746 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1748 def do_process_from_text(self):
1749 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1752 tx_dict = self.tx_dict_from_text(text)
1754 self.create_process_transaction_window(tx_dict)
1756 def do_process_from_file(self):
1757 tx_dict = self.read_tx_from_file()
1759 self.create_process_transaction_window(tx_dict)
1761 def create_process_transaction_window(self, tx_dict):
1762 tx = Transaction(tx_dict["hex"])
1764 dialog = QDialog(self)
1765 dialog.setMinimumWidth(500)
1766 dialog.setWindowTitle(_('Process raw transaction'))
1772 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1773 l.addWidget(QLabel(_("Actions")), 4,0)
1775 if tx_dict["complete"] == False:
1776 l.addWidget(QLabel(_("Unsigned")), 3,1)
1777 if self.wallet.seed :
1778 b = QPushButton("Sign transaction")
1779 input_info = json.loads(tx_dict["input_info"])
1780 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1781 l.addWidget(b, 4, 1)
1783 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1785 l.addWidget(QLabel(_("Signed")), 3,1)
1786 b = QPushButton("Broadcast transaction")
1787 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1790 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1791 cancelButton = QPushButton(_("Cancel"))
1792 cancelButton.clicked.connect(lambda: dialog.done(0))
1793 l.addWidget(cancelButton, 4,2)
1799 def do_export_privkeys(self, password):
1800 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.")))
1803 select_export = _('Select file to export your private keys to')
1804 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1806 with open(fileName, "w+") as csvfile:
1807 transaction = csv.writer(csvfile)
1808 transaction.writerow(["address", "private_key"])
1811 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1812 transaction.writerow(["%34s"%addr,pk])
1814 self.show_message(_("Private keys exported."))
1816 except (IOError, os.error), reason:
1817 export_error_label = _("Electrum was unable to produce a private key-export.")
1818 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1820 except BaseException, e:
1821 self.show_message(str(e))
1825 def do_import_labels(self):
1826 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1827 if not labelsFile: return
1829 f = open(labelsFile, 'r')
1832 for key, value in json.loads(data).items():
1833 self.wallet.labels[key] = value
1835 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1836 except (IOError, os.error), reason:
1837 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1840 def do_export_labels(self):
1841 labels = self.wallet.labels
1843 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1845 with open(fileName, 'w+') as f:
1846 json.dump(labels, f)
1847 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1848 except (IOError, os.error), reason:
1849 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1852 def do_export_history(self):
1853 from gui_lite import csv_transaction
1854 csv_transaction(self.wallet)
1858 def do_import_privkey(self, password):
1859 if not self.wallet.imported_keys:
1860 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1861 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1862 + _('Are you sure you understand what you are doing?'), 3, 4)
1865 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1868 text = str(text).split()
1873 addr = self.wallet.import_key(key, password)
1874 except BaseException as e:
1880 addrlist.append(addr)
1882 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1884 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1885 self.update_receive_tab()
1886 self.update_history_tab()
1889 def settings_dialog(self):
1891 d.setWindowTitle(_('Electrum Settings'))
1893 vbox = QVBoxLayout()
1895 tabs = QTabWidget(self)
1896 vbox.addWidget(tabs)
1899 grid_ui = QGridLayout(tab1)
1900 grid_ui.setColumnStretch(0,1)
1901 tabs.addTab(tab1, _('Display') )
1903 nz_label = QLabel(_('Display zeros'))
1904 grid_ui.addWidget(nz_label, 0, 0)
1906 nz_e.setText("%d"% self.wallet.num_zeros)
1907 grid_ui.addWidget(nz_e, 0, 1)
1908 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1909 grid_ui.addWidget(HelpButton(msg), 0, 2)
1910 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1911 if not self.config.is_modifiable('num_zeros'):
1912 for w in [nz_e, nz_label]: w.setEnabled(False)
1914 lang_label=QLabel(_('Language') + ':')
1915 grid_ui.addWidget(lang_label, 1, 0)
1916 lang_combo = QComboBox()
1917 from i18n import languages
1918 lang_combo.addItems(languages.values())
1920 index = languages.keys().index(self.config.get("language",''))
1923 lang_combo.setCurrentIndex(index)
1924 grid_ui.addWidget(lang_combo, 1, 1)
1925 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1926 if not self.config.is_modifiable('language'):
1927 for w in [lang_combo, lang_label]: w.setEnabled(False)
1929 currencies = self.exchanger.get_currencies()
1930 currencies.insert(0, "None")
1932 cur_label=QLabel(_('Currency') + ':')
1933 grid_ui.addWidget(cur_label , 2, 0)
1934 cur_combo = QComboBox()
1935 cur_combo.addItems(currencies)
1937 index = currencies.index(self.config.get('currency', "None"))
1940 cur_combo.setCurrentIndex(index)
1941 grid_ui.addWidget(cur_combo, 2, 1)
1942 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1944 view_label=QLabel(_('Receive Tab') + ':')
1945 grid_ui.addWidget(view_label , 3, 0)
1946 view_combo = QComboBox()
1947 view_combo.addItems([_('Simple'), _('Advanced')])
1948 view_combo.setCurrentIndex(self.expert_mode)
1949 grid_ui.addWidget(view_combo, 3, 1)
1950 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1951 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1952 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1954 grid_ui.addWidget(HelpButton(hh), 3, 2)
1955 grid_ui.setRowStretch(4,1)
1959 grid_wallet = QGridLayout(tab2)
1960 grid_wallet.setColumnStretch(0,1)
1961 tabs.addTab(tab2, _('Wallet') )
1963 fee_label = QLabel(_('Transaction fee'))
1964 grid_wallet.addWidget(fee_label, 0, 0)
1966 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1967 grid_wallet.addWidget(fee_e, 0, 2)
1968 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1969 + _('Recommended value') + ': 0.001'
1970 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1971 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1972 if not self.config.is_modifiable('fee'):
1973 for w in [fee_e, fee_label]: w.setEnabled(False)
1975 usechange_label = QLabel(_('Use change addresses'))
1976 grid_wallet.addWidget(usechange_label, 1, 0)
1977 usechange_combo = QComboBox()
1978 usechange_combo.addItems([_('Yes'), _('No')])
1979 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1980 grid_wallet.addWidget(usechange_combo, 1, 2)
1981 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1982 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1984 gap_label = QLabel(_('Gap limit'))
1985 grid_wallet.addWidget(gap_label, 2, 0)
1987 gap_e.setText("%d"% self.wallet.gap_limit)
1988 grid_wallet.addWidget(gap_e, 2, 2)
1989 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1990 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1991 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1992 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1993 + _('Warning') + ': ' \
1994 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1995 + _('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'
1996 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1997 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1998 if not self.config.is_modifiable('gap_limit'):
1999 for w in [gap_e, gap_label]: w.setEnabled(False)
2001 grid_wallet.setRowStretch(3,1)
2006 grid_io = QGridLayout(tab3)
2007 grid_io.setColumnStretch(0,1)
2008 tabs.addTab(tab3, _('Import/Export') )
2010 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2011 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2012 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2013 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2015 grid_io.addWidget(QLabel(_('History')), 2, 0)
2016 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2017 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2019 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2021 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2022 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2023 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2025 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2026 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2027 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2028 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2029 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2032 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2033 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2034 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2035 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2037 grid_io.setRowStretch(5,1)
2042 tab5 = QScrollArea()
2043 grid_plugins = QGridLayout(tab5)
2044 grid_plugins.setColumnStretch(0,1)
2045 tabs.addTab(tab5, _('Plugins') )
2046 def mk_toggle(cb, p):
2047 return lambda: cb.setChecked(p.toggle(self))
2048 for i, p in enumerate(self.plugins):
2050 name, description = p.get_info()
2051 cb = QCheckBox(name)
2052 cb.setDisabled(not p.is_available())
2053 cb.setChecked(p.is_enabled())
2054 cb.clicked.connect(mk_toggle(cb,p))
2055 grid_plugins.addWidget(cb, i, 0)
2056 grid_plugins.addWidget(HelpButton(description), i, 1)
2058 print_msg("Error: cannot display plugin", p)
2059 traceback.print_exc(file=sys.stdout)
2060 grid_plugins.setRowStretch(i+1,1)
2062 vbox.addLayout(ok_cancel_buttons(d))
2066 if not d.exec_(): return
2068 fee = unicode(fee_e.text())
2070 fee = int( 100000000 * Decimal(fee) )
2072 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2075 if self.wallet.fee != fee:
2076 self.wallet.fee = fee
2079 nz = unicode(nz_e.text())
2084 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2087 if self.wallet.num_zeros != nz:
2088 self.wallet.num_zeros = nz
2089 self.config.set_key('num_zeros', nz, True)
2090 self.update_history_tab()
2091 self.update_receive_tab()
2093 usechange_result = usechange_combo.currentIndex() == 0
2094 if self.wallet.use_change != usechange_result:
2095 self.wallet.use_change = usechange_result
2096 self.config.set_key('use_change', self.wallet.use_change, True)
2099 n = int(gap_e.text())
2101 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2104 if self.wallet.gap_limit != n:
2105 r = self.wallet.change_gap_limit(n)
2107 self.update_receive_tab()
2108 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2110 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2112 need_restart = False
2114 lang_request = languages.keys()[lang_combo.currentIndex()]
2115 if lang_request != self.config.get('language'):
2116 self.config.set_key("language", lang_request, True)
2119 cur_request = str(currencies[cur_combo.currentIndex()])
2120 if cur_request != self.config.get('currency', "None"):
2121 self.config.set_key('currency', cur_request, True)
2122 self.update_wallet()
2125 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2127 self.receive_tab_set_mode(view_combo.currentIndex())
2131 def network_dialog(wallet, parent=None):
2132 interface = wallet.interface
2134 if interface.is_connected:
2135 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2137 status = _("Not connected")
2138 server = interface.server
2141 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2142 server = interface.server
2144 plist, servers_list = interface.get_servers_list()
2148 d.setWindowTitle(_('Server'))
2149 d.setMinimumSize(375, 20)
2151 vbox = QVBoxLayout()
2154 hbox = QHBoxLayout()
2156 l.setPixmap(QPixmap(":icons/network.png"))
2159 hbox.addWidget(QLabel(status))
2161 vbox.addLayout(hbox)
2165 grid = QGridLayout()
2167 vbox.addLayout(grid)
2170 server_protocol = QComboBox()
2171 server_host = QLineEdit()
2172 server_host.setFixedWidth(200)
2173 server_port = QLineEdit()
2174 server_port.setFixedWidth(60)
2176 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2177 protocol_letters = 'thsg'
2178 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2179 server_protocol.addItems(protocol_names)
2181 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2182 grid.addWidget(server_protocol, 0, 1)
2183 grid.addWidget(server_host, 0, 2)
2184 grid.addWidget(server_port, 0, 3)
2186 def change_protocol(p):
2187 protocol = protocol_letters[p]
2188 host = unicode(server_host.text())
2189 pp = plist.get(host,DEFAULT_PORTS)
2190 if protocol not in pp.keys():
2191 protocol = pp.keys()[0]
2193 server_host.setText( host )
2194 server_port.setText( port )
2196 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2198 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2199 servers_list_widget = QTreeWidget(parent)
2200 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2201 servers_list_widget.setMaximumHeight(150)
2202 servers_list_widget.setColumnWidth(0, 240)
2203 for _host in servers_list.keys():
2204 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2205 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2207 def change_server(host, protocol=None):
2208 pp = plist.get(host,DEFAULT_PORTS)
2210 port = pp.get(protocol)
2211 if not port: protocol = None
2214 if 't' in pp.keys():
2216 port = pp.get(protocol)
2218 protocol = pp.keys()[0]
2219 port = pp.get(protocol)
2221 server_host.setText( host )
2222 server_port.setText( port )
2223 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2225 if not plist: return
2226 for p in protocol_letters:
2227 i = protocol_letters.index(p)
2228 j = server_protocol.model().index(i,0)
2229 if p not in pp.keys():
2230 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2232 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2236 host, port, protocol = server.split(':')
2237 change_server(host,protocol)
2239 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2240 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2242 if not wallet.config.is_modifiable('server'):
2243 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2246 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2247 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2248 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2249 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2252 proxy_mode = QComboBox()
2253 proxy_host = QLineEdit()
2254 proxy_host.setFixedWidth(200)
2255 proxy_port = QLineEdit()
2256 proxy_port.setFixedWidth(60)
2257 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2259 def check_for_disable(index = False):
2260 if proxy_mode.currentText() != 'NONE':
2261 proxy_host.setEnabled(True)
2262 proxy_port.setEnabled(True)
2264 proxy_host.setEnabled(False)
2265 proxy_port.setEnabled(False)
2268 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2270 if not wallet.config.is_modifiable('proxy'):
2271 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2273 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2274 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2275 proxy_host.setText(proxy_config.get("host"))
2276 proxy_port.setText(proxy_config.get("port"))
2278 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2279 grid.addWidget(proxy_mode, 2, 1)
2280 grid.addWidget(proxy_host, 2, 2)
2281 grid.addWidget(proxy_port, 2, 3)
2284 vbox.addLayout(ok_cancel_buttons(d))
2287 if not d.exec_(): return
2289 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2290 if proxy_mode.currentText() != 'NONE':
2291 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2295 wallet.config.set_key("proxy", proxy, True)
2296 wallet.config.set_key("server", server, True)
2297 interface.set_server(server, proxy)
2298 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2301 def closeEvent(self, event):
2303 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2304 self.save_column_widths()
2305 self.config.set_key("column-widths", self.column_widths, True)
2306 self.config.set_key("console-history",self.console.history[-50:])
2312 def __init__(self, wallet, config, app=None):
2313 self.wallet = wallet
2314 self.config = config
2316 self.app = QApplication(sys.argv)
2319 def restore_or_create(self):
2320 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2321 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2322 if r==2: return None
2323 return 'restore' if r==1 else 'create'
2325 def seed_dialog(self):
2326 return ElectrumWindow.seed_dialog( self.wallet )
2328 def network_dialog(self):
2329 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2332 def show_seed(self):
2333 ElectrumWindow.show_seed(self.wallet.seed)
2336 def password_dialog(self):
2337 if self.wallet.seed:
2338 ElectrumWindow.change_password_dialog(self.wallet)
2341 def restore_wallet(self):
2342 wallet = self.wallet
2343 # wait until we are connected, because the user might have selected another server
2344 if not wallet.interface.is_connected:
2345 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2346 waiting_dialog(waiting)
2348 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2349 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2351 wallet.set_up_to_date(False)
2352 wallet.interface.poke('synchronizer')
2353 waiting_dialog(waiting)
2354 if wallet.is_found():
2355 print_error( "Recovery successful" )
2357 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2364 w = ElectrumWindow(self.wallet, self.config)
2365 if url: w.set_url(url)