3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
23 from qrcodewidget import QRCodeWidget
28 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
30 from PyQt4.QtGui import *
31 from PyQt4.QtCore import *
32 import PyQt4.QtCore as QtCore
33 import PyQt4.QtGui as QtGui
34 from electrum.interface import DEFAULT_SERVERS
39 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41 from electrum.wallet import format_satoshis
42 from electrum.bitcoin import Transaction, is_valid
43 from electrum import mnemonic
44 from electrum import util, bitcoin, commands
46 import bmp, pyqrnative
49 from decimal import Decimal
57 if platform.system() == 'Windows':
58 MONOSPACE_FONT = 'Lucida Console'
59 elif platform.system() == 'Darwin':
60 MONOSPACE_FONT = 'Monaco'
62 MONOSPACE_FONT = 'monospace'
64 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
66 from electrum import ELECTRUM_VERSION
69 class UpdateLabel(QtGui.QLabel):
70 def __init__(self, config, parent=None):
71 QtGui.QLabel.__init__(self, parent)
72 self.new_version = False
75 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
76 con.request("GET", "/version")
77 res = con.getresponse()
78 except socket.error as msg:
79 print_error("Could not retrieve version information")
83 self.latest_version = res.read()
84 self.latest_version = self.latest_version.replace("\n","")
85 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
87 self.current_version = ELECTRUM_VERSION
88 if(self.compare_versions(self.latest_version, self.current_version) == 1):
89 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
90 if(self.compare_versions(self.latest_version, latest_seen) == 1):
91 self.new_version = True
92 self.setText(_("New version available") + ": " + self.latest_version)
95 def compare_versions(self, version1, version2):
97 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
98 return cmp(normalize(version1), normalize(version2))
100 def ignore_this_version(self):
102 self.config.set_key("last_seen_version", self.latest_version, True)
103 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
106 def ignore_all_version(self):
108 self.config.set_key("last_seen_version", "9.9.9", True)
109 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
112 def open_website(self):
113 webbrowser.open("http://electrum.org/download.html")
116 def mouseReleaseEvent(self, event):
117 dialog = QDialog(self)
118 dialog.setWindowTitle(_('Electrum update'))
121 main_layout = QGridLayout()
122 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
124 ignore_version = QPushButton(_("Ignore this version"))
125 ignore_version.clicked.connect(self.ignore_this_version)
127 ignore_all_versions = QPushButton(_("Ignore all versions"))
128 ignore_all_versions.clicked.connect(self.ignore_all_version)
130 open_website = QPushButton(_("Goto download page"))
131 open_website.clicked.connect(self.open_website)
133 main_layout.addWidget(ignore_version, 1, 0)
134 main_layout.addWidget(ignore_all_versions, 1, 1)
135 main_layout.addWidget(open_website, 1, 2)
137 dialog.setLayout(main_layout)
141 if not dialog.exec_(): return
143 def numbify(entry, is_int = False):
144 text = unicode(entry.text()).strip()
145 pos = entry.cursorPosition()
147 if not is_int: chars +='.'
148 s = ''.join([i for i in text if i in chars])
152 s = s.replace('.','')
153 s = s[:p] + '.' + s[p:p+8]
155 amount = int( Decimal(s) * 100000000 )
164 entry.setCursorPosition(pos)
168 class Timer(QtCore.QThread):
171 self.emit(QtCore.SIGNAL('timersignal'))
174 class HelpButton(QPushButton):
175 def __init__(self, text):
176 QPushButton.__init__(self, '?')
177 self.setFocusPolicy(Qt.NoFocus)
178 self.setFixedWidth(20)
179 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
182 class EnterButton(QPushButton):
183 def __init__(self, text, func):
184 QPushButton.__init__(self, text)
186 self.clicked.connect(func)
188 def keyPressEvent(self, e):
189 if e.key() == QtCore.Qt.Key_Return:
192 class MyTreeWidget(QTreeWidget):
193 def __init__(self, parent):
194 QTreeWidget.__init__(self, parent)
197 for i in range(0,self.viewport().height()/5):
198 if self.itemAt(QPoint(0,i*5)) == item:
202 for j in range(0,30):
203 if self.itemAt(QPoint(0,i*5 + j)) != item:
205 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
207 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
212 class StatusBarButton(QPushButton):
213 def __init__(self, icon, tooltip, func):
214 QPushButton.__init__(self, icon, '')
215 self.setToolTip(tooltip)
217 self.setMaximumWidth(25)
218 self.clicked.connect(func)
221 def keyPressEvent(self, e):
222 if e.key() == QtCore.Qt.Key_Return:
230 def waiting_dialog(f):
236 w.setWindowTitle('Electrum')
246 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
251 def ok_cancel_buttons(dialog):
254 b = QPushButton("Cancel")
256 b.clicked.connect(dialog.reject)
257 b = QPushButton("OK")
259 b.clicked.connect(dialog.accept)
264 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
265 "receive":[[370],[370,200,130]] }
267 class ElectrumWindow(QMainWindow):
269 def __init__(self, wallet, config):
270 QMainWindow.__init__(self)
276 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
277 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
278 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
279 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
281 self.expert_mode = config.get('classic_expert_mode', False)
283 set_language(config.get('language'))
285 self.funds_error = False
286 self.completions = QStringListModel()
288 self.tabs = tabs = QTabWidget(self)
289 self.column_widths = self.config.get("column-widths", default_column_widths )
290 tabs.addTab(self.create_history_tab(), _('History') )
291 tabs.addTab(self.create_send_tab(), _('Send') )
292 tabs.addTab(self.create_receive_tab(), _('Receive') )
293 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
294 tabs.addTab(self.create_console_tab(), _('Console') )
295 tabs.setMinimumSize(600, 400)
296 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
297 self.setCentralWidget(tabs)
298 self.create_status_bar()
300 g = self.config.get("winpos-qt",[100, 100, 840, 400])
301 self.setGeometry(g[0], g[1], g[2], g[3])
302 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
303 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
304 self.setWindowTitle( title )
306 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
307 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
308 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
309 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
311 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
312 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
313 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
314 self.history_list.setFocus(True)
316 self.exchanger = exchange_rate.Exchanger(self)
317 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
319 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
320 if platform.system() == 'Windows':
321 n = 3 if self.wallet.seed else 2
322 tabs.setCurrentIndex (n)
323 tabs.setCurrentIndex (0)
325 # set initial message
326 self.console.showMessage(self.wallet.banner)
330 def init_plugins(self):
332 if os.path.exists("plugins"):
333 fp, pathname, description = imp.find_module('plugins')
334 imp.load_module('electrum_plugins', fp, pathname, description)
335 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
336 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
338 import electrum_plugins
339 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
340 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
342 self.plugin_hooks = {}
343 for p in self.plugins:
347 print_msg("Error:cannot initialize plugin",p)
348 traceback.print_exc(file=sys.stdout)
350 def set_hook(self, name, callback):
351 h = self.plugin_hooks.get(name, [])
353 self.plugin_hooks[name] = h
355 def unset_hook(self, name, callback):
356 h = self.plugin_hooks.get(name,[])
357 if callback in h: h.remove(callback)
358 self.plugin_hooks[name] = h
360 def run_hook(self, name, args):
361 for cb in self.plugin_hooks.get(name,[]):
366 QMainWindow.close(self)
367 self.run_hook('close_main_window', (self,))
369 def connect_slots(self, sender):
370 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
371 self.previous_payto_e=''
373 def timer_actions(self):
374 self.run_hook('timer_actions', (self,))
376 if self.payto_e.hasFocus():
378 r = unicode( self.payto_e.text() )
379 if r != self.previous_payto_e:
380 self.previous_payto_e = r
382 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
384 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
388 s = r + ' <' + to_address + '>'
389 self.payto_e.setText(s)
393 def update_status(self):
394 if self.wallet.interface and self.wallet.interface.is_connected:
395 if not self.wallet.up_to_date:
396 text = _("Synchronizing...")
397 icon = QIcon(":icons/status_waiting.png")
399 c, u = self.wallet.get_balance()
400 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
401 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
402 text += self.create_quote_text(Decimal(c+u)/100000000)
403 icon = QIcon(":icons/status_connected.png")
405 text = _("Not connected")
406 icon = QIcon(":icons/status_disconnected.png")
408 self.status_text = text
409 self.statusBar().showMessage(text)
410 self.status_button.setIcon( icon )
412 def update_wallet(self):
414 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
415 self.update_history_tab()
416 self.update_receive_tab()
417 self.update_contacts_tab()
418 self.update_completions()
421 def create_quote_text(self, btc_balance):
422 quote_currency = self.config.get("currency", "None")
423 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
424 if quote_balance is None:
427 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
430 def create_history_tab(self):
431 self.history_list = l = MyTreeWidget(self)
433 for i,width in enumerate(self.column_widths['history']):
434 l.setColumnWidth(i, width)
435 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
436 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
437 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
439 l.setContextMenuPolicy(Qt.CustomContextMenu)
440 l.customContextMenuRequested.connect(self.create_history_menu)
444 def create_history_menu(self, position):
445 self.history_list.selectedIndexes()
446 item = self.history_list.currentItem()
448 tx_hash = str(item.data(0, Qt.UserRole).toString())
449 if not tx_hash: return
451 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
452 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
453 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
454 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
457 def show_tx_details(self, tx):
458 dialog = QDialog(self)
460 dialog.setWindowTitle(_("Transaction Details"))
462 dialog.setLayout(vbox)
463 dialog.setMinimumSize(600,300)
466 if tx_hash in self.wallet.transactions.keys():
467 is_mine, v, fee = self.wallet.get_tx_value(tx)
468 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
470 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
476 vbox.addWidget(QLabel("Transaction ID:"))
477 e = QLineEdit(tx_hash)
481 vbox.addWidget(QLabel("Date: %s"%time_str))
482 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
485 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
486 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
488 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
489 vbox.addWidget(QLabel("Transaction fee: unknown"))
491 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
493 vbox.addWidget( self.generate_transaction_information_widget(tx) )
495 ok_button = QPushButton(_("Close"))
496 ok_button.setDefault(True)
497 ok_button.clicked.connect(dialog.accept)
501 hbox.addWidget(ok_button)
505 def tx_label_clicked(self, item, column):
506 if column==2 and item.isSelected():
508 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
509 self.history_list.editItem( item, column )
510 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
513 def tx_label_changed(self, item, column):
517 tx_hash = str(item.data(0, Qt.UserRole).toString())
518 tx = self.wallet.transactions.get(tx_hash)
519 s = self.wallet.labels.get(tx_hash)
520 text = unicode( item.text(2) )
522 self.wallet.labels[tx_hash] = text
523 item.setForeground(2, QBrush(QColor('black')))
525 if s: self.wallet.labels.pop(tx_hash)
526 text = self.wallet.get_default_label(tx_hash)
527 item.setText(2, text)
528 item.setForeground(2, QBrush(QColor('gray')))
532 def edit_label(self, is_recv):
533 l = self.receive_list if is_recv else self.contacts_list
534 item = l.currentItem()
535 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
536 l.editItem( item, 1 )
537 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
541 def address_label_clicked(self, item, column, l, column_addr, column_label):
542 if column == column_label and item.isSelected():
543 addr = unicode( item.text(column_addr) )
544 label = unicode( item.text(column_label) )
545 if label in self.wallet.aliases.keys():
547 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
548 l.editItem( item, column )
549 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
552 def address_label_changed(self, item, column, l, column_addr, column_label):
554 if column == column_label:
555 addr = unicode( item.text(column_addr) )
556 text = unicode( item.text(column_label) )
560 if text not in self.wallet.aliases.keys():
561 old_addr = self.wallet.labels.get(text)
563 self.wallet.labels[addr] = text
566 print_error("Error: This is one of your aliases")
567 label = self.wallet.labels.get(addr,'')
568 item.setText(column_label, QString(label))
570 s = self.wallet.labels.get(addr)
572 self.wallet.labels.pop(addr)
576 self.update_history_tab()
577 self.update_completions()
579 self.current_item_changed(item)
581 self.run_hook('item_changed',(self, item, column))
584 def current_item_changed(self, a):
585 self.run_hook('current_item_changed',(self, a))
589 def update_history_tab(self):
591 self.history_list.clear()
592 for item in self.wallet.get_tx_history():
593 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
596 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
602 icon = QIcon(":icons/unconfirmed.png")
604 icon = QIcon(":icons/clock%d.png"%conf)
606 icon = QIcon(":icons/confirmed.png")
609 icon = QIcon(":icons/unconfirmed.png")
611 if value is not None:
612 v_str = format_satoshis(value, True, self.wallet.num_zeros)
616 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
619 label, is_default_label = self.wallet.get_label(tx_hash)
621 label = _('Pruned transaction outputs')
622 is_default_label = False
624 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
625 item.setFont(2, QFont(MONOSPACE_FONT))
626 item.setFont(3, QFont(MONOSPACE_FONT))
627 item.setFont(4, QFont(MONOSPACE_FONT))
629 item.setForeground(3, QBrush(QColor("#BC1E1E")))
631 item.setData(0, Qt.UserRole, tx_hash)
632 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
634 item.setForeground(2, QBrush(QColor('grey')))
636 item.setIcon(0, icon)
637 self.history_list.insertTopLevelItem(0,item)
640 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
643 def create_send_tab(self):
648 grid.setColumnMinimumWidth(3,300)
649 grid.setColumnStretch(5,1)
651 self.payto_e = QLineEdit()
652 grid.addWidget(QLabel(_('Pay to')), 1, 0)
653 grid.addWidget(self.payto_e, 1, 1, 1, 3)
655 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)
657 completer = QCompleter()
658 completer.setCaseSensitivity(False)
659 self.payto_e.setCompleter(completer)
660 completer.setModel(self.completions)
662 self.message_e = QLineEdit()
663 grid.addWidget(QLabel(_('Description')), 2, 0)
664 grid.addWidget(self.message_e, 2, 1, 1, 3)
665 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)
667 self.amount_e = QLineEdit()
668 grid.addWidget(QLabel(_('Amount')), 3, 0)
669 grid.addWidget(self.amount_e, 3, 1, 1, 2)
670 grid.addWidget(HelpButton(
671 _('Amount to be sent.') + '\n\n' \
672 + _('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)
674 self.fee_e = QLineEdit()
675 grid.addWidget(QLabel(_('Fee')), 4, 0)
676 grid.addWidget(self.fee_e, 4, 1, 1, 2)
677 grid.addWidget(HelpButton(
678 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
679 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
680 + _('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)
683 b = EnterButton(_("Send"), self.do_send)
685 b = EnterButton(_("Create unsigned transaction"), self.do_send)
686 grid.addWidget(b, 6, 1)
688 b = EnterButton(_("Clear"),self.do_clear)
689 grid.addWidget(b, 6, 2)
691 self.payto_sig = QLabel('')
692 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
694 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
695 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
704 def entry_changed( is_fee ):
705 self.funds_error = False
706 amount = numbify(self.amount_e)
707 fee = numbify(self.fee_e)
708 if not is_fee: fee = None
711 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
713 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
716 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
717 text = self.status_text
720 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
721 self.funds_error = True
722 text = _( "Not enough funds" )
724 self.statusBar().showMessage(text)
725 self.amount_e.setPalette(palette)
726 self.fee_e.setPalette(palette)
728 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
729 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
731 self.run_hook('create_send_tab',(self,grid))
735 def update_completions(self):
737 for addr,label in self.wallet.labels.items():
738 if addr in self.wallet.addressbook:
739 l.append( label + ' <' + addr + '>')
740 l = l + self.wallet.aliases.keys()
742 self.completions.setStringList(l)
746 return lambda s, *args: s.do_protect(func, args)
750 def do_send(self, password):
752 label = unicode( self.message_e.text() )
753 r = unicode( self.payto_e.text() )
757 m1 = re.match(ALIAS_REGEXP, r)
758 # label or alias, with address in brackets
759 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
762 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
766 to_address = m2.group(2)
770 if not is_valid(to_address):
771 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
775 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
777 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
780 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
782 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
786 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
787 except BaseException, e:
788 self.show_message(str(e))
791 self.run_hook('send_tx', (self.wallet, self, tx))
794 self.wallet.labels[tx.hash()] = label
797 h = self.wallet.send_tx(tx)
798 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
799 status, msg = self.wallet.receive_tx( h )
801 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
803 self.update_contacts_tab()
805 QMessageBox.warning(self, _('Error'), msg, _('OK'))
807 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
809 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
810 with open(fileName,'w') as f:
811 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
812 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
814 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
819 def set_url(self, url):
820 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
821 self.tabs.setCurrentIndex(1)
822 label = self.wallet.labels.get(payto)
823 m_addr = label + ' <'+ payto+'>' if label else payto
824 self.payto_e.setText(m_addr)
826 self.message_e.setText(message)
827 self.amount_e.setText(amount)
829 self.set_frozen(self.payto_e,True)
830 self.set_frozen(self.amount_e,True)
831 self.set_frozen(self.message_e,True)
832 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
834 self.payto_sig.setVisible(False)
837 self.payto_sig.setVisible(False)
838 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
840 self.set_frozen(e,False)
842 def set_frozen(self,entry,frozen):
844 entry.setReadOnly(True)
845 entry.setFrame(False)
847 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
848 entry.setPalette(palette)
850 entry.setReadOnly(False)
853 palette.setColor(entry.backgroundRole(), QColor('white'))
854 entry.setPalette(palette)
857 def toggle_freeze(self,addr):
859 if addr in self.wallet.frozen_addresses:
860 self.wallet.unfreeze(addr)
862 self.wallet.freeze(addr)
863 self.update_receive_tab()
865 def toggle_priority(self,addr):
867 if addr in self.wallet.prioritized_addresses:
868 self.wallet.unprioritize(addr)
870 self.wallet.prioritize(addr)
871 self.update_receive_tab()
874 def create_list_tab(self, headers):
875 "generic tab creation method"
876 l = MyTreeWidget(self)
877 l.setColumnCount( len(headers) )
878 l.setHeaderLabels( headers )
888 vbox.addWidget(buttons)
893 buttons.setLayout(hbox)
898 def create_receive_tab(self):
899 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
900 l.setContextMenuPolicy(Qt.CustomContextMenu)
901 l.customContextMenuRequested.connect(self.create_receive_menu)
902 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
903 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
904 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
905 self.receive_list = l
906 self.receive_buttons_hbox = hbox
911 def receive_tab_set_mode(self, i):
912 self.save_column_widths()
913 self.expert_mode = (i == 1)
914 self.config.set_key('classic_expert_mode', self.expert_mode, True)
916 self.update_receive_tab()
919 def save_column_widths(self):
920 if not self.expert_mode:
921 widths = [ self.receive_list.columnWidth(0) ]
924 for i in range(self.receive_list.columnCount() -1):
925 widths.append(self.receive_list.columnWidth(i))
926 self.column_widths["receive"][self.expert_mode] = widths
928 self.column_widths["history"] = []
929 for i in range(self.history_list.columnCount() - 1):
930 self.column_widths["history"].append(self.history_list.columnWidth(i))
932 self.column_widths["contacts"] = []
933 for i in range(self.contacts_list.columnCount() - 1):
934 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
937 def create_contacts_tab(self):
938 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
939 l.setContextMenuPolicy(Qt.CustomContextMenu)
940 l.customContextMenuRequested.connect(self.create_contact_menu)
941 for i,width in enumerate(self.column_widths['contacts']):
942 l.setColumnWidth(i, width)
944 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
945 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
946 self.contacts_list = l
947 self.contacts_buttons_hbox = hbox
948 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
953 def delete_imported_key(self, addr):
954 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
955 self.wallet.imported_keys.pop(addr)
956 self.update_receive_tab()
957 self.update_history_tab()
961 def create_receive_menu(self, position):
962 # fixme: this function apparently has a side effect.
963 # if it is not called the menu pops up several times
964 #self.receive_list.selectedIndexes()
966 item = self.receive_list.itemAt(position)
968 addr = unicode(item.text(0))
969 if not is_valid(addr):
970 item.setExpanded(not item.isExpanded())
973 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
974 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
975 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
976 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
977 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
978 if addr in self.wallet.imported_keys:
979 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
982 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
983 menu.addAction(t, lambda: self.toggle_freeze(addr))
984 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
985 menu.addAction(t, lambda: self.toggle_priority(addr))
987 self.run_hook('receive_menu', (self, menu,))
988 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
991 def payto(self, x, is_alias):
998 label = self.wallet.labels.get(addr)
999 m_addr = label + ' <' + addr + '>' if label else addr
1000 self.tabs.setCurrentIndex(1)
1001 self.payto_e.setText(m_addr)
1002 self.amount_e.setFocus()
1004 def delete_contact(self, x, is_alias):
1005 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1006 if not is_alias and x in self.wallet.addressbook:
1007 self.wallet.addressbook.remove(x)
1008 if x in self.wallet.labels.keys():
1009 self.wallet.labels.pop(x)
1010 elif is_alias and x in self.wallet.aliases:
1011 self.wallet.aliases.pop(x)
1012 self.update_history_tab()
1013 self.update_contacts_tab()
1014 self.update_completions()
1016 def create_contact_menu(self, position):
1017 # fixme: this function apparently has a side effect.
1018 # if it is not called the menu pops up several times
1019 #self.contacts_list.selectedIndexes()
1021 item = self.contacts_list.itemAt(position)
1023 addr = unicode(item.text(0))
1024 label = unicode(item.text(1))
1025 is_alias = label in self.wallet.aliases.keys()
1026 x = label if is_alias else addr
1028 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1029 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1030 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1032 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1034 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1035 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1036 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1039 def update_receive_item(self, item):
1040 item.setFont(0, QFont(MONOSPACE_FONT))
1041 address = str(item.data(0,0).toString())
1042 label = self.wallet.labels.get(address,'')
1043 item.setData(1,0,label)
1045 self.run_hook('update_receive_item', (self, address, item))
1047 c, u = self.wallet.get_addr_balance(address)
1048 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1049 item.setData(2,0,balance)
1051 if self.expert_mode:
1052 if address in self.wallet.frozen_addresses:
1053 item.setBackgroundColor(0, QColor('lightblue'))
1054 elif address in self.wallet.prioritized_addresses:
1055 item.setBackgroundColor(0, QColor('lightgreen'))
1058 def update_receive_tab(self):
1059 l = self.receive_list
1062 l.setColumnHidden(2, not self.expert_mode)
1063 l.setColumnHidden(3, not self.expert_mode)
1064 if not self.expert_mode:
1065 width = self.column_widths['receive'][0][0]
1066 l.setColumnWidth(0, width)
1068 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1069 l.setColumnWidth(i, width)
1072 for k, account in self.wallet.accounts.items():
1073 name = account.get('name',str(k))
1074 c,u = self.wallet.get_account_balance(k)
1075 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1076 l.addTopLevelItem(account_item)
1077 account_item.setExpanded(True)
1080 for is_change in [0,1]:
1081 name = "Receiving" if not is_change else "Change"
1082 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1083 account_item.addChild(seq_item)
1084 if not is_change: seq_item.setExpanded(True)
1088 for address in account[is_change]:
1089 h = self.wallet.history.get(address,[])
1094 if gap > self.wallet.gap_limit:
1099 num_tx = '*' if h == ['*'] else "%d"%len(h)
1100 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1101 self.update_receive_item(item)
1103 item.setBackgroundColor(1, QColor('red'))
1104 seq_item.addChild(item)
1106 if self.wallet.imported_keys:
1107 c,u = self.wallet.get_imported_balance()
1108 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1109 l.addTopLevelItem(account_item)
1110 account_item.setExpanded(True)
1111 for address in self.wallet.imported_keys.keys():
1112 item = QTreeWidgetItem( [ address, '', '', ''] )
1113 self.update_receive_item(item)
1114 account_item.addChild(item)
1117 # we use column 1 because column 0 may be hidden
1118 l.setCurrentItem(l.topLevelItem(0),1)
1120 def show_contact_details(self, m):
1121 a = self.wallet.aliases.get(m)
1123 if a[0] in self.wallet.authorities.keys():
1124 s = self.wallet.authorities.get(a[0])
1127 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1128 QMessageBox.information(self, 'Alias', msg, 'OK')
1130 def update_contacts_tab(self):
1132 l = self.contacts_list
1136 for alias, v in self.wallet.aliases.items():
1138 alias_targets.append(target)
1139 item = QTreeWidgetItem( [ target, alias, '-'] )
1140 item.setBackgroundColor(0, QColor('lightgray'))
1141 l.addTopLevelItem(item)
1143 for address in self.wallet.addressbook:
1144 if address in alias_targets: continue
1145 label = self.wallet.labels.get(address,'')
1147 for tx in self.wallet.transactions.values():
1148 if address in map(lambda x: x[0], tx.outputs): n += 1
1150 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1151 item.setFont(0, QFont(MONOSPACE_FONT))
1152 l.addTopLevelItem(item)
1154 l.setCurrentItem(l.topLevelItem(0))
1157 def create_console_tab(self):
1158 from qt_console import Console
1159 self.console = console = Console()
1160 self.console.history = self.config.get("console-history",[])
1161 self.console.history_index = len(self.console.history)
1163 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1164 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1166 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1168 def mkfunc(f, method):
1169 return lambda *args: apply( f, (method, args, self.password_dialog ))
1171 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1172 methods[m] = mkfunc(c._run, m)
1174 console.updateNamespace(methods)
1178 def create_status_bar(self):
1179 self.status_text = ""
1181 sb.setFixedHeight(35)
1182 qtVersion = qVersion()
1184 update_notification = UpdateLabel(self.config)
1185 if(update_notification.new_version):
1186 sb.addPermanentWidget(update_notification)
1188 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1189 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1190 if self.wallet.seed:
1191 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1192 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1193 if self.wallet.seed:
1194 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1195 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1196 sb.addPermanentWidget( self.status_button )
1198 self.setStatusBar(sb)
1202 self.config.set_key('gui', 'lite', True)
1205 self.lite.mini.show()
1207 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1208 self.lite.main(None)
1210 def new_contact_dialog(self):
1211 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1212 address = unicode(text)
1214 if is_valid(address):
1215 self.wallet.addressbook.append(address)
1217 self.update_contacts_tab()
1218 self.update_history_tab()
1219 self.update_completions()
1221 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1223 def show_master_public_key(self):
1224 dialog = QDialog(self)
1226 dialog.setWindowTitle(_("Master Public Key"))
1228 main_text = QTextEdit()
1229 main_text.setText(self.wallet.get_master_public_key())
1230 main_text.setReadOnly(True)
1231 main_text.setMaximumHeight(170)
1232 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1234 ok_button = QPushButton(_("OK"))
1235 ok_button.setDefault(True)
1236 ok_button.clicked.connect(dialog.accept)
1238 main_layout = QGridLayout()
1239 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1241 main_layout.addWidget(main_text, 1, 0)
1242 main_layout.addWidget(qrw, 1, 1 )
1244 vbox = QVBoxLayout()
1245 vbox.addLayout(main_layout)
1246 hbox = QHBoxLayout()
1248 hbox.addWidget(ok_button)
1249 vbox.addLayout(hbox)
1251 dialog.setLayout(vbox)
1256 def show_seed_dialog(self, password):
1257 if not self.wallet.seed:
1258 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1261 seed = self.wallet.decode_seed(password)
1263 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1265 self.show_seed(seed, self)
1269 def show_seed(self, seed, parent=None):
1270 dialog = QDialog(parent)
1272 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1274 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1276 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1278 seed_text = QTextEdit(brainwallet)
1279 seed_text.setReadOnly(True)
1280 seed_text.setMaximumHeight(130)
1282 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1283 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1284 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1285 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1286 label2 = QLabel(msg2)
1287 label2.setWordWrap(True)
1290 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1291 logo.setMaximumWidth(60)
1293 qrw = QRCodeWidget(seed)
1295 ok_button = QPushButton(_("OK"))
1296 ok_button.setDefault(True)
1297 ok_button.clicked.connect(dialog.accept)
1299 grid = QGridLayout()
1300 #main_layout.addWidget(logo, 0, 0)
1302 grid.addWidget(logo, 0, 0)
1303 grid.addWidget(label1, 0, 1)
1305 grid.addWidget(seed_text, 1, 0, 1, 2)
1307 grid.addWidget(qrw, 0, 2, 2, 1)
1309 vbox = QVBoxLayout()
1310 vbox.addLayout(grid)
1311 vbox.addWidget(label2)
1313 hbox = QHBoxLayout()
1315 hbox.addWidget(ok_button)
1316 vbox.addLayout(hbox)
1318 dialog.setLayout(vbox)
1321 def show_qrcode(self, data, title = "QR code"):
1325 d.setWindowTitle(title)
1326 d.setMinimumSize(270, 300)
1327 vbox = QVBoxLayout()
1328 qrw = QRCodeWidget(data)
1329 vbox.addWidget(qrw, 1)
1330 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1331 hbox = QHBoxLayout()
1335 filename = "qrcode.bmp"
1336 bmp.save_qrcode(qrw.qr, filename)
1337 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1339 b = QPushButton(_("Save"))
1341 b.clicked.connect(print_qr)
1343 b = QPushButton(_("Close"))
1345 b.clicked.connect(d.accept)
1348 vbox.addLayout(hbox)
1353 def do_protect(self, func, args):
1354 if self.wallet.use_encryption:
1355 password = self.password_dialog()
1361 if args != (False,):
1362 args = (self,) + args + (password,)
1364 args = (self,password)
1369 def show_private_key(self, address, password):
1370 if not address: return
1372 pk = self.wallet.get_private_key(address, password)
1373 except BaseException, e:
1374 self.show_message(str(e))
1376 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1380 def do_sign(self, address, message, signature, password):
1382 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1383 signature.setText(sig)
1384 except BaseException, e:
1385 self.show_message(str(e))
1387 def sign_message(self, address):
1388 if not address: return
1391 d.setWindowTitle(_('Sign Message'))
1392 d.setMinimumSize(410, 290)
1394 tab_widget = QTabWidget()
1396 layout = QGridLayout(tab)
1398 sign_address = QLineEdit()
1400 sign_address.setText(address)
1401 layout.addWidget(QLabel(_('Address')), 1, 0)
1402 layout.addWidget(sign_address, 1, 1)
1404 sign_message = QTextEdit()
1405 layout.addWidget(QLabel(_('Message')), 2, 0)
1406 layout.addWidget(sign_message, 2, 1)
1407 layout.setRowStretch(2,3)
1409 sign_signature = QTextEdit()
1410 layout.addWidget(QLabel(_('Signature')), 3, 0)
1411 layout.addWidget(sign_signature, 3, 1)
1412 layout.setRowStretch(3,1)
1415 hbox = QHBoxLayout()
1416 b = QPushButton(_("Sign"))
1418 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1419 b = QPushButton(_("Close"))
1420 b.clicked.connect(d.accept)
1422 layout.addLayout(hbox, 4, 1)
1423 tab_widget.addTab(tab, _("Sign"))
1427 layout = QGridLayout(tab)
1429 verify_address = QLineEdit()
1430 layout.addWidget(QLabel(_('Address')), 1, 0)
1431 layout.addWidget(verify_address, 1, 1)
1433 verify_message = QTextEdit()
1434 layout.addWidget(QLabel(_('Message')), 2, 0)
1435 layout.addWidget(verify_message, 2, 1)
1436 layout.setRowStretch(2,3)
1438 verify_signature = QTextEdit()
1439 layout.addWidget(QLabel(_('Signature')), 3, 0)
1440 layout.addWidget(verify_signature, 3, 1)
1441 layout.setRowStretch(3,1)
1445 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1446 self.show_message(_("Signature verified"))
1447 except BaseException, e:
1448 self.show_message(str(e))
1451 hbox = QHBoxLayout()
1452 b = QPushButton(_("Verify"))
1453 b.clicked.connect(do_verify)
1455 b = QPushButton(_("Close"))
1456 b.clicked.connect(d.accept)
1458 layout.addLayout(hbox, 4, 1)
1459 tab_widget.addTab(tab, _("Verify"))
1461 vbox = QVBoxLayout()
1462 vbox.addWidget(tab_widget)
1469 def question(self, msg):
1470 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1472 def show_message(self, msg):
1473 QMessageBox.information(self, _('Message'), msg, _('OK'))
1475 def password_dialog(self ):
1482 vbox = QVBoxLayout()
1483 msg = _('Please enter your password')
1484 vbox.addWidget(QLabel(msg))
1486 grid = QGridLayout()
1488 grid.addWidget(QLabel(_('Password')), 1, 0)
1489 grid.addWidget(pw, 1, 1)
1490 vbox.addLayout(grid)
1492 vbox.addLayout(ok_cancel_buttons(d))
1495 if not d.exec_(): return
1496 return unicode(pw.text())
1503 def change_password_dialog( wallet, parent=None ):
1506 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1514 new_pw = QLineEdit()
1515 new_pw.setEchoMode(2)
1516 conf_pw = QLineEdit()
1517 conf_pw.setEchoMode(2)
1519 vbox = QVBoxLayout()
1521 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1522 +_('To disable wallet encryption, enter an empty new password.')) \
1523 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1525 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1526 +_("Leave these fields empty if you want to disable encryption.")
1527 vbox.addWidget(QLabel(msg))
1529 grid = QGridLayout()
1532 if wallet.use_encryption:
1533 grid.addWidget(QLabel(_('Password')), 1, 0)
1534 grid.addWidget(pw, 1, 1)
1536 grid.addWidget(QLabel(_('New Password')), 2, 0)
1537 grid.addWidget(new_pw, 2, 1)
1539 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1540 grid.addWidget(conf_pw, 3, 1)
1541 vbox.addLayout(grid)
1543 vbox.addLayout(ok_cancel_buttons(d))
1546 if not d.exec_(): return
1548 password = unicode(pw.text()) if wallet.use_encryption else None
1549 new_password = unicode(new_pw.text())
1550 new_password2 = unicode(conf_pw.text())
1553 seed = wallet.decode_seed(password)
1555 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1558 if new_password != new_password2:
1559 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1560 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1562 wallet.update_password(seed, password, new_password)
1565 def seed_dialog(wallet, parent=None):
1569 vbox = QVBoxLayout()
1570 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1571 vbox.addWidget(QLabel(msg))
1573 grid = QGridLayout()
1576 seed_e = QLineEdit()
1577 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1578 grid.addWidget(seed_e, 1, 1)
1582 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1583 grid.addWidget(gap_e, 2, 1)
1584 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1585 vbox.addLayout(grid)
1587 vbox.addLayout(ok_cancel_buttons(d))
1590 if not d.exec_(): return
1593 gap = int(unicode(gap_e.text()))
1595 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1599 seed = str(seed_e.text())
1602 print_error("Warning: Not hex, trying decode")
1604 seed = mnemonic.mn_decode( seed.split(' ') )
1606 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1610 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1615 def generate_transaction_information_widget(self, tx):
1616 tabs = QTabWidget(self)
1619 grid_ui = QGridLayout(tab1)
1620 grid_ui.setColumnStretch(0,1)
1621 tabs.addTab(tab1, _('Outputs') )
1623 tree_widget = MyTreeWidget(self)
1624 tree_widget.setColumnCount(2)
1625 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1626 tree_widget.setColumnWidth(0, 300)
1627 tree_widget.setColumnWidth(1, 50)
1629 for address, value in tx.outputs:
1630 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1631 tree_widget.addTopLevelItem(item)
1633 tree_widget.setMaximumHeight(100)
1635 grid_ui.addWidget(tree_widget)
1638 grid_ui = QGridLayout(tab2)
1639 grid_ui.setColumnStretch(0,1)
1640 tabs.addTab(tab2, _('Inputs') )
1642 tree_widget = MyTreeWidget(self)
1643 tree_widget.setColumnCount(2)
1644 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1646 for input_line in tx.inputs:
1647 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1648 tree_widget.addTopLevelItem(item)
1650 tree_widget.setMaximumHeight(100)
1652 grid_ui.addWidget(tree_widget)
1656 def tx_dict_from_text(self, txt):
1658 tx_dict = json.loads(str(txt))
1659 assert "hex" in tx_dict.keys()
1660 assert "complete" in tx_dict.keys()
1661 if not tx_dict["complete"]:
1662 assert "input_info" in tx_dict.keys()
1664 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1669 def read_tx_from_file(self):
1670 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1674 with open(fileName, "r") as f:
1675 file_content = f.read()
1676 except (ValueError, IOError, os.error), reason:
1677 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1679 return self.tx_dict_from_text(file_content)
1683 def sign_raw_transaction(self, tx, input_info, password):
1685 self.wallet.signrawtransaction(tx, input_info, [], password)
1687 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1689 with open(fileName, "w+") as f:
1690 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1691 self.show_message(_("Transaction saved succesfully"))
1692 except BaseException, e:
1693 self.show_message(str(e))
1696 def create_sign_transaction_window(self, tx_dict):
1697 tx = Transaction(tx_dict["hex"])
1699 dialog = QDialog(self)
1700 dialog.setMinimumWidth(500)
1701 dialog.setWindowTitle(_('Sign unsigned transaction'))
1704 vbox = QVBoxLayout()
1705 dialog.setLayout(vbox)
1706 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1708 if tx_dict["complete"] == True:
1709 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1711 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1712 vbox.addLayout(ok_cancel_buttons(dialog))
1713 input_info = json.loads(tx_dict["input_info"])
1716 self.sign_raw_transaction(tx, input_info)
1720 def do_sign_from_text(self):
1721 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1724 tx_dict = self.tx_dict_from_text(unicode(txt))
1726 self.create_sign_transaction_window(tx_dict)
1729 def do_sign_from_file(self):
1730 tx_dict = self.read_tx_from_file()
1732 self.create_sign_transaction_window(tx_dict)
1735 def send_raw_transaction(self, raw_tx):
1736 result, result_message = self.wallet.sendtx( raw_tx )
1738 self.show_message("Transaction succesfully sent: %s" % (result_message))
1740 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1743 def create_send_transaction_window(self, tx_dict):
1744 tx = Transaction(tx_dict["hex"])
1746 dialog = QDialog(self)
1747 dialog.setMinimumWidth(500)
1748 dialog.setWindowTitle(_('Send raw transaction'))
1751 vbox = QVBoxLayout()
1752 dialog.setLayout(vbox)
1753 vbox.addWidget( self.generate_transaction_information_widget(tx))
1755 if tx_dict["complete"] == False:
1756 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1758 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1759 vbox.addLayout(ok_cancel_buttons(dialog))
1762 self.send_raw_transaction(tx_dict["hex"])
1765 def do_send_from_file(self):
1766 tx_dict = self.read_tx_from_file()
1768 self.create_send_transaction_window(tx_dict)
1771 def do_send_from_text(self):
1772 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1775 tx_dict = self.tx_dict_from_text(unicode(txt))
1777 self.create_send_transaction_window(tx_dict)
1781 def do_export_privkeys(self, password):
1782 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.")))
1785 select_export = _('Select file to export your private keys to')
1786 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1788 with open(fileName, "w+") as csvfile:
1789 transaction = csv.writer(csvfile)
1790 transaction.writerow(["address", "private_key"])
1793 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1794 transaction.writerow(["%34s"%addr,pk])
1796 self.show_message(_("Private keys exported."))
1798 except (IOError, os.error), reason:
1799 export_error_label = _("Electrum was unable to produce a private key-export.")
1800 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1802 except BaseException, e:
1803 self.show_message(str(e))
1807 def do_import_labels(self):
1808 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1809 if not labelsFile: return
1811 f = open(labelsFile, 'r')
1814 for key, value in json.loads(data).items():
1815 self.wallet.labels[key] = value
1817 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1818 except (IOError, os.error), reason:
1819 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1822 def do_export_labels(self):
1823 labels = self.wallet.labels
1825 labelsFile = util.user_dir() + '/labels.dat'
1826 f = open(labelsFile, 'w+')
1827 json.dump(labels, f)
1829 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1830 except (IOError, os.error), reason:
1831 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1834 def do_export_history(self):
1835 from gui_lite import csv_transaction
1836 csv_transaction(self.wallet)
1840 def do_import_privkey(self, password):
1841 if not self.wallet.imported_keys:
1842 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1843 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1844 + _('Are you sure you understand what you are doing?'), 3, 4)
1847 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1849 sec = str(text).strip()
1851 addr = self.wallet.import_key(sec, password)
1853 QMessageBox.critical(None, _("Unable to import key"), "error")
1855 QMessageBox.information(None, _("Key imported"), addr)
1856 self.update_receive_tab()
1857 self.update_history_tab()
1858 except BaseException as e:
1859 QMessageBox.critical(None, _("Unable to import key"), str(e))
1862 def settings_dialog(self):
1864 d.setWindowTitle(_('Electrum Settings'))
1866 vbox = QVBoxLayout()
1868 tabs = QTabWidget(self)
1869 vbox.addWidget(tabs)
1872 grid_ui = QGridLayout(tab1)
1873 grid_ui.setColumnStretch(0,1)
1874 tabs.addTab(tab1, _('Display') )
1876 nz_label = QLabel(_('Display zeros'))
1877 grid_ui.addWidget(nz_label, 3, 0)
1879 nz_e.setText("%d"% self.wallet.num_zeros)
1880 grid_ui.addWidget(nz_e, 3, 1)
1881 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1882 grid_ui.addWidget(HelpButton(msg), 3, 2)
1883 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1884 if not self.config.is_modifiable('num_zeros'):
1885 for w in [nz_e, nz_label]: w.setEnabled(False)
1887 lang_label=QLabel(_('Language') + ':')
1888 grid_ui.addWidget(lang_label , 8, 0)
1889 lang_combo = QComboBox()
1890 from i18n import languages
1891 lang_combo.addItems(languages.values())
1893 index = languages.keys().index(self.config.get("language",''))
1896 lang_combo.setCurrentIndex(index)
1897 grid_ui.addWidget(lang_combo, 8, 1)
1898 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1899 if not self.config.is_modifiable('language'):
1900 for w in [lang_combo, lang_label]: w.setEnabled(False)
1902 currencies = self.exchanger.get_currencies()
1903 currencies.insert(0, "None")
1905 cur_label=QLabel(_('Currency') + ':')
1906 grid_ui.addWidget(cur_label , 9, 0)
1907 cur_combo = QComboBox()
1908 cur_combo.addItems(currencies)
1910 index = currencies.index(self.config.get('currency', "None"))
1913 cur_combo.setCurrentIndex(index)
1914 grid_ui.addWidget(cur_combo, 9, 1)
1915 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1917 view_label=QLabel(_('Receive Tab') + ':')
1918 grid_ui.addWidget(view_label , 10, 0)
1919 view_combo = QComboBox()
1920 view_combo.addItems([_('Simple'), _('Advanced')])
1921 view_combo.setCurrentIndex(self.expert_mode)
1922 grid_ui.addWidget(view_combo, 10, 1)
1923 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1924 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1925 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1927 grid_ui.addWidget(HelpButton(hh), 10, 2)
1931 grid_wallet = QGridLayout(tab2)
1932 grid_wallet.setColumnStretch(0,1)
1933 tabs.addTab(tab2, _('Wallet') )
1935 fee_label = QLabel(_('Transaction fee'))
1936 grid_wallet.addWidget(fee_label, 0, 0)
1938 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1939 grid_wallet.addWidget(fee_e, 0, 1)
1940 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1941 + _('Recommended value') + ': 0.001'
1942 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1943 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1944 if not self.config.is_modifiable('fee'):
1945 for w in [fee_e, fee_label]: w.setEnabled(False)
1947 usechange_label = QLabel(_('Use change addresses'))
1948 grid_wallet.addWidget(usechange_label, 1, 0)
1949 usechange_combo = QComboBox()
1950 usechange_combo.addItems([_('Yes'), _('No')])
1951 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1952 grid_wallet.addWidget(usechange_combo, 1, 1)
1953 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1954 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1956 gap_label = QLabel(_('Gap limit'))
1957 grid_wallet.addWidget(gap_label, 2, 0)
1959 gap_e.setText("%d"% self.wallet.gap_limit)
1960 grid_wallet.addWidget(gap_e, 2, 1)
1961 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1962 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1963 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1964 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1965 + _('Warning') + ': ' \
1966 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1967 + _('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'
1968 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1969 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1970 if not self.config.is_modifiable('gap_limit'):
1971 for w in [gap_e, gap_label]: w.setEnabled(False)
1973 grid_wallet.setRowStretch(3,1)
1978 grid_io = QGridLayout(tab3)
1979 grid_io.setColumnStretch(0,1)
1980 tabs.addTab(tab3, _('Import/Export') )
1982 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1983 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1984 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1985 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1987 grid_io.addWidget(QLabel(_('History')), 2, 0)
1988 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1989 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1991 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1993 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1994 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1995 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1997 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1998 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1999 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2000 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2001 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2003 grid_io.setRowStretch(4,1)
2006 grid_raw = QGridLayout(tab4)
2007 grid_raw.setColumnStretch(0,1)
2008 tabs.addTab(tab4, _('Raw tx') ) # move this to wallet tab
2010 if self.wallet.seed:
2011 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2012 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2013 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2014 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2016 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2017 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2018 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2019 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2020 grid_raw.setRowStretch(3,1)
2025 grid_plugins = QGridLayout(tab5)
2026 grid_plugins.setColumnStretch(0,1)
2027 tabs.addTab(tab5, _('Plugins') )
2028 def mk_toggle(cb, p):
2029 return lambda: cb.setChecked(p.toggle(self))
2030 for i, p in enumerate(self.plugins):
2032 name, description = p.get_info()
2033 cb = QCheckBox(name)
2034 cb.setChecked(p.is_enabled())
2035 cb.stateChanged.connect(mk_toggle(cb,p))
2036 grid_plugins.addWidget(cb, i, 0)
2037 grid_plugins.addWidget(HelpButton(description), i, 2)
2039 print_msg("Error: cannot display plugin", p)
2040 traceback.print_exc(file=sys.stdout)
2041 grid_plugins.setRowStretch(i+1,1)
2043 vbox.addLayout(ok_cancel_buttons(d))
2047 if not d.exec_(): return
2049 fee = unicode(fee_e.text())
2051 fee = int( 100000000 * Decimal(fee) )
2053 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2056 if self.wallet.fee != fee:
2057 self.wallet.fee = fee
2060 nz = unicode(nz_e.text())
2065 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2068 if self.wallet.num_zeros != nz:
2069 self.wallet.num_zeros = nz
2070 self.config.set_key('num_zeros', nz, True)
2071 self.update_history_tab()
2072 self.update_receive_tab()
2074 usechange_result = usechange_combo.currentIndex() == 0
2075 if self.wallet.use_change != usechange_result:
2076 self.wallet.use_change = usechange_result
2077 self.config.set_key('use_change', self.wallet.use_change, True)
2080 n = int(gap_e.text())
2082 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2085 if self.wallet.gap_limit != n:
2086 r = self.wallet.change_gap_limit(n)
2088 self.update_receive_tab()
2089 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2091 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2093 need_restart = False
2095 lang_request = languages.keys()[lang_combo.currentIndex()]
2096 if lang_request != self.config.get('language'):
2097 self.config.set_key("language", lang_request, True)
2100 cur_request = str(currencies[cur_combo.currentIndex()])
2101 if cur_request != self.config.get('currency', "None"):
2102 self.config.set_key('currency', cur_request, True)
2103 self.update_wallet()
2106 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2108 self.receive_tab_set_mode(view_combo.currentIndex())
2112 def network_dialog(wallet, parent=None):
2113 interface = wallet.interface
2115 if interface.is_connected:
2116 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2118 status = _("Not connected")
2119 server = interface.server
2122 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2123 server = interface.server
2125 plist, servers_list = interface.get_servers_list()
2129 d.setWindowTitle(_('Server'))
2130 d.setMinimumSize(375, 20)
2132 vbox = QVBoxLayout()
2135 hbox = QHBoxLayout()
2137 l.setPixmap(QPixmap(":icons/network.png"))
2140 hbox.addWidget(QLabel(status))
2142 vbox.addLayout(hbox)
2146 grid = QGridLayout()
2148 vbox.addLayout(grid)
2151 server_protocol = QComboBox()
2152 server_host = QLineEdit()
2153 server_host.setFixedWidth(200)
2154 server_port = QLineEdit()
2155 server_port.setFixedWidth(60)
2157 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2158 protocol_letters = 'thsg'
2159 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2160 server_protocol.addItems(protocol_names)
2162 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2163 grid.addWidget(server_protocol, 0, 1)
2164 grid.addWidget(server_host, 0, 2)
2165 grid.addWidget(server_port, 0, 3)
2167 def change_protocol(p):
2168 protocol = protocol_letters[p]
2169 host = unicode(server_host.text())
2170 pp = plist.get(host,DEFAULT_PORTS)
2171 if protocol not in pp.keys():
2172 protocol = pp.keys()[0]
2174 server_host.setText( host )
2175 server_port.setText( port )
2177 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2179 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2180 servers_list_widget = QTreeWidget(parent)
2181 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2182 servers_list_widget.setMaximumHeight(150)
2183 servers_list_widget.setColumnWidth(0, 240)
2184 for _host in servers_list.keys():
2185 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2186 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2188 def change_server(host, protocol=None):
2189 pp = plist.get(host,DEFAULT_PORTS)
2191 port = pp.get(protocol)
2192 if not port: protocol = None
2195 if 't' in pp.keys():
2197 port = pp.get(protocol)
2199 protocol = pp.keys()[0]
2200 port = pp.get(protocol)
2202 server_host.setText( host )
2203 server_port.setText( port )
2204 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2206 if not plist: return
2207 for p in protocol_letters:
2208 i = protocol_letters.index(p)
2209 j = server_protocol.model().index(i,0)
2210 if p not in pp.keys():
2211 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2213 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2217 host, port, protocol = server.split(':')
2218 change_server(host,protocol)
2220 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2221 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2223 if not wallet.config.is_modifiable('server'):
2224 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2227 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2228 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2229 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2230 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2233 proxy_mode = QComboBox()
2234 proxy_host = QLineEdit()
2235 proxy_host.setFixedWidth(200)
2236 proxy_port = QLineEdit()
2237 proxy_port.setFixedWidth(60)
2238 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2240 def check_for_disable(index = False):
2241 if proxy_mode.currentText() != 'NONE':
2242 proxy_host.setEnabled(True)
2243 proxy_port.setEnabled(True)
2245 proxy_host.setEnabled(False)
2246 proxy_port.setEnabled(False)
2249 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2251 if not wallet.config.is_modifiable('proxy'):
2252 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2254 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2255 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2256 proxy_host.setText(proxy_config.get("host"))
2257 proxy_port.setText(proxy_config.get("port"))
2259 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2260 grid.addWidget(proxy_mode, 2, 1)
2261 grid.addWidget(proxy_host, 2, 2)
2262 grid.addWidget(proxy_port, 2, 3)
2265 vbox.addLayout(ok_cancel_buttons(d))
2268 if not d.exec_(): return
2270 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2271 if proxy_mode.currentText() != 'NONE':
2272 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2276 wallet.config.set_key("proxy", proxy, True)
2277 wallet.config.set_key("server", server, True)
2278 interface.set_server(server, proxy)
2279 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2282 def closeEvent(self, event):
2284 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2285 self.save_column_widths()
2286 self.config.set_key("column-widths", self.column_widths, True)
2287 self.config.set_key("console-history",self.console.history[-50:])
2293 def __init__(self, wallet, config, app=None):
2294 self.wallet = wallet
2295 self.config = config
2297 self.app = QApplication(sys.argv)
2300 def restore_or_create(self):
2301 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2302 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2303 if r==2: return None
2304 return 'restore' if r==1 else 'create'
2306 def seed_dialog(self):
2307 return ElectrumWindow.seed_dialog( self.wallet )
2309 def network_dialog(self):
2310 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2313 def show_seed(self):
2314 ElectrumWindow.show_seed(self.wallet.seed)
2317 def password_dialog(self):
2318 if self.wallet.seed:
2319 ElectrumWindow.change_password_dialog(self.wallet)
2322 def restore_wallet(self):
2323 wallet = self.wallet
2324 # wait until we are connected, because the user might have selected another server
2325 if not wallet.interface.is_connected:
2326 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2327 waiting_dialog(waiting)
2329 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2330 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2332 wallet.set_up_to_date(False)
2333 wallet.interface.poke('synchronizer')
2334 waiting_dialog(waiting)
2335 if wallet.is_found():
2336 print_error( "Recovery successful" )
2338 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2345 w = ElectrumWindow(self.wallet, self.config)
2346 if url: w.set_url(url)