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 c = 2 if is_recv else 1
533 item = l.currentItem()
534 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
535 l.editItem( item, c )
536 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
540 def address_label_clicked(self, item, column, l, column_addr, column_label):
541 if column == column_label and item.isSelected():
542 addr = unicode( item.text(column_addr) )
543 label = unicode( item.text(column_label) )
544 if label in self.wallet.aliases.keys():
546 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
547 l.editItem( item, column )
548 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
551 def address_label_changed(self, item, column, l, column_addr, column_label):
553 if column == column_label:
554 addr = unicode( item.text(column_addr) )
555 text = unicode( item.text(column_label) )
559 if text not in self.wallet.aliases.keys():
560 old_addr = self.wallet.labels.get(text)
562 self.wallet.labels[addr] = text
565 print_error("Error: This is one of your aliases")
566 label = self.wallet.labels.get(addr,'')
567 item.setText(column_label, QString(label))
569 s = self.wallet.labels.get(addr)
571 self.wallet.labels.pop(addr)
575 self.update_history_tab()
576 self.update_completions()
578 self.current_item_changed(item)
580 self.run_hook('item_changed',(self, item, column))
583 def current_item_changed(self, a):
584 self.run_hook('current_item_changed',(self, a))
588 def update_history_tab(self):
590 self.history_list.clear()
591 for item in self.wallet.get_tx_history():
592 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
595 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
601 icon = QIcon(":icons/unconfirmed.png")
603 icon = QIcon(":icons/clock%d.png"%conf)
605 icon = QIcon(":icons/confirmed.png")
608 icon = QIcon(":icons/unconfirmed.png")
610 if value is not None:
611 v_str = format_satoshis(value, True, self.wallet.num_zeros)
615 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
618 label, is_default_label = self.wallet.get_label(tx_hash)
620 label = _('Pruned transaction outputs')
621 is_default_label = False
623 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
624 item.setFont(2, QFont(MONOSPACE_FONT))
625 item.setFont(3, QFont(MONOSPACE_FONT))
626 item.setFont(4, QFont(MONOSPACE_FONT))
628 item.setForeground(3, QBrush(QColor("#BC1E1E")))
630 item.setData(0, Qt.UserRole, tx_hash)
631 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
633 item.setForeground(2, QBrush(QColor('grey')))
635 item.setIcon(0, icon)
636 self.history_list.insertTopLevelItem(0,item)
639 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
642 def create_send_tab(self):
647 grid.setColumnMinimumWidth(3,300)
648 grid.setColumnStretch(5,1)
650 self.payto_e = QLineEdit()
651 grid.addWidget(QLabel(_('Pay to')), 1, 0)
652 grid.addWidget(self.payto_e, 1, 1, 1, 3)
654 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)
656 completer = QCompleter()
657 completer.setCaseSensitivity(False)
658 self.payto_e.setCompleter(completer)
659 completer.setModel(self.completions)
661 self.message_e = QLineEdit()
662 grid.addWidget(QLabel(_('Description')), 2, 0)
663 grid.addWidget(self.message_e, 2, 1, 1, 3)
664 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)
666 self.amount_e = QLineEdit()
667 grid.addWidget(QLabel(_('Amount')), 3, 0)
668 grid.addWidget(self.amount_e, 3, 1, 1, 2)
669 grid.addWidget(HelpButton(
670 _('Amount to be sent.') + '\n\n' \
671 + _('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)
673 self.fee_e = QLineEdit()
674 grid.addWidget(QLabel(_('Fee')), 4, 0)
675 grid.addWidget(self.fee_e, 4, 1, 1, 2)
676 grid.addWidget(HelpButton(
677 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
678 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
679 + _('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)
682 b = EnterButton(_("Send"), self.do_send)
684 b = EnterButton(_("Create unsigned transaction"), self.do_send)
685 grid.addWidget(b, 6, 1)
687 b = EnterButton(_("Clear"),self.do_clear)
688 grid.addWidget(b, 6, 2)
690 self.payto_sig = QLabel('')
691 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
693 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
694 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
703 def entry_changed( is_fee ):
704 self.funds_error = False
705 amount = numbify(self.amount_e)
706 fee = numbify(self.fee_e)
707 if not is_fee: fee = None
710 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
712 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
715 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
716 text = self.status_text
719 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
720 self.funds_error = True
721 text = _( "Not enough funds" )
723 self.statusBar().showMessage(text)
724 self.amount_e.setPalette(palette)
725 self.fee_e.setPalette(palette)
727 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
728 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
730 self.run_hook('create_send_tab',(self,grid))
734 def update_completions(self):
736 for addr,label in self.wallet.labels.items():
737 if addr in self.wallet.addressbook:
738 l.append( label + ' <' + addr + '>')
739 l = l + self.wallet.aliases.keys()
741 self.completions.setStringList(l)
747 label = unicode( self.message_e.text() )
748 r = unicode( self.payto_e.text() )
752 m1 = re.match(ALIAS_REGEXP, r)
753 # label or alias, with address in brackets
754 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
757 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
761 to_address = m2.group(2)
765 if not is_valid(to_address):
766 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
770 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
772 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
775 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
777 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
780 if self.wallet.use_encryption:
781 password = self.password_dialog()
788 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
789 except BaseException, e:
790 self.show_message(str(e))
793 self.run_hook('send_tx', (wallet, self, tx))
796 self.wallet.labels[tx.hash()] = label
799 h = self.wallet.send_tx(tx)
800 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
801 status, msg = self.wallet.receive_tx( h )
803 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
805 self.update_contacts_tab()
807 QMessageBox.warning(self, _('Error'), msg, _('OK'))
809 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
811 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
812 with open(fileName,'w') as f:
813 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
814 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
816 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
821 def set_url(self, url):
822 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
823 self.tabs.setCurrentIndex(1)
824 label = self.wallet.labels.get(payto)
825 m_addr = label + ' <'+ payto+'>' if label else payto
826 self.payto_e.setText(m_addr)
828 self.message_e.setText(message)
829 self.amount_e.setText(amount)
831 self.set_frozen(self.payto_e,True)
832 self.set_frozen(self.amount_e,True)
833 self.set_frozen(self.message_e,True)
834 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
836 self.payto_sig.setVisible(False)
839 self.payto_sig.setVisible(False)
840 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
842 self.set_frozen(e,False)
844 def set_frozen(self,entry,frozen):
846 entry.setReadOnly(True)
847 entry.setFrame(False)
849 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
850 entry.setPalette(palette)
852 entry.setReadOnly(False)
855 palette.setColor(entry.backgroundRole(), QColor('white'))
856 entry.setPalette(palette)
859 def toggle_freeze(self,addr):
861 if addr in self.wallet.frozen_addresses:
862 self.wallet.unfreeze(addr)
864 self.wallet.freeze(addr)
865 self.update_receive_tab()
867 def toggle_priority(self,addr):
869 if addr in self.wallet.prioritized_addresses:
870 self.wallet.unprioritize(addr)
872 self.wallet.prioritize(addr)
873 self.update_receive_tab()
876 def create_list_tab(self, headers):
877 "generic tab creation method"
878 l = MyTreeWidget(self)
879 l.setColumnCount( len(headers) )
880 l.setHeaderLabels( headers )
890 vbox.addWidget(buttons)
895 buttons.setLayout(hbox)
900 def create_receive_tab(self):
901 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
902 l.setContextMenuPolicy(Qt.CustomContextMenu)
903 l.customContextMenuRequested.connect(self.create_receive_menu)
904 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
905 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
906 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
907 self.receive_list = l
908 self.receive_buttons_hbox = hbox
913 def receive_tab_set_mode(self, i):
914 self.save_column_widths()
915 self.expert_mode = (i == 1)
916 self.config.set_key('classic_expert_mode', self.expert_mode, True)
918 self.update_receive_tab()
921 def save_column_widths(self):
922 if not self.expert_mode:
923 widths = [ self.receive_list.columnWidth(0) ]
926 for i in range(self.receive_list.columnCount() -1):
927 widths.append(self.receive_list.columnWidth(i))
928 self.column_widths["receive"][self.expert_mode] = widths
930 self.column_widths["history"] = []
931 for i in range(self.history_list.columnCount() - 1):
932 self.column_widths["history"].append(self.history_list.columnWidth(i))
934 self.column_widths["contacts"] = []
935 for i in range(self.contacts_list.columnCount() - 1):
936 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
939 def create_contacts_tab(self):
940 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
941 l.setContextMenuPolicy(Qt.CustomContextMenu)
942 l.customContextMenuRequested.connect(self.create_contact_menu)
943 for i,width in enumerate(self.column_widths['contacts']):
944 l.setColumnWidth(i, width)
946 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
947 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
948 self.contacts_list = l
949 self.contacts_buttons_hbox = hbox
950 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
955 def delete_imported_key(self, addr):
956 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
957 self.wallet.imported_keys.pop(addr)
958 self.update_receive_tab()
959 self.update_history_tab()
963 def create_receive_menu(self, position):
964 # fixme: this function apparently has a side effect.
965 # if it is not called the menu pops up several times
966 #self.receive_list.selectedIndexes()
968 item = self.receive_list.itemAt(position)
970 addr = unicode(item.text(0))
972 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
973 menu.addAction(_("QR code"), lambda: ElectrumWindow.show_qrcode("bitcoin:" + addr, _("Address")) )
974 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
975 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
976 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
977 if addr in self.wallet.imported_keys:
978 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
981 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
982 menu.addAction(t, lambda: self.toggle_freeze(addr))
983 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
984 menu.addAction(t, lambda: self.toggle_priority(addr))
986 self.run_hook('receive_menu', (self, menu,))
987 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
990 def payto(self, x, is_alias):
997 label = self.wallet.labels.get(addr)
998 m_addr = label + ' <' + addr + '>' if label else addr
999 self.tabs.setCurrentIndex(1)
1000 self.payto_e.setText(m_addr)
1001 self.amount_e.setFocus()
1003 def delete_contact(self, x, is_alias):
1004 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1005 if not is_alias and x in self.wallet.addressbook:
1006 self.wallet.addressbook.remove(x)
1007 if x in self.wallet.labels.keys():
1008 self.wallet.labels.pop(x)
1009 elif is_alias and x in self.wallet.aliases:
1010 self.wallet.aliases.pop(x)
1011 self.update_history_tab()
1012 self.update_contacts_tab()
1013 self.update_completions()
1015 def create_contact_menu(self, position):
1016 # fixme: this function apparently has a side effect.
1017 # if it is not called the menu pops up several times
1018 #self.contacts_list.selectedIndexes()
1020 item = self.contacts_list.itemAt(position)
1022 addr = unicode(item.text(0))
1023 label = unicode(item.text(1))
1024 is_alias = label in self.wallet.aliases.keys()
1025 x = label if is_alias else addr
1027 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1028 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1029 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1031 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1033 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1034 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1035 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1038 def update_receive_item(self, item):
1039 item.setFont(0, QFont(MONOSPACE_FONT))
1040 address = str(item.data(0,0).toString())
1041 label = self.wallet.labels.get(address,'')
1042 item.setData(1,0,label)
1044 self.run_hook('update_receive_item', (self, address, item))
1046 c, u = self.wallet.get_addr_balance(address)
1047 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1048 item.setData(2,0,balance)
1050 if self.expert_mode:
1051 if address in self.wallet.frozen_addresses:
1052 item.setBackgroundColor(0, QColor('lightblue'))
1053 elif address in self.wallet.prioritized_addresses:
1054 item.setBackgroundColor(0, QColor('lightgreen'))
1057 def update_receive_tab(self):
1058 l = self.receive_list
1061 l.setColumnHidden(2, not self.expert_mode)
1062 l.setColumnHidden(3, not self.expert_mode)
1063 if not self.expert_mode:
1064 width = self.column_widths['receive'][0][0]
1065 l.setColumnWidth(0, width)
1067 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1068 l.setColumnWidth(i, width)
1071 for k, account in self.wallet.accounts.items():
1072 name = account.get('name',str(k))
1073 c,u = self.wallet.get_account_balance(k)
1074 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1075 l.addTopLevelItem(account_item)
1076 account_item.setExpanded(True)
1079 for is_change in [0,1]:
1080 name = "Receiving" if not is_change else "Change"
1081 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1082 account_item.addChild(seq_item)
1083 if not is_change: seq_item.setExpanded(True)
1087 for address in account[is_change]:
1088 h = self.wallet.history.get(address,[])
1093 if gap > self.wallet.gap_limit:
1098 num_tx = '*' if h == ['*'] else "%d"%len(h)
1099 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1100 self.update_receive_item(item)
1102 item.setBackgroundColor(1, QColor('red'))
1103 seq_item.addChild(item)
1105 if self.wallet.imported_keys:
1106 c,u = self.wallet.get_imported_balance()
1107 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1108 l.addTopLevelItem(account_item)
1109 account_item.setExpanded(True)
1110 for address in self.wallet.imported_keys.keys():
1111 item = QTreeWidgetItem( [ address, '', '', ''] )
1112 self.update_receive_item(item)
1113 account_item.addChild(item)
1116 # we use column 1 because column 0 may be hidden
1117 l.setCurrentItem(l.topLevelItem(0),1)
1119 def show_contact_details(self, m):
1120 a = self.wallet.aliases.get(m)
1122 if a[0] in self.wallet.authorities.keys():
1123 s = self.wallet.authorities.get(a[0])
1126 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1127 QMessageBox.information(self, 'Alias', msg, 'OK')
1129 def update_contacts_tab(self):
1131 l = self.contacts_list
1135 for alias, v in self.wallet.aliases.items():
1137 alias_targets.append(target)
1138 item = QTreeWidgetItem( [ target, alias, '-'] )
1139 item.setBackgroundColor(0, QColor('lightgray'))
1140 l.addTopLevelItem(item)
1142 for address in self.wallet.addressbook:
1143 if address in alias_targets: continue
1144 label = self.wallet.labels.get(address,'')
1146 for tx in self.wallet.transactions.values():
1147 if address in map(lambda x: x[0], tx.outputs): n += 1
1149 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1150 item.setFont(0, QFont(MONOSPACE_FONT))
1151 l.addTopLevelItem(item)
1153 l.setCurrentItem(l.topLevelItem(0))
1156 def create_console_tab(self):
1157 from qt_console import Console
1158 from electrum import util, bitcoin, commands
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"), lambda: self.show_seed_dialog(self.wallet, self) ) )
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(None)
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(), 6)
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, wallet, parent=None):
1258 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1261 if wallet.use_encryption:
1262 password = parent.password_dialog()
1269 seed = wallet.decode_seed(password)
1271 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1274 self.show_seed(seed)
1277 def show_seed(self, seed):
1278 dialog = QDialog(None)
1280 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1282 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1284 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1286 seed_text = QTextEdit(brainwallet)
1287 seed_text.setReadOnly(True)
1288 seed_text.setMaximumHeight(130)
1290 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1291 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1292 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1293 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1294 label2 = QLabel(msg2)
1295 label2.setWordWrap(True)
1298 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1299 logo.setMaximumWidth(60)
1301 qrw = QRCodeWidget(seed, 4)
1303 ok_button = QPushButton(_("OK"))
1304 ok_button.setDefault(True)
1305 ok_button.clicked.connect(dialog.accept)
1307 grid = QGridLayout()
1308 #main_layout.addWidget(logo, 0, 0)
1310 grid.addWidget(logo, 0, 0)
1311 grid.addWidget(label1, 0, 1)
1313 grid.addWidget(seed_text, 1, 0, 1, 2)
1315 grid.addWidget(qrw, 0, 2, 2, 1)
1317 vbox = QVBoxLayout()
1318 vbox.addLayout(grid)
1319 vbox.addWidget(label2)
1321 hbox = QHBoxLayout()
1323 hbox.addWidget(ok_button)
1324 vbox.addLayout(hbox)
1326 dialog.setLayout(vbox)
1330 def show_qrcode(data, title = "QR code"):
1334 d.setWindowTitle(title)
1335 d.setMinimumSize(270, 300)
1336 vbox = QVBoxLayout()
1337 qrw = QRCodeWidget(data)
1338 vbox.addWidget(qrw, 1)
1339 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1340 hbox = QHBoxLayout()
1344 filename = "qrcode.bmp"
1345 bmp.save_qrcode(qrw.qr, filename)
1346 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1348 b = QPushButton(_("Save"))
1350 b.clicked.connect(print_qr)
1352 b = QPushButton(_("Close"))
1354 b.clicked.connect(d.accept)
1357 vbox.addLayout(hbox)
1361 def view_private_key(self,address):
1362 if not address: return
1363 if self.wallet.use_encryption:
1364 password = self.password_dialog()
1371 pk = self.wallet.get_private_key(address, password)
1372 except BaseException, e:
1373 self.show_message(str(e))
1376 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1379 def sign_message(self,address):
1380 if not address: return
1383 d.setWindowTitle(_('Sign Message'))
1384 d.setMinimumSize(410, 290)
1386 tab_widget = QTabWidget()
1388 layout = QGridLayout(tab)
1390 sign_address = QLineEdit()
1392 sign_address.setText(address)
1393 layout.addWidget(QLabel(_('Address')), 1, 0)
1394 layout.addWidget(sign_address, 1, 1)
1396 sign_message = QTextEdit()
1397 layout.addWidget(QLabel(_('Message')), 2, 0)
1398 layout.addWidget(sign_message, 2, 1)
1399 layout.setRowStretch(2,3)
1401 sign_signature = QTextEdit()
1402 layout.addWidget(QLabel(_('Signature')), 3, 0)
1403 layout.addWidget(sign_signature, 3, 1)
1404 layout.setRowStretch(3,1)
1407 if self.wallet.use_encryption:
1408 password = self.password_dialog()
1415 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1416 sign_signature.setText(signature)
1417 except BaseException, e:
1418 self.show_message(str(e))
1421 hbox = QHBoxLayout()
1422 b = QPushButton(_("Sign"))
1424 b.clicked.connect(do_sign)
1425 b = QPushButton(_("Close"))
1426 b.clicked.connect(d.accept)
1428 layout.addLayout(hbox, 4, 1)
1429 tab_widget.addTab(tab, _("Sign"))
1433 layout = QGridLayout(tab)
1435 verify_address = QLineEdit()
1436 layout.addWidget(QLabel(_('Address')), 1, 0)
1437 layout.addWidget(verify_address, 1, 1)
1439 verify_message = QTextEdit()
1440 layout.addWidget(QLabel(_('Message')), 2, 0)
1441 layout.addWidget(verify_message, 2, 1)
1442 layout.setRowStretch(2,3)
1444 verify_signature = QTextEdit()
1445 layout.addWidget(QLabel(_('Signature')), 3, 0)
1446 layout.addWidget(verify_signature, 3, 1)
1447 layout.setRowStretch(3,1)
1451 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1452 self.show_message(_("Signature verified"))
1453 except BaseException, e:
1454 self.show_message(str(e))
1457 hbox = QHBoxLayout()
1458 b = QPushButton(_("Verify"))
1459 b.clicked.connect(do_verify)
1461 b = QPushButton(_("Close"))
1462 b.clicked.connect(d.accept)
1464 layout.addLayout(hbox, 4, 1)
1465 tab_widget.addTab(tab, _("Verify"))
1467 vbox = QVBoxLayout()
1468 vbox.addWidget(tab_widget)
1475 def question(self, msg):
1476 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1478 def show_message(self, msg):
1479 QMessageBox.information(self, _('Message'), msg, _('OK'))
1481 def password_dialog(self ):
1488 vbox = QVBoxLayout()
1489 msg = _('Please enter your password')
1490 vbox.addWidget(QLabel(msg))
1492 grid = QGridLayout()
1494 grid.addWidget(QLabel(_('Password')), 1, 0)
1495 grid.addWidget(pw, 1, 1)
1496 vbox.addLayout(grid)
1498 vbox.addLayout(ok_cancel_buttons(d))
1501 if not d.exec_(): return
1502 return unicode(pw.text())
1509 def change_password_dialog( wallet, parent=None ):
1512 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1520 new_pw = QLineEdit()
1521 new_pw.setEchoMode(2)
1522 conf_pw = QLineEdit()
1523 conf_pw.setEchoMode(2)
1525 vbox = QVBoxLayout()
1527 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1528 +_('To disable wallet encryption, enter an empty new password.')) \
1529 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1531 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1532 +_("Leave these fields empty if you want to disable encryption.")
1533 vbox.addWidget(QLabel(msg))
1535 grid = QGridLayout()
1538 if wallet.use_encryption:
1539 grid.addWidget(QLabel(_('Password')), 1, 0)
1540 grid.addWidget(pw, 1, 1)
1542 grid.addWidget(QLabel(_('New Password')), 2, 0)
1543 grid.addWidget(new_pw, 2, 1)
1545 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1546 grid.addWidget(conf_pw, 3, 1)
1547 vbox.addLayout(grid)
1549 vbox.addLayout(ok_cancel_buttons(d))
1552 if not d.exec_(): return
1554 password = unicode(pw.text()) if wallet.use_encryption else None
1555 new_password = unicode(new_pw.text())
1556 new_password2 = unicode(conf_pw.text())
1559 seed = wallet.decode_seed(password)
1561 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1564 if new_password != new_password2:
1565 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1566 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1568 wallet.update_password(seed, password, new_password)
1571 def seed_dialog(wallet, parent=None):
1575 vbox = QVBoxLayout()
1576 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1577 vbox.addWidget(QLabel(msg))
1579 grid = QGridLayout()
1582 seed_e = QLineEdit()
1583 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1584 grid.addWidget(seed_e, 1, 1)
1588 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1589 grid.addWidget(gap_e, 2, 1)
1590 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1591 vbox.addLayout(grid)
1593 vbox.addLayout(ok_cancel_buttons(d))
1596 if not d.exec_(): return
1599 gap = int(unicode(gap_e.text()))
1601 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1605 seed = str(seed_e.text())
1608 print_error("Warning: Not hex, trying decode")
1610 seed = mnemonic.mn_decode( seed.split(' ') )
1612 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1616 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1621 def generate_transaction_information_widget(self, tx):
1622 tabs = QTabWidget(self)
1625 grid_ui = QGridLayout(tab1)
1626 grid_ui.setColumnStretch(0,1)
1627 tabs.addTab(tab1, _('Outputs') )
1629 tree_widget = MyTreeWidget(self)
1630 tree_widget.setColumnCount(2)
1631 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1632 tree_widget.setColumnWidth(0, 300)
1633 tree_widget.setColumnWidth(1, 50)
1635 for output in tx.d["outputs"]:
1636 item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
1637 tree_widget.addTopLevelItem(item)
1639 tree_widget.setMaximumHeight(100)
1641 grid_ui.addWidget(tree_widget)
1644 grid_ui = QGridLayout(tab2)
1645 grid_ui.setColumnStretch(0,1)
1646 tabs.addTab(tab2, _('Inputs') )
1648 tree_widget = MyTreeWidget(self)
1649 tree_widget.setColumnCount(2)
1650 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1652 for input_line in tx.inputs:
1653 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1654 tree_widget.addTopLevelItem(item)
1656 tree_widget.setMaximumHeight(100)
1658 grid_ui.addWidget(tree_widget)
1662 def tx_dict_from_text(self, txt):
1664 tx_dict = json.loads(str(txt))
1665 assert "hex" in tx_dict.keys()
1666 assert "complete" in tx_dict.keys()
1667 if not tx_dict["complete"]:
1668 assert "input_info" in tx_dict.keys()
1670 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1675 def read_tx_from_file(self):
1676 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1680 with open(fileName, "r") as f:
1681 file_content = f.read()
1682 except (ValueError, IOError, os.error), reason:
1683 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1685 return self.tx_dict_from_text(file_content)
1688 def sign_raw_transaction(self, tx, input_info):
1689 if self.wallet.use_encryption:
1690 password = self.password_dialog()
1697 self.wallet.signrawtransaction(tx, input_info, [], password)
1699 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1701 with open(fileName, "w+") as f:
1702 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1703 self.show_message(_("Transaction saved succesfully"))
1704 except BaseException, e:
1705 self.show_message(str(e))
1708 def create_sign_transaction_window(self, tx_dict):
1709 tx = Transaction(tx_dict["hex"])
1711 dialog = QDialog(self)
1712 dialog.setMinimumWidth(500)
1713 dialog.setWindowTitle(_('Sign unsigned transaction'))
1716 vbox = QVBoxLayout()
1717 dialog.setLayout(vbox)
1718 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1720 if tx_dict["complete"] == True:
1721 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1723 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1724 vbox.addLayout(ok_cancel_buttons(dialog))
1725 input_info = json.loads(tx_dict["input_info"])
1728 self.sign_raw_transaction(tx, input_info)
1732 def do_sign_from_text(self):
1733 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1736 tx_dict = self.tx_dict_from_text(unicode(txt))
1738 self.create_sign_transaction_window(tx_dict)
1741 def do_sign_from_file(self):
1742 tx_dict = self.read_tx_from_file()
1744 self.create_sign_transaction_window(tx_dict)
1747 def send_raw_transaction(self, raw_tx):
1748 result, result_message = self.wallet.sendtx( raw_tx )
1750 self.show_message("Transaction succesfully sent: %s" % (result_message))
1752 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1755 def create_send_transaction_window(self, tx_dict):
1756 tx = Transaction(tx_dict["hex"])
1758 dialog = QDialog(self)
1759 dialog.setMinimumWidth(500)
1760 dialog.setWindowTitle(_('Send raw transaction'))
1763 vbox = QVBoxLayout()
1764 dialog.setLayout(vbox)
1765 vbox.addWidget( self.generate_transaction_information_widget(tx))
1767 if tx_dict["complete"] == False:
1768 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1770 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1771 vbox.addLayout(ok_cancel_buttons(dialog))
1774 self.send_raw_transaction(tx_dict["hex"])
1777 def do_send_from_file(self):
1778 tx_dict = self.read_tx_from_file()
1780 self.create_send_transaction_window(tx_dict)
1783 def do_send_from_text(self):
1784 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1787 tx_dict = self.tx_dict_from_text(unicode(txt))
1789 self.create_send_transaction_window(tx_dict)
1792 def do_export_privkeys(self):
1793 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.")))
1795 if self.wallet.use_encryption:
1796 password = self.password_dialog()
1802 select_export = _('Select file to export your private keys to')
1803 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1805 with open(fileName, "w+") as csvfile:
1806 transaction = csv.writer(csvfile)
1807 transaction.writerow(["address", "private_key"])
1810 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1811 transaction.writerow(["%34s"%addr,pk])
1813 self.show_message(_("Private keys exported."))
1815 except (IOError, os.error), reason:
1816 export_error_label = _("Electrum was unable to produce a private key-export.")
1817 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1819 except BaseException, e:
1820 self.show_message(str(e))
1824 def do_import_labels(self):
1825 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1826 if not labelsFile: return
1828 f = open(labelsFile, 'r')
1831 for key, value in json.loads(data).items():
1832 self.wallet.labels[key] = value
1834 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1835 except (IOError, os.error), reason:
1836 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1840 def do_export_labels(self):
1841 labels = self.wallet.labels
1843 labelsFile = util.user_dir() + '/labels.dat'
1844 f = open(labelsFile, 'w+')
1845 json.dump(labels, f)
1847 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1848 except (IOError, os.error), reason:
1849 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1851 def do_export_history(self):
1852 from gui_lite import csv_transaction
1853 csv_transaction(self.wallet)
1855 def do_import_privkey(self):
1856 if not self.wallet.imported_keys:
1857 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1858 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1859 + _('Are you sure you understand what you are doing?'), 3, 4)
1862 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1864 sec = str(text).strip()
1865 if self.wallet.use_encryption:
1866 password = self.password_dialog()
1872 addr = self.wallet.import_key(sec, password)
1874 QMessageBox.critical(None, _("Unable to import key"), "error")
1876 QMessageBox.information(None, _("Key imported"), addr)
1877 self.update_receive_tab()
1878 self.update_history_tab()
1879 except BaseException as e:
1880 QMessageBox.critical(None, _("Unable to import key"), str(e))
1882 def settings_dialog(self):
1884 d.setWindowTitle(_('Electrum Settings'))
1886 vbox = QVBoxLayout()
1888 tabs = QTabWidget(self)
1889 vbox.addWidget(tabs)
1892 grid_ui = QGridLayout(tab1)
1893 grid_ui.setColumnStretch(0,1)
1894 tabs.addTab(tab1, _('Display') )
1896 nz_label = QLabel(_('Display zeros'))
1897 grid_ui.addWidget(nz_label, 3, 0)
1899 nz_e.setText("%d"% self.wallet.num_zeros)
1900 grid_ui.addWidget(nz_e, 3, 1)
1901 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1902 grid_ui.addWidget(HelpButton(msg), 3, 2)
1903 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1904 if not self.config.is_modifiable('num_zeros'):
1905 for w in [nz_e, nz_label]: w.setEnabled(False)
1907 lang_label=QLabel(_('Language') + ':')
1908 grid_ui.addWidget(lang_label , 8, 0)
1909 lang_combo = QComboBox()
1910 from i18n import languages
1911 lang_combo.addItems(languages.values())
1913 index = languages.keys().index(self.config.get("language",''))
1916 lang_combo.setCurrentIndex(index)
1917 grid_ui.addWidget(lang_combo, 8, 1)
1918 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1919 if not self.config.is_modifiable('language'):
1920 for w in [lang_combo, lang_label]: w.setEnabled(False)
1922 currencies = self.exchanger.get_currencies()
1923 currencies.insert(0, "None")
1925 cur_label=QLabel(_('Currency') + ':')
1926 grid_ui.addWidget(cur_label , 9, 0)
1927 cur_combo = QComboBox()
1928 cur_combo.addItems(currencies)
1930 index = currencies.index(self.config.get('currency', "None"))
1933 cur_combo.setCurrentIndex(index)
1934 grid_ui.addWidget(cur_combo, 9, 1)
1935 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1937 view_label=QLabel(_('Receive Tab') + ':')
1938 grid_ui.addWidget(view_label , 10, 0)
1939 view_combo = QComboBox()
1940 view_combo.addItems([_('Simple'), _('Advanced')])
1941 view_combo.setCurrentIndex(self.expert_mode)
1942 grid_ui.addWidget(view_combo, 10, 1)
1943 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1944 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1945 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1947 grid_ui.addWidget(HelpButton(hh), 10, 2)
1951 grid_wallet = QGridLayout(tab2)
1952 grid_wallet.setColumnStretch(0,1)
1953 tabs.addTab(tab2, _('Wallet') )
1955 fee_label = QLabel(_('Transaction fee'))
1956 grid_wallet.addWidget(fee_label, 0, 0)
1958 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1959 grid_wallet.addWidget(fee_e, 0, 1)
1960 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1961 + _('Recommended value') + ': 0.001'
1962 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1963 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1964 if not self.config.is_modifiable('fee'):
1965 for w in [fee_e, fee_label]: w.setEnabled(False)
1967 usechange_label = QLabel(_('Use change addresses'))
1968 grid_wallet.addWidget(usechange_label, 1, 0)
1969 usechange_combo = QComboBox()
1970 usechange_combo.addItems([_('Yes'), _('No')])
1971 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1972 grid_wallet.addWidget(usechange_combo, 1, 1)
1973 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1974 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1976 gap_label = QLabel(_('Gap limit'))
1977 grid_wallet.addWidget(gap_label, 2, 0)
1979 gap_e.setText("%d"% self.wallet.gap_limit)
1980 grid_wallet.addWidget(gap_e, 2, 1)
1981 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1982 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1983 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1984 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1985 + _('Warning') + ': ' \
1986 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1987 + _('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'
1988 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1989 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1990 if not self.config.is_modifiable('gap_limit'):
1991 for w in [gap_e, gap_label]: w.setEnabled(False)
1993 grid_wallet.setRowStretch(3,1)
1998 grid_io = QGridLayout(tab3)
1999 grid_io.setColumnStretch(0,1)
2000 tabs.addTab(tab3, _('Import/Export') )
2002 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2003 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2004 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2005 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2007 grid_io.addWidget(QLabel(_('History')), 2, 0)
2008 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2009 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2011 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2013 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2014 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2015 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2017 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2018 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2019 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2020 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2021 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2023 grid_io.setRowStretch(4,1)
2026 grid_raw = QGridLayout(tab4)
2027 grid_raw.setColumnStretch(0,1)
2028 tabs.addTab(tab4, _('Raw tx') ) # move this to wallet tab
2030 if self.wallet.seed:
2031 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2032 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2033 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2034 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2036 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2037 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2038 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2039 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2040 grid_raw.setRowStretch(3,1)
2045 grid_plugins = QGridLayout(tab5)
2046 grid_plugins.setColumnStretch(0,1)
2047 tabs.addTab(tab5, _('Plugins') )
2048 def mk_toggle(cb, p):
2049 return lambda: cb.setChecked(p.toggle(self))
2050 for i, p in enumerate(self.plugins):
2052 name, description = p.get_info()
2053 cb = QCheckBox(name)
2054 cb.setChecked(p.is_enabled())
2055 cb.stateChanged.connect(mk_toggle(cb,p))
2056 grid_plugins.addWidget(cb, i, 0)
2057 grid_plugins.addWidget(HelpButton(description), i, 2)
2059 print_msg("Error: cannot display plugin", p)
2060 traceback.print_exc(file=sys.stdout)
2061 grid_plugins.setRowStretch(i+1,1)
2063 vbox.addLayout(ok_cancel_buttons(d))
2067 if not d.exec_(): return
2069 fee = unicode(fee_e.text())
2071 fee = int( 100000000 * Decimal(fee) )
2073 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2076 if self.wallet.fee != fee:
2077 self.wallet.fee = fee
2080 nz = unicode(nz_e.text())
2085 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2088 if self.wallet.num_zeros != nz:
2089 self.wallet.num_zeros = nz
2090 self.config.set_key('num_zeros', nz, True)
2091 self.update_history_tab()
2092 self.update_receive_tab()
2094 usechange_result = usechange_combo.currentIndex() == 0
2095 if self.wallet.use_change != usechange_result:
2096 self.wallet.use_change = usechange_result
2097 self.config.set_key('use_change', self.wallet.use_change, True)
2100 n = int(gap_e.text())
2102 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2105 if self.wallet.gap_limit != n:
2106 r = self.wallet.change_gap_limit(n)
2108 self.update_receive_tab()
2109 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2111 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2113 need_restart = False
2115 lang_request = languages.keys()[lang_combo.currentIndex()]
2116 if lang_request != self.config.get('language'):
2117 self.config.set_key("language", lang_request, True)
2120 cur_request = str(currencies[cur_combo.currentIndex()])
2121 if cur_request != self.config.get('currency', "None"):
2122 self.config.set_key('currency', cur_request, True)
2123 self.update_wallet()
2126 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2128 self.receive_tab_set_mode(view_combo.currentIndex())
2132 def network_dialog(wallet, parent=None):
2133 interface = wallet.interface
2135 if interface.is_connected:
2136 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2138 status = _("Not connected")
2139 server = interface.server
2142 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2143 server = interface.server
2145 plist, servers_list = interface.get_servers_list()
2149 d.setWindowTitle(_('Server'))
2150 d.setMinimumSize(375, 20)
2152 vbox = QVBoxLayout()
2155 hbox = QHBoxLayout()
2157 l.setPixmap(QPixmap(":icons/network.png"))
2160 hbox.addWidget(QLabel(status))
2162 vbox.addLayout(hbox)
2166 grid = QGridLayout()
2168 vbox.addLayout(grid)
2171 server_protocol = QComboBox()
2172 server_host = QLineEdit()
2173 server_host.setFixedWidth(200)
2174 server_port = QLineEdit()
2175 server_port.setFixedWidth(60)
2177 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2178 protocol_letters = 'thsg'
2179 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2180 server_protocol.addItems(protocol_names)
2182 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2183 grid.addWidget(server_protocol, 0, 1)
2184 grid.addWidget(server_host, 0, 2)
2185 grid.addWidget(server_port, 0, 3)
2187 def change_protocol(p):
2188 protocol = protocol_letters[p]
2189 host = unicode(server_host.text())
2190 pp = plist.get(host,DEFAULT_PORTS)
2191 if protocol not in pp.keys():
2192 protocol = pp.keys()[0]
2194 server_host.setText( host )
2195 server_port.setText( port )
2197 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2199 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2200 servers_list_widget = QTreeWidget(parent)
2201 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2202 servers_list_widget.setMaximumHeight(150)
2203 servers_list_widget.setColumnWidth(0, 240)
2204 for _host in servers_list.keys():
2205 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2206 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2208 def change_server(host, protocol=None):
2209 pp = plist.get(host,DEFAULT_PORTS)
2211 port = pp.get(protocol)
2212 if not port: protocol = None
2215 if 't' in pp.keys():
2217 port = pp.get(protocol)
2219 protocol = pp.keys()[0]
2220 port = pp.get(protocol)
2222 server_host.setText( host )
2223 server_port.setText( port )
2224 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2226 if not plist: return
2227 for p in protocol_letters:
2228 i = protocol_letters.index(p)
2229 j = server_protocol.model().index(i,0)
2230 if p not in pp.keys():
2231 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2233 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2237 host, port, protocol = server.split(':')
2238 change_server(host,protocol)
2240 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2241 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2243 if not wallet.config.is_modifiable('server'):
2244 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2247 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2248 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2249 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2250 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2253 proxy_mode = QComboBox()
2254 proxy_host = QLineEdit()
2255 proxy_host.setFixedWidth(200)
2256 proxy_port = QLineEdit()
2257 proxy_port.setFixedWidth(60)
2258 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2260 def check_for_disable(index = False):
2261 if proxy_mode.currentText() != 'NONE':
2262 proxy_host.setEnabled(True)
2263 proxy_port.setEnabled(True)
2265 proxy_host.setEnabled(False)
2266 proxy_port.setEnabled(False)
2269 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2271 if not wallet.config.is_modifiable('proxy'):
2272 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2274 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2275 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2276 proxy_host.setText(proxy_config.get("host"))
2277 proxy_port.setText(proxy_config.get("port"))
2279 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2280 grid.addWidget(proxy_mode, 2, 1)
2281 grid.addWidget(proxy_host, 2, 2)
2282 grid.addWidget(proxy_port, 2, 3)
2285 vbox.addLayout(ok_cancel_buttons(d))
2288 if not d.exec_(): return
2290 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2291 if proxy_mode.currentText() != 'NONE':
2292 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2296 wallet.config.set_key("proxy", proxy, True)
2297 wallet.config.set_key("server", server, True)
2298 interface.set_server(server, proxy)
2299 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2302 def closeEvent(self, event):
2304 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2305 self.save_column_widths()
2306 self.config.set_key("column-widths", self.column_widths, True)
2307 self.config.set_key("console-history",self.console.history[-50:])
2313 def __init__(self, wallet, config, app=None):
2314 self.wallet = wallet
2315 self.config = config
2317 self.app = QApplication(sys.argv)
2320 def restore_or_create(self):
2321 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2322 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2323 if r==2: return None
2324 return 'restore' if r==1 else 'create'
2326 def seed_dialog(self):
2327 return ElectrumWindow.seed_dialog( self.wallet )
2329 def network_dialog(self):
2330 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2333 def show_seed(self):
2334 ElectrumWindow.show_seed_dialog(self.wallet)
2337 def password_dialog(self):
2338 ElectrumWindow.change_password_dialog(self.wallet)
2341 def restore_wallet(self):
2342 wallet = self.wallet
2343 # wait until we are connected, because the user might have selected another server
2344 if not wallet.interface.is_connected:
2345 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2346 waiting_dialog(waiting)
2348 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2349 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2351 wallet.set_up_to_date(False)
2352 wallet.interface.poke('synchronizer')
2353 waiting_dialog(waiting)
2354 if wallet.is_found():
2355 print_error( "Recovery successful" )
2357 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2364 w = ElectrumWindow(self.wallet, self.config)
2365 if url: w.set_url(url)