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
45 import bmp, pyqrnative
48 from decimal import Decimal
56 if platform.system() == 'Windows':
57 MONOSPACE_FONT = 'Lucida Console'
58 elif platform.system() == 'Darwin':
59 MONOSPACE_FONT = 'Monaco'
61 MONOSPACE_FONT = 'monospace'
63 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
65 from electrum import ELECTRUM_VERSION
68 class UpdateLabel(QtGui.QLabel):
69 def __init__(self, config, parent=None):
70 QtGui.QLabel.__init__(self, parent)
71 self.new_version = False
74 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
75 con.request("GET", "/version")
76 res = con.getresponse()
77 except socket.error as msg:
78 print_error("Could not retrieve version information")
82 self.latest_version = res.read()
83 self.latest_version = self.latest_version.replace("\n","")
84 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
86 self.current_version = ELECTRUM_VERSION
87 if(self.compare_versions(self.latest_version, self.current_version) == 1):
88 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
89 if(self.compare_versions(self.latest_version, latest_seen) == 1):
90 self.new_version = True
91 self.setText(_("New version available") + ": " + self.latest_version)
94 def compare_versions(self, version1, version2):
96 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
97 return cmp(normalize(version1), normalize(version2))
99 def ignore_this_version(self):
101 self.config.set_key("last_seen_version", self.latest_version, True)
102 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
105 def ignore_all_version(self):
107 self.config.set_key("last_seen_version", "9.9.9", True)
108 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
111 def open_website(self):
112 webbrowser.open("http://electrum.org/download.html")
115 def mouseReleaseEvent(self, event):
116 dialog = QDialog(self)
117 dialog.setWindowTitle(_('Electrum update'))
120 main_layout = QGridLayout()
121 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
123 ignore_version = QPushButton(_("Ignore this version"))
124 ignore_version.clicked.connect(self.ignore_this_version)
126 ignore_all_versions = QPushButton(_("Ignore all versions"))
127 ignore_all_versions.clicked.connect(self.ignore_all_version)
129 open_website = QPushButton(_("Goto download page"))
130 open_website.clicked.connect(self.open_website)
132 main_layout.addWidget(ignore_version, 1, 0)
133 main_layout.addWidget(ignore_all_versions, 1, 1)
134 main_layout.addWidget(open_website, 1, 2)
136 dialog.setLayout(main_layout)
140 if not dialog.exec_(): return
142 def numbify(entry, is_int = False):
143 text = unicode(entry.text()).strip()
144 pos = entry.cursorPosition()
146 if not is_int: chars +='.'
147 s = ''.join([i for i in text if i in chars])
151 s = s.replace('.','')
152 s = s[:p] + '.' + s[p:p+8]
154 amount = int( Decimal(s) * 100000000 )
163 entry.setCursorPosition(pos)
167 class Timer(QtCore.QThread):
170 self.emit(QtCore.SIGNAL('timersignal'))
173 class HelpButton(QPushButton):
174 def __init__(self, text):
175 QPushButton.__init__(self, '?')
176 self.setFocusPolicy(Qt.NoFocus)
177 self.setFixedWidth(20)
178 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
181 class EnterButton(QPushButton):
182 def __init__(self, text, func):
183 QPushButton.__init__(self, text)
185 self.clicked.connect(func)
187 def keyPressEvent(self, e):
188 if e.key() == QtCore.Qt.Key_Return:
191 class MyTreeWidget(QTreeWidget):
192 def __init__(self, parent):
193 QTreeWidget.__init__(self, parent)
196 for i in range(0,self.viewport().height()/5):
197 if self.itemAt(QPoint(0,i*5)) == item:
201 for j in range(0,30):
202 if self.itemAt(QPoint(0,i*5 + j)) != item:
204 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
206 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
211 class StatusBarButton(QPushButton):
212 def __init__(self, icon, tooltip, func):
213 QPushButton.__init__(self, icon, '')
214 self.setToolTip(tooltip)
216 self.setMaximumWidth(25)
217 self.clicked.connect(func)
220 def keyPressEvent(self, e):
221 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):
253 b = QPushButton("OK")
255 b.clicked.connect(dialog.accept)
256 b = QPushButton("Cancel")
258 b.clicked.connect(dialog.reject)
262 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
263 "receive":[[370],[370,200,130]] }
265 class ElectrumWindow(QMainWindow):
267 def __init__(self, wallet, config):
268 QMainWindow.__init__(self)
274 self.wallet.interface.register_callback('updated', self.update_callback)
275 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
276 self.wallet.interface.register_callback('disconnected', self.update_callback)
277 self.wallet.interface.register_callback('disconnecting', self.update_callback)
279 self.expert_mode = config.get('classic_expert_mode', False)
280 self.merchant_name = config.get('merchant_name', 'Invoice')
282 set_language(config.get('language'))
284 self.funds_error = False
285 self.completions = QStringListModel()
287 self.tabs = tabs = QTabWidget(self)
288 self.column_widths = self.config.get("column-widths", default_column_widths )
289 tabs.addTab(self.create_history_tab(), _('History') )
290 tabs.addTab(self.create_send_tab(), _('Send') )
291 tabs.addTab(self.create_receive_tab(), _('Receive') )
292 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
293 tabs.addTab(self.create_console_tab(), _('Console') )
294 tabs.setMinimumSize(600, 400)
295 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
296 self.setCentralWidget(tabs)
297 self.create_status_bar()
299 g = self.config.get("winpos-qt",[100, 100, 840, 400])
300 self.setGeometry(g[0], g[1], g[2], g[3])
301 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
302 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
303 self.setWindowTitle( title )
305 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
306 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
307 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
308 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
310 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
311 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
312 self.history_list.setFocus(True)
314 self.exchanger = exchange_rate.Exchanger(self)
315 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
317 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
318 if platform.system() == 'Windows':
319 n = 3 if self.wallet.seed else 2
320 tabs.setCurrentIndex (n)
321 tabs.setCurrentIndex (0)
323 # set initial message
324 self.console.showMessage(self.wallet.banner)
328 def init_plugins(self):
330 if os.path.exists("plugins"):
331 fp, pathname, description = imp.find_module('plugins')
332 imp.load_module('electrum_plugins', fp, pathname, description)
333 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
334 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
336 import electrum_plugins
337 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
338 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
340 self.plugin_hooks = {}
341 for p in self.plugins:
345 print_msg("Error:cannot initialize plugin",p)
346 traceback.print_exc(file=sys.stdout)
348 def set_hook(self, name, callback):
349 h = self.plugin_hooks.get(name, [])
351 self.plugin_hooks[name] = h
353 def unset_hook(self, name, callback):
354 h = self.plugin_hooks.get(name,[])
355 if callback in h: h.remove(callback)
356 self.plugin_hooks[name] = h
358 def run_hook(self, name, args):
359 for cb in self.plugin_hooks.get(name,[]):
364 QMainWindow.close(self)
365 self.run_hook('close_main_window', (self,))
367 def connect_slots(self, sender):
368 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
369 self.previous_payto_e=''
371 def timer_actions(self):
372 self.run_hook('timer_actions', (self,))
374 if self.payto_e.hasFocus():
376 r = unicode( self.payto_e.text() )
377 if r != self.previous_payto_e:
378 self.previous_payto_e = r
380 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
382 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
386 s = r + ' <' + to_address + '>'
387 self.payto_e.setText(s)
390 def update_callback(self):
391 self.emit(QtCore.SIGNAL('updatesignal'))
393 def update_wallet(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 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
413 self.update_history_tab()
414 self.update_receive_tab()
415 self.update_contacts_tab()
416 self.update_completions()
419 def create_quote_text(self, btc_balance):
420 quote_currency = self.config.get("currency", "None")
421 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
422 if quote_balance is None:
425 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
428 def create_history_tab(self):
429 self.history_list = l = MyTreeWidget(self)
431 for i,width in enumerate(self.column_widths['history']):
432 l.setColumnWidth(i, width)
433 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
434 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
435 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
437 l.setContextMenuPolicy(Qt.CustomContextMenu)
438 l.customContextMenuRequested.connect(self.create_history_menu)
442 def create_history_menu(self, position):
443 self.history_list.selectedIndexes()
444 item = self.history_list.currentItem()
446 tx_hash = str(item.data(0, Qt.UserRole).toString())
447 if not tx_hash: return
449 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
450 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
451 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
452 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
455 def show_tx_details(self, tx):
456 dialog = QDialog(None)
458 dialog.setWindowTitle(_("Transaction Details"))
460 dialog.setLayout(vbox)
461 dialog.setMinimumSize(600,300)
464 if tx_hash in self.wallet.transactions.keys():
465 is_mine, v, fee = self.wallet.get_tx_value(tx)
466 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
468 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
474 vbox.addWidget(QLabel("Transaction ID:"))
475 e = QLineEdit(tx_hash)
479 vbox.addWidget(QLabel("Date: %s"%time_str))
480 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
483 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
484 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
486 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
487 vbox.addWidget(QLabel("Transaction fee: unknown"))
489 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
491 vbox.addWidget( self.generate_transaction_information_widget(tx) )
493 ok_button = QPushButton(_("Close"))
494 ok_button.setDefault(True)
495 ok_button.clicked.connect(dialog.accept)
499 hbox.addWidget(ok_button)
503 def tx_label_clicked(self, item, column):
504 if column==2 and item.isSelected():
506 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
507 self.history_list.editItem( item, column )
508 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
511 def tx_label_changed(self, item, column):
515 tx_hash = str(item.data(0, Qt.UserRole).toString())
516 tx = self.wallet.transactions.get(tx_hash)
517 s = self.wallet.labels.get(tx_hash)
518 text = unicode( item.text(2) )
520 self.wallet.labels[tx_hash] = text
521 item.setForeground(2, QBrush(QColor('black')))
523 if s: self.wallet.labels.pop(tx_hash)
524 text = self.wallet.get_default_label(tx_hash)
525 item.setText(2, text)
526 item.setForeground(2, QBrush(QColor('gray')))
530 def edit_label(self, is_recv):
531 l = self.receive_list if is_recv else self.contacts_list
532 item = l.currentItem()
533 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
534 l.editItem( item, 1 )
535 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
539 def address_label_clicked(self, item, column, l, column_addr, column_label):
540 if column == column_label and item.isSelected():
541 addr = unicode( item.text(column_addr) )
542 label = unicode( item.text(column_label) )
543 if label in self.wallet.aliases.keys():
545 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
546 l.editItem( item, column )
547 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
550 def address_label_changed(self, item, column, l, column_addr, column_label):
552 if column == column_label:
553 addr = unicode( item.text(column_addr) )
554 text = unicode( item.text(column_label) )
558 if text not in self.wallet.aliases.keys():
559 old_addr = self.wallet.labels.get(text)
561 self.wallet.labels[addr] = text
564 print_error("Error: This is one of your aliases")
565 label = self.wallet.labels.get(addr,'')
566 item.setText(column_label, QString(label))
568 s = self.wallet.labels.get(addr)
570 self.wallet.labels.pop(addr)
574 self.update_history_tab()
575 self.update_completions()
577 self.current_item_changed(item)
579 self.run_hook('item_changed',(self, item, column))
582 def current_item_changed(self, a):
583 self.run_hook('current_item_changed',(self, a))
587 def update_history_tab(self):
589 self.history_list.clear()
590 for item in self.wallet.get_tx_history():
591 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
594 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
600 icon = QIcon(":icons/unconfirmed.png")
602 icon = QIcon(":icons/clock%d.png"%conf)
604 icon = QIcon(":icons/confirmed.png")
607 icon = QIcon(":icons/unconfirmed.png")
609 if value is not None:
610 v_str = format_satoshis(value, True, self.wallet.num_zeros)
614 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
617 label, is_default_label = self.wallet.get_label(tx_hash)
619 label = _('Pruned transaction outputs')
620 is_default_label = False
622 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
623 item.setFont(2, QFont(MONOSPACE_FONT))
624 item.setFont(3, QFont(MONOSPACE_FONT))
625 item.setFont(4, QFont(MONOSPACE_FONT))
627 item.setForeground(3, QBrush(QColor("#BC1E1E")))
629 item.setData(0, Qt.UserRole, tx_hash)
630 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
632 item.setForeground(2, QBrush(QColor('grey')))
634 item.setIcon(0, icon)
635 self.history_list.insertTopLevelItem(0,item)
638 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
641 def create_send_tab(self):
646 grid.setColumnMinimumWidth(3,300)
647 grid.setColumnStretch(5,1)
649 self.payto_e = QLineEdit()
650 grid.addWidget(QLabel(_('Pay to')), 1, 0)
651 grid.addWidget(self.payto_e, 1, 1, 1, 3)
653 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)
655 completer = QCompleter()
656 completer.setCaseSensitivity(False)
657 self.payto_e.setCompleter(completer)
658 completer.setModel(self.completions)
660 self.message_e = QLineEdit()
661 grid.addWidget(QLabel(_('Description')), 2, 0)
662 grid.addWidget(self.message_e, 2, 1, 1, 3)
663 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)
665 self.amount_e = QLineEdit()
666 grid.addWidget(QLabel(_('Amount')), 3, 0)
667 grid.addWidget(self.amount_e, 3, 1, 1, 2)
668 grid.addWidget(HelpButton(
669 _('Amount to be sent.') + '\n\n' \
670 + _('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)
672 self.fee_e = QLineEdit()
673 grid.addWidget(QLabel(_('Fee')), 4, 0)
674 grid.addWidget(self.fee_e, 4, 1, 1, 2)
675 grid.addWidget(HelpButton(
676 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
677 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
678 + _('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)
681 b = EnterButton(_("Send"), self.do_send)
683 b = EnterButton(_("Create unsigned transaction"), self.do_send)
684 grid.addWidget(b, 6, 1)
686 b = EnterButton(_("Clear"),self.do_clear)
687 grid.addWidget(b, 6, 2)
689 self.payto_sig = QLabel('')
690 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
692 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
693 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
702 def entry_changed( is_fee ):
703 self.funds_error = False
704 amount = numbify(self.amount_e)
705 fee = numbify(self.fee_e)
706 if not is_fee: fee = None
709 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
711 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
714 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
715 text = self.status_text
718 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
719 self.funds_error = True
720 text = _( "Not enough funds" )
722 self.statusBar().showMessage(text)
723 self.amount_e.setPalette(palette)
724 self.fee_e.setPalette(palette)
726 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
727 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
729 self.run_hook('create_send_tab',(self,grid))
733 def update_completions(self):
735 for addr,label in self.wallet.labels.items():
736 if addr in self.wallet.addressbook:
737 l.append( label + ' <' + addr + '>')
738 l = l + self.wallet.aliases.keys()
740 self.completions.setStringList(l)
746 label = unicode( self.message_e.text() )
747 r = unicode( self.payto_e.text() )
751 m1 = re.match(ALIAS_REGEXP, r)
752 # label or alias, with address in brackets
753 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
756 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
760 to_address = m2.group(2)
764 if not is_valid(to_address):
765 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
769 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
771 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
774 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
776 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
779 if self.wallet.use_encryption:
780 password = self.password_dialog()
787 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
788 except BaseException, e:
789 self.show_message(str(e))
792 self.run_hook('send_tx', (wallet, self, tx))
795 self.wallet.labels[tx.hash()] = label
798 h = self.wallet.send_tx(tx)
799 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
800 status, msg = self.wallet.receive_tx( h )
802 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
804 self.update_contacts_tab()
806 QMessageBox.warning(self, _('Error'), msg, _('OK'))
808 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
810 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
811 with open(fileName,'w') as f:
812 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
813 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
815 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
820 def set_url(self, url):
821 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
822 self.tabs.setCurrentIndex(1)
823 label = self.wallet.labels.get(payto)
824 m_addr = label + ' <'+ payto+'>' if label else payto
825 self.payto_e.setText(m_addr)
827 self.message_e.setText(message)
828 self.amount_e.setText(amount)
830 self.set_frozen(self.payto_e,True)
831 self.set_frozen(self.amount_e,True)
832 self.set_frozen(self.message_e,True)
833 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
835 self.payto_sig.setVisible(False)
838 self.payto_sig.setVisible(False)
839 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
841 self.set_frozen(e,False)
843 def set_frozen(self,entry,frozen):
845 entry.setReadOnly(True)
846 entry.setFrame(False)
848 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
849 entry.setPalette(palette)
851 entry.setReadOnly(False)
854 palette.setColor(entry.backgroundRole(), QColor('white'))
855 entry.setPalette(palette)
858 def toggle_freeze(self,addr):
860 if addr in self.wallet.frozen_addresses:
861 self.wallet.unfreeze(addr)
863 self.wallet.freeze(addr)
864 self.update_receive_tab()
866 def toggle_priority(self,addr):
868 if addr in self.wallet.prioritized_addresses:
869 self.wallet.unprioritize(addr)
871 self.wallet.prioritize(addr)
872 self.update_receive_tab()
875 def create_list_tab(self, headers):
876 "generic tab creation method"
877 l = MyTreeWidget(self)
878 l.setColumnCount( len(headers) )
879 l.setHeaderLabels( headers )
889 vbox.addWidget(buttons)
894 buttons.setLayout(hbox)
899 def create_receive_tab(self):
900 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
901 l.setContextMenuPolicy(Qt.CustomContextMenu)
902 l.customContextMenuRequested.connect(self.create_receive_menu)
903 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
904 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
905 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
906 self.receive_list = l
907 self.receive_buttons_hbox = hbox
912 def receive_tab_set_mode(self, i):
913 self.save_column_widths()
914 self.expert_mode = (i == 1)
915 self.config.set_key('classic_expert_mode', self.expert_mode, True)
917 self.update_receive_tab()
920 def save_column_widths(self):
921 if not self.expert_mode:
922 widths = [ self.receive_list.columnWidth(0) ]
925 for i in range(self.receive_list.columnCount() -1):
926 widths.append(self.receive_list.columnWidth(i))
927 self.column_widths["receive"][self.expert_mode] = widths
929 self.column_widths["history"] = []
930 for i in range(self.history_list.columnCount() - 1):
931 self.column_widths["history"].append(self.history_list.columnWidth(i))
933 self.column_widths["contacts"] = []
934 for i in range(self.contacts_list.columnCount() - 1):
935 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
938 def create_contacts_tab(self):
939 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
940 l.setContextMenuPolicy(Qt.CustomContextMenu)
941 l.customContextMenuRequested.connect(self.create_contact_menu)
942 for i,width in enumerate(self.column_widths['contacts']):
943 l.setColumnWidth(i, width)
945 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
946 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
947 self.contacts_list = l
948 self.contacts_buttons_hbox = hbox
949 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
954 def delete_imported_key(self, addr):
955 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
956 self.wallet.imported_keys.pop(addr)
957 self.update_receive_tab()
958 self.update_history_tab()
962 def create_receive_menu(self, position):
963 # fixme: this function apparently has a side effect.
964 # if it is not called the menu pops up several times
965 #self.receive_list.selectedIndexes()
967 item = self.receive_list.itemAt(position)
969 addr = unicode(item.text(0))
970 if not is_valid(addr):
971 item.setExpanded(not item.isExpanded())
974 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
975 menu.addAction(_("QR code"), lambda: ElectrumWindow.show_qrcode("bitcoin:" + addr, _("Address")) )
976 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
977 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
978 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
979 if addr in self.wallet.imported_keys:
980 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
983 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
984 menu.addAction(t, lambda: self.toggle_freeze(addr))
985 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
986 menu.addAction(t, lambda: self.toggle_priority(addr))
988 self.run_hook('receive_menu', (self, menu,))
989 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
992 def payto(self, x, is_alias):
999 label = self.wallet.labels.get(addr)
1000 m_addr = label + ' <' + addr + '>' if label else addr
1001 self.tabs.setCurrentIndex(1)
1002 self.payto_e.setText(m_addr)
1003 self.amount_e.setFocus()
1005 def delete_contact(self, x, is_alias):
1006 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1007 if not is_alias and x in self.wallet.addressbook:
1008 self.wallet.addressbook.remove(x)
1009 if x in self.wallet.labels.keys():
1010 self.wallet.labels.pop(x)
1011 elif is_alias and x in self.wallet.aliases:
1012 self.wallet.aliases.pop(x)
1013 self.update_history_tab()
1014 self.update_contacts_tab()
1015 self.update_completions()
1017 def create_contact_menu(self, position):
1018 # fixme: this function apparently has a side effect.
1019 # if it is not called the menu pops up several times
1020 #self.contacts_list.selectedIndexes()
1022 item = self.contacts_list.itemAt(position)
1024 addr = unicode(item.text(0))
1025 label = unicode(item.text(1))
1026 is_alias = label in self.wallet.aliases.keys()
1027 x = label if is_alias else addr
1029 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1030 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1031 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1033 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1035 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1036 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1037 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1040 def update_receive_item(self, item):
1041 item.setFont(0, QFont(MONOSPACE_FONT))
1042 address = str(item.data(0,0).toString())
1043 label = self.wallet.labels.get(address,'')
1044 item.setData(1,0,label)
1046 self.run_hook('update_receive_item', (self, address, item))
1048 c, u = self.wallet.get_addr_balance(address)
1049 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1050 item.setData(2,0,balance)
1052 if self.expert_mode:
1053 if address in self.wallet.frozen_addresses:
1054 item.setBackgroundColor(0, QColor('lightblue'))
1055 elif address in self.wallet.prioritized_addresses:
1056 item.setBackgroundColor(0, QColor('lightgreen'))
1059 def update_receive_tab(self):
1060 l = self.receive_list
1063 l.setColumnHidden(2, not self.expert_mode)
1064 l.setColumnHidden(3, not self.expert_mode)
1065 if not self.expert_mode:
1066 width = self.column_widths['receive'][0][0]
1067 l.setColumnWidth(0, width)
1069 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1070 l.setColumnWidth(i, width)
1073 for k, account in self.wallet.accounts.items():
1074 name = account.get('name',str(k))
1075 c,u = self.wallet.get_account_balance(k)
1076 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1077 l.addTopLevelItem(account_item)
1078 account_item.setExpanded(True)
1081 for is_change in [0,1]:
1082 name = "Receiving" if not is_change else "Change"
1083 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1084 account_item.addChild(seq_item)
1085 if not is_change: seq_item.setExpanded(True)
1089 for address in account[is_change]:
1090 h = self.wallet.history.get(address,[])
1095 if gap > self.wallet.gap_limit:
1100 num_tx = '*' if h == ['*'] else "%d"%len(h)
1101 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1102 self.update_receive_item(item)
1104 item.setBackgroundColor(1, QColor('red'))
1105 seq_item.addChild(item)
1107 if self.wallet.imported_keys:
1108 c,u = self.wallet.get_imported_balance()
1109 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1110 l.addTopLevelItem(account_item)
1111 account_item.setExpanded(True)
1112 for address in self.wallet.imported_keys.keys():
1113 item = QTreeWidgetItem( [ address, '', '', ''] )
1114 self.update_receive_item(item)
1115 account_item.addChild(item)
1118 # we use column 1 because column 0 may be hidden
1119 l.setCurrentItem(l.topLevelItem(0),1)
1121 def show_contact_details(self, m):
1122 a = self.wallet.aliases.get(m)
1124 if a[0] in self.wallet.authorities.keys():
1125 s = self.wallet.authorities.get(a[0])
1128 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1129 QMessageBox.information(self, 'Alias', msg, 'OK')
1131 def update_contacts_tab(self):
1133 l = self.contacts_list
1137 for alias, v in self.wallet.aliases.items():
1139 alias_targets.append(target)
1140 item = QTreeWidgetItem( [ target, alias, '-'] )
1141 item.setBackgroundColor(0, QColor('lightgray'))
1142 l.addTopLevelItem(item)
1144 for address in self.wallet.addressbook:
1145 if address in alias_targets: continue
1146 label = self.wallet.labels.get(address,'')
1148 for tx in self.wallet.transactions.values():
1149 if address in map(lambda x: x[0], tx.outputs): n += 1
1151 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1152 item.setFont(0, QFont(MONOSPACE_FONT))
1153 l.addTopLevelItem(item)
1155 l.setCurrentItem(l.topLevelItem(0))
1158 def create_console_tab(self):
1159 from qt_console import Console
1160 from electrum import util, bitcoin, commands
1161 self.console = console = Console()
1162 self.console.history = self.config.get("console-history",[])
1163 self.console.history_index = len(self.console.history)
1165 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1166 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1168 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1170 def mkfunc(f, method):
1171 return lambda *args: apply( f, (method, args, self.password_dialog ))
1173 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1174 methods[m] = mkfunc(c._run, m)
1176 console.updateNamespace(methods)
1180 def create_status_bar(self):
1181 self.status_text = ""
1183 sb.setFixedHeight(35)
1184 qtVersion = qVersion()
1186 update_notification = UpdateLabel(self.config)
1187 if(update_notification.new_version):
1188 sb.addPermanentWidget(update_notification)
1190 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1191 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1192 if self.wallet.seed:
1193 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1194 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1195 if self.wallet.seed:
1196 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1197 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1198 sb.addPermanentWidget( self.status_button )
1200 self.setStatusBar(sb)
1204 self.config.set_key('gui', 'lite', True)
1207 self.lite.mini.show()
1209 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1210 self.lite.main(None)
1212 def new_contact_dialog(self):
1213 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1214 address = unicode(text)
1216 if is_valid(address):
1217 self.wallet.addressbook.append(address)
1219 self.update_contacts_tab()
1220 self.update_history_tab()
1221 self.update_completions()
1223 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1225 def show_master_public_key(self):
1226 dialog = QDialog(None)
1228 dialog.setWindowTitle(_("Master Public Key"))
1230 main_text = QTextEdit()
1231 main_text.setText(self.wallet.get_master_public_key())
1232 main_text.setReadOnly(True)
1233 main_text.setMaximumHeight(170)
1234 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1236 ok_button = QPushButton(_("OK"))
1237 ok_button.setDefault(True)
1238 ok_button.clicked.connect(dialog.accept)
1240 main_layout = QGridLayout()
1241 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1243 main_layout.addWidget(main_text, 1, 0)
1244 main_layout.addWidget(qrw, 1, 1 )
1246 vbox = QVBoxLayout()
1247 vbox.addLayout(main_layout)
1248 hbox = QHBoxLayout()
1250 hbox.addWidget(ok_button)
1251 vbox.addLayout(hbox)
1253 dialog.setLayout(vbox)
1258 def show_seed_dialog(self, wallet, parent=None):
1260 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1263 if wallet.use_encryption:
1264 password = parent.password_dialog()
1271 seed = wallet.decode_seed(password)
1273 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1276 self.show_seed(seed)
1279 def show_seed(self, seed):
1280 dialog = QDialog(None)
1282 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1284 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1286 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1288 seed_text = QTextEdit(brainwallet)
1289 seed_text.setReadOnly(True)
1290 seed_text.setMaximumHeight(130)
1292 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1293 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1294 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1295 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1296 label2 = QLabel(msg2)
1297 label2.setWordWrap(True)
1300 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1301 logo.setMaximumWidth(60)
1303 qrw = QRCodeWidget(seed, 4)
1305 ok_button = QPushButton(_("OK"))
1306 ok_button.setDefault(True)
1307 ok_button.clicked.connect(dialog.accept)
1309 grid = QGridLayout()
1310 #main_layout.addWidget(logo, 0, 0)
1312 grid.addWidget(logo, 0, 0)
1313 grid.addWidget(label1, 0, 1)
1315 grid.addWidget(seed_text, 1, 0, 1, 2)
1317 grid.addWidget(qrw, 0, 2, 2, 1)
1319 vbox = QVBoxLayout()
1320 vbox.addLayout(grid)
1321 vbox.addWidget(label2)
1323 hbox = QHBoxLayout()
1325 hbox.addWidget(ok_button)
1326 vbox.addLayout(hbox)
1328 dialog.setLayout(vbox)
1332 def show_qrcode(data, title = "QR code"):
1336 d.setWindowTitle(title)
1337 d.setMinimumSize(270, 300)
1338 vbox = QVBoxLayout()
1339 qrw = QRCodeWidget(data)
1340 vbox.addWidget(qrw, 1)
1341 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1342 hbox = QHBoxLayout()
1346 filename = "qrcode.bmp"
1347 bmp.save_qrcode(qrw.qr, filename)
1348 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1350 b = QPushButton(_("Save"))
1352 b.clicked.connect(print_qr)
1354 b = QPushButton(_("Close"))
1356 b.clicked.connect(d.accept)
1359 vbox.addLayout(hbox)
1363 def view_private_key(self,address):
1364 if not address: return
1365 if self.wallet.use_encryption:
1366 password = self.password_dialog()
1373 pk = self.wallet.get_private_key(address, password)
1374 except BaseException, e:
1375 self.show_message(str(e))
1378 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1381 def sign_message(self,address):
1382 if not address: return
1385 d.setWindowTitle(_('Sign Message'))
1386 d.setMinimumSize(410, 290)
1388 tab_widget = QTabWidget()
1390 layout = QGridLayout(tab)
1392 sign_address = QLineEdit()
1394 sign_address.setText(address)
1395 layout.addWidget(QLabel(_('Address')), 1, 0)
1396 layout.addWidget(sign_address, 1, 1)
1398 sign_message = QTextEdit()
1399 layout.addWidget(QLabel(_('Message')), 2, 0)
1400 layout.addWidget(sign_message, 2, 1)
1401 layout.setRowStretch(2,3)
1403 sign_signature = QTextEdit()
1404 layout.addWidget(QLabel(_('Signature')), 3, 0)
1405 layout.addWidget(sign_signature, 3, 1)
1406 layout.setRowStretch(3,1)
1409 if self.wallet.use_encryption:
1410 password = self.password_dialog()
1417 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1418 sign_signature.setText(signature)
1419 except BaseException, e:
1420 self.show_message(str(e))
1423 hbox = QHBoxLayout()
1424 b = QPushButton(_("Sign"))
1426 b.clicked.connect(do_sign)
1427 b = QPushButton(_("Close"))
1428 b.clicked.connect(d.accept)
1430 layout.addLayout(hbox, 4, 1)
1431 tab_widget.addTab(tab, _("Sign"))
1435 layout = QGridLayout(tab)
1437 verify_address = QLineEdit()
1438 layout.addWidget(QLabel(_('Address')), 1, 0)
1439 layout.addWidget(verify_address, 1, 1)
1441 verify_message = QTextEdit()
1442 layout.addWidget(QLabel(_('Message')), 2, 0)
1443 layout.addWidget(verify_message, 2, 1)
1444 layout.setRowStretch(2,3)
1446 verify_signature = QTextEdit()
1447 layout.addWidget(QLabel(_('Signature')), 3, 0)
1448 layout.addWidget(verify_signature, 3, 1)
1449 layout.setRowStretch(3,1)
1453 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1454 self.show_message(_("Signature verified"))
1455 except BaseException, e:
1456 self.show_message(str(e))
1459 hbox = QHBoxLayout()
1460 b = QPushButton(_("Verify"))
1461 b.clicked.connect(do_verify)
1463 b = QPushButton(_("Close"))
1464 b.clicked.connect(d.accept)
1466 layout.addLayout(hbox, 4, 1)
1467 tab_widget.addTab(tab, _("Verify"))
1469 vbox = QVBoxLayout()
1470 vbox.addWidget(tab_widget)
1477 def question(self, msg):
1478 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1480 def show_message(self, msg):
1481 QMessageBox.information(self, _('Message'), msg, _('OK'))
1483 def password_dialog(self ):
1490 vbox = QVBoxLayout()
1491 msg = _('Please enter your password')
1492 vbox.addWidget(QLabel(msg))
1494 grid = QGridLayout()
1496 grid.addWidget(QLabel(_('Password')), 1, 0)
1497 grid.addWidget(pw, 1, 1)
1498 vbox.addLayout(grid)
1500 vbox.addLayout(ok_cancel_buttons(d))
1503 if not d.exec_(): return
1504 return unicode(pw.text())
1511 def change_password_dialog( wallet, parent=None ):
1514 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1522 new_pw = QLineEdit()
1523 new_pw.setEchoMode(2)
1524 conf_pw = QLineEdit()
1525 conf_pw.setEchoMode(2)
1527 vbox = QVBoxLayout()
1529 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1530 +_('To disable wallet encryption, enter an empty new password.')) \
1531 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1533 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1534 +_("Leave these fields empty if you want to disable encryption.")
1535 vbox.addWidget(QLabel(msg))
1537 grid = QGridLayout()
1540 if wallet.use_encryption:
1541 grid.addWidget(QLabel(_('Password')), 1, 0)
1542 grid.addWidget(pw, 1, 1)
1544 grid.addWidget(QLabel(_('New Password')), 2, 0)
1545 grid.addWidget(new_pw, 2, 1)
1547 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1548 grid.addWidget(conf_pw, 3, 1)
1549 vbox.addLayout(grid)
1551 vbox.addLayout(ok_cancel_buttons(d))
1554 if not d.exec_(): return
1556 password = unicode(pw.text()) if wallet.use_encryption else None
1557 new_password = unicode(new_pw.text())
1558 new_password2 = unicode(conf_pw.text())
1561 seed = wallet.decode_seed(password)
1563 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1566 if new_password != new_password2:
1567 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1568 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1570 wallet.update_password(seed, password, new_password)
1573 def seed_dialog(wallet, parent=None):
1577 vbox = QVBoxLayout()
1578 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1579 vbox.addWidget(QLabel(msg))
1581 grid = QGridLayout()
1584 seed_e = QLineEdit()
1585 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1586 grid.addWidget(seed_e, 1, 1)
1590 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1591 grid.addWidget(gap_e, 2, 1)
1592 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1593 vbox.addLayout(grid)
1595 vbox.addLayout(ok_cancel_buttons(d))
1598 if not d.exec_(): return
1601 gap = int(unicode(gap_e.text()))
1603 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1607 seed = str(seed_e.text())
1610 print_error("Warning: Not hex, trying decode")
1612 seed = mnemonic.mn_decode( seed.split(' ') )
1614 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1618 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1623 def generate_transaction_information_widget(self, tx):
1624 tabs = QTabWidget(self)
1627 grid_ui = QGridLayout(tab1)
1628 grid_ui.setColumnStretch(0,1)
1629 tabs.addTab(tab1, _('Outputs') )
1631 tree_widget = MyTreeWidget(self)
1632 tree_widget.setColumnCount(2)
1633 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1634 tree_widget.setColumnWidth(0, 300)
1635 tree_widget.setColumnWidth(1, 50)
1637 for output in tx.d["outputs"]:
1638 item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
1639 tree_widget.addTopLevelItem(item)
1641 tree_widget.setMaximumHeight(100)
1643 grid_ui.addWidget(tree_widget)
1646 grid_ui = QGridLayout(tab2)
1647 grid_ui.setColumnStretch(0,1)
1648 tabs.addTab(tab2, _('Inputs') )
1650 tree_widget = MyTreeWidget(self)
1651 tree_widget.setColumnCount(2)
1652 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1654 for input_line in tx.inputs:
1655 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1656 tree_widget.addTopLevelItem(item)
1658 tree_widget.setMaximumHeight(100)
1660 grid_ui.addWidget(tree_widget)
1664 def tx_dict_from_text(self, txt):
1666 tx_dict = json.loads(str(txt))
1667 assert "hex" in tx_dict.keys()
1668 assert "complete" in tx_dict.keys()
1669 if not tx_dict["complete"]:
1670 assert "input_info" in tx_dict.keys()
1672 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1677 def read_tx_from_file(self):
1678 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1682 with open(fileName, "r") as f:
1683 file_content = f.read()
1684 except (ValueError, IOError, os.error), reason:
1685 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1687 return self.tx_dict_from_text(file_content)
1690 def sign_raw_transaction(self, tx, input_info):
1691 if self.wallet.use_encryption:
1692 password = self.password_dialog()
1699 self.wallet.signrawtransaction(tx, input_info, [], password)
1701 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1703 with open(fileName, "w+") as f:
1704 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1705 self.show_message(_("Transaction saved succesfully"))
1706 except BaseException, e:
1707 self.show_message(str(e))
1710 def create_sign_transaction_window(self, tx_dict):
1711 tx = Transaction(tx_dict["hex"])
1713 dialog = QDialog(self)
1714 dialog.setMinimumWidth(500)
1715 dialog.setWindowTitle(_('Sign unsigned transaction'))
1718 vbox = QVBoxLayout()
1719 dialog.setLayout(vbox)
1720 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1722 if tx_dict["complete"] == True:
1723 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1725 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1726 vbox.addLayout(ok_cancel_buttons(dialog))
1727 input_info = json.loads(tx_dict["input_info"])
1730 self.sign_raw_transaction(tx, input_info)
1734 def do_sign_from_text(self):
1735 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1738 tx_dict = self.tx_dict_from_text(unicode(txt))
1740 self.create_sign_transaction_window(tx_dict)
1743 def do_sign_from_file(self):
1744 tx_dict = self.read_tx_from_file()
1746 self.create_sign_transaction_window(tx_dict)
1749 def send_raw_transaction(self, raw_tx):
1750 result, result_message = self.wallet.sendtx( raw_tx )
1752 self.show_message("Transaction succesfully sent: %s" % (result_message))
1754 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1757 def create_send_transaction_window(self, tx_dict):
1758 tx = Transaction(tx_dict["hex"])
1760 dialog = QDialog(self)
1761 dialog.setMinimumWidth(500)
1762 dialog.setWindowTitle(_('Send raw transaction'))
1765 vbox = QVBoxLayout()
1766 dialog.setLayout(vbox)
1767 vbox.addWidget( self.generate_transaction_information_widget(tx))
1769 if tx_dict["complete"] == False:
1770 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1772 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1773 vbox.addLayout(ok_cancel_buttons(dialog))
1776 self.send_raw_transaction(tx_dict["hex"])
1779 def do_send_from_file(self):
1780 tx_dict = self.read_tx_from_file()
1782 self.create_send_transaction_window(tx_dict)
1785 def do_send_from_text(self):
1786 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1789 tx_dict = self.tx_dict_from_text(unicode(txt))
1791 self.create_send_transaction_window(tx_dict)
1794 def do_export_privkeys(self):
1795 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.")))
1797 if self.wallet.use_encryption:
1798 password = self.password_dialog()
1804 select_export = _('Select file to export your private keys to')
1805 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1807 with open(fileName, "w+") as csvfile:
1808 transaction = csv.writer(csvfile)
1809 transaction.writerow(["address", "private_key"])
1812 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1813 transaction.writerow(["%34s"%addr,pk])
1815 self.show_message(_("Private keys exported."))
1817 except (IOError, os.error), reason:
1818 export_error_label = _("Electrum was unable to produce a private key-export.")
1819 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1821 except BaseException, e:
1822 self.show_message(str(e))
1826 def do_import_labels(self):
1827 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1828 if not labelsFile: return
1830 f = open(labelsFile, 'r')
1833 for key, value in json.loads(data).items():
1834 self.wallet.labels[key] = value
1836 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1837 except (IOError, os.error), reason:
1838 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1842 def do_export_labels(self):
1843 labels = self.wallet.labels
1845 labelsFile = util.user_dir() + '/labels.dat'
1846 f = open(labelsFile, 'w+')
1847 json.dump(labels, f)
1849 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1850 except (IOError, os.error), reason:
1851 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1853 def do_export_history(self):
1854 from gui_lite import csv_transaction
1855 csv_transaction(self.wallet)
1857 def do_import_privkey(self):
1858 if not self.wallet.imported_keys:
1859 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1860 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1861 + _('Are you sure you understand what you are doing?'), 3, 4)
1864 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1866 sec = str(text).strip()
1867 if self.wallet.use_encryption:
1868 password = self.password_dialog()
1874 addr = self.wallet.import_key(sec, password)
1876 QMessageBox.critical(None, _("Unable to import key"), "error")
1878 QMessageBox.information(None, _("Key imported"), addr)
1879 self.update_receive_tab()
1880 self.update_history_tab()
1881 except BaseException as e:
1882 QMessageBox.critical(None, _("Unable to import key"), str(e))
1884 def settings_dialog(self):
1886 d.setWindowTitle(_('Electrum Settings'))
1888 vbox = QVBoxLayout()
1890 tabs = QTabWidget(self)
1891 vbox.addWidget(tabs)
1894 grid_ui = QGridLayout(tab1)
1895 grid_ui.setColumnStretch(0,1)
1896 tabs.addTab(tab1, _('Display') )
1898 nz_label = QLabel(_('Display zeros'))
1899 grid_ui.addWidget(nz_label, 3, 0)
1901 nz_e.setText("%d"% self.wallet.num_zeros)
1902 grid_ui.addWidget(nz_e, 3, 1)
1903 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1904 grid_ui.addWidget(HelpButton(msg), 3, 2)
1905 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1906 if not self.config.is_modifiable('num_zeros'):
1907 for w in [nz_e, nz_label]: w.setEnabled(False)
1909 lang_label=QLabel(_('Language') + ':')
1910 grid_ui.addWidget(lang_label , 8, 0)
1911 lang_combo = QComboBox()
1912 from i18n import languages
1913 lang_combo.addItems(languages.values())
1915 index = languages.keys().index(self.config.get("language",''))
1918 lang_combo.setCurrentIndex(index)
1919 grid_ui.addWidget(lang_combo, 8, 1)
1920 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1921 if not self.config.is_modifiable('language'):
1922 for w in [lang_combo, lang_label]: w.setEnabled(False)
1924 currencies = self.exchanger.get_currencies()
1925 currencies.insert(0, "None")
1927 cur_label=QLabel(_('Currency') + ':')
1928 grid_ui.addWidget(cur_label , 9, 0)
1929 cur_combo = QComboBox()
1930 cur_combo.addItems(currencies)
1932 index = currencies.index(self.config.get('currency', "None"))
1935 cur_combo.setCurrentIndex(index)
1936 grid_ui.addWidget(cur_combo, 9, 1)
1937 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1939 view_label=QLabel(_('Receive Tab') + ':')
1940 grid_ui.addWidget(view_label , 10, 0)
1941 view_combo = QComboBox()
1942 view_combo.addItems([_('Simple'), _('Advanced')])
1943 view_combo.setCurrentIndex(self.expert_mode)
1944 grid_ui.addWidget(view_combo, 10, 1)
1945 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1946 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1947 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1949 grid_ui.addWidget(HelpButton(hh), 10, 2)
1953 grid_wallet = QGridLayout(tab2)
1954 grid_wallet.setColumnStretch(0,1)
1955 tabs.addTab(tab2, _('Wallet') )
1957 fee_label = QLabel(_('Transaction fee'))
1958 grid_wallet.addWidget(fee_label, 0, 0)
1960 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1961 grid_wallet.addWidget(fee_e, 0, 1)
1962 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1963 + _('Recommended value') + ': 0.001'
1964 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1965 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1966 if not self.config.is_modifiable('fee'):
1967 for w in [fee_e, fee_label]: w.setEnabled(False)
1969 usechange_label = QLabel(_('Use change addresses'))
1970 grid_wallet.addWidget(usechange_label, 1, 0)
1971 usechange_combo = QComboBox()
1972 usechange_combo.addItems([_('Yes'), _('No')])
1973 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1974 grid_wallet.addWidget(usechange_combo, 1, 1)
1975 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1976 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1978 gap_label = QLabel(_('Gap limit'))
1979 grid_wallet.addWidget(gap_label, 2, 0)
1981 gap_e.setText("%d"% self.wallet.gap_limit)
1982 grid_wallet.addWidget(gap_e, 2, 1)
1983 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1984 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1985 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1986 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1987 + _('Warning') + ': ' \
1988 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1989 + _('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'
1990 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1991 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1992 if not self.config.is_modifiable('gap_limit'):
1993 for w in [gap_e, gap_label]: w.setEnabled(False)
1995 grid_wallet.setRowStretch(3,1)
2000 grid_io = QGridLayout(tab3)
2001 grid_io.setColumnStretch(0,1)
2002 tabs.addTab(tab3, _('Import/Export') )
2004 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2005 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2006 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2007 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2009 grid_io.addWidget(QLabel(_('History')), 2, 0)
2010 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2011 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2013 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2015 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2016 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2017 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2019 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2020 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2021 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2022 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2023 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2025 grid_io.setRowStretch(4,1)
2028 grid_raw = QGridLayout(tab4)
2029 grid_raw.setColumnStretch(0,1)
2030 tabs.addTab(tab4, _('Raw tx') ) # move this to wallet tab
2032 if self.wallet.seed:
2033 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2034 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2035 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2036 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2038 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2039 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2040 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2041 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2042 grid_raw.setRowStretch(3,1)
2047 grid_plugins = QGridLayout(tab5)
2048 grid_plugins.setColumnStretch(0,1)
2049 tabs.addTab(tab5, _('Plugins') )
2050 def mk_toggle(cb, p):
2051 return lambda: cb.setChecked(p.toggle(self))
2052 for i, p in enumerate(self.plugins):
2054 name, description = p.get_info()
2055 cb = QCheckBox(name)
2056 cb.setChecked(p.is_enabled())
2057 cb.stateChanged.connect(mk_toggle(cb,p))
2058 grid_plugins.addWidget(cb, i, 0)
2059 grid_plugins.addWidget(HelpButton(description), i, 2)
2061 print_msg("Error: cannot display plugin", p)
2062 traceback.print_exc(file=sys.stdout)
2063 grid_plugins.setRowStretch(i+1,1)
2065 vbox.addLayout(ok_cancel_buttons(d))
2069 if not d.exec_(): return
2071 fee = unicode(fee_e.text())
2073 fee = int( 100000000 * Decimal(fee) )
2075 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2078 if self.wallet.fee != fee:
2079 self.wallet.fee = fee
2082 nz = unicode(nz_e.text())
2087 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2090 if self.wallet.num_zeros != nz:
2091 self.wallet.num_zeros = nz
2092 self.config.set_key('num_zeros', nz, True)
2093 self.update_history_tab()
2094 self.update_receive_tab()
2096 usechange_result = usechange_combo.currentIndex() == 0
2097 if self.wallet.use_change != usechange_result:
2098 self.wallet.use_change = usechange_result
2099 self.config.set_key('use_change', self.wallet.use_change, True)
2102 n = int(gap_e.text())
2104 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2107 if self.wallet.gap_limit != n:
2108 r = self.wallet.change_gap_limit(n)
2110 self.update_receive_tab()
2111 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2113 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2115 need_restart = False
2117 lang_request = languages.keys()[lang_combo.currentIndex()]
2118 if lang_request != self.config.get('language'):
2119 self.config.set_key("language", lang_request, True)
2122 cur_request = str(currencies[cur_combo.currentIndex()])
2123 if cur_request != self.config.get('currency', "None"):
2124 self.config.set_key('currency', cur_request, True)
2125 self.update_wallet()
2128 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2130 self.receive_tab_set_mode(view_combo.currentIndex())
2134 def network_dialog(wallet, parent=None):
2135 interface = wallet.interface
2137 if interface.is_connected:
2138 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2140 status = _("Not connected")
2141 server = interface.server
2144 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2145 server = interface.server
2147 plist, servers_list = interface.get_servers_list()
2151 d.setWindowTitle(_('Server'))
2152 d.setMinimumSize(375, 20)
2154 vbox = QVBoxLayout()
2157 hbox = QHBoxLayout()
2159 l.setPixmap(QPixmap(":icons/network.png"))
2162 hbox.addWidget(QLabel(status))
2164 vbox.addLayout(hbox)
2168 grid = QGridLayout()
2170 vbox.addLayout(grid)
2173 server_protocol = QComboBox()
2174 server_host = QLineEdit()
2175 server_host.setFixedWidth(200)
2176 server_port = QLineEdit()
2177 server_port.setFixedWidth(60)
2179 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2180 protocol_letters = 'thsg'
2181 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2182 server_protocol.addItems(protocol_names)
2184 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2185 grid.addWidget(server_protocol, 0, 1)
2186 grid.addWidget(server_host, 0, 2)
2187 grid.addWidget(server_port, 0, 3)
2189 def change_protocol(p):
2190 protocol = protocol_letters[p]
2191 host = unicode(server_host.text())
2192 pp = plist.get(host,DEFAULT_PORTS)
2193 if protocol not in pp.keys():
2194 protocol = pp.keys()[0]
2196 server_host.setText( host )
2197 server_port.setText( port )
2199 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2201 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2202 servers_list_widget = QTreeWidget(parent)
2203 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2204 servers_list_widget.setMaximumHeight(150)
2205 servers_list_widget.setColumnWidth(0, 240)
2206 for _host in servers_list.keys():
2207 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2208 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2210 def change_server(host, protocol=None):
2211 pp = plist.get(host,DEFAULT_PORTS)
2213 port = pp.get(protocol)
2214 if not port: protocol = None
2217 if 't' in pp.keys():
2219 port = pp.get(protocol)
2221 protocol = pp.keys()[0]
2222 port = pp.get(protocol)
2224 server_host.setText( host )
2225 server_port.setText( port )
2226 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2228 if not plist: return
2229 for p in protocol_letters:
2230 i = protocol_letters.index(p)
2231 j = server_protocol.model().index(i,0)
2232 if p not in pp.keys():
2233 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2235 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2239 host, port, protocol = server.split(':')
2240 change_server(host,protocol)
2242 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2243 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2245 if not wallet.config.is_modifiable('server'):
2246 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2249 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2250 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2251 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2252 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2255 proxy_mode = QComboBox()
2256 proxy_host = QLineEdit()
2257 proxy_host.setFixedWidth(200)
2258 proxy_port = QLineEdit()
2259 proxy_port.setFixedWidth(60)
2260 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2262 def check_for_disable(index = False):
2263 if proxy_mode.currentText() != 'NONE':
2264 proxy_host.setEnabled(True)
2265 proxy_port.setEnabled(True)
2267 proxy_host.setEnabled(False)
2268 proxy_port.setEnabled(False)
2271 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2273 if not wallet.config.is_modifiable('proxy'):
2274 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2276 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2277 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2278 proxy_host.setText(proxy_config.get("host"))
2279 proxy_port.setText(proxy_config.get("port"))
2281 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2282 grid.addWidget(proxy_mode, 2, 1)
2283 grid.addWidget(proxy_host, 2, 2)
2284 grid.addWidget(proxy_port, 2, 3)
2287 vbox.addLayout(ok_cancel_buttons(d))
2290 if not d.exec_(): return
2292 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2293 if proxy_mode.currentText() != 'NONE':
2294 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2298 wallet.config.set_key("proxy", proxy, True)
2299 wallet.config.set_key("server", server, True)
2300 interface.set_server(server, proxy)
2301 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2304 def closeEvent(self, event):
2306 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2307 self.save_column_widths()
2308 self.config.set_key("column-widths", self.column_widths, True)
2309 self.config.set_key("console-history",self.console.history[-50:])
2315 def __init__(self, wallet, config, app=None):
2316 self.wallet = wallet
2317 self.config = config
2319 self.app = QApplication(sys.argv)
2322 def restore_or_create(self):
2323 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2324 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2325 if r==2: return None
2326 return 'restore' if r==1 else 'create'
2328 def seed_dialog(self):
2329 return ElectrumWindow.seed_dialog( self.wallet )
2331 def network_dialog(self):
2332 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2335 def show_seed(self):
2336 ElectrumWindow.show_seed_dialog(self.wallet)
2339 def password_dialog(self):
2340 ElectrumWindow.change_password_dialog(self.wallet)
2343 def restore_wallet(self):
2344 wallet = self.wallet
2345 # wait until we are connected, because the user might have selected another server
2346 if not wallet.interface.is_connected:
2347 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2348 waiting_dialog(waiting)
2350 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2351 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2353 wallet.set_up_to_date(False)
2354 wallet.interface.poke('synchronizer')
2355 waiting_dialog(waiting)
2356 if wallet.is_found():
2357 print_error( "Recovery successful" )
2359 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2366 w = ElectrumWindow(self.wallet, self.config)
2367 if url: w.set_url(url)