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))
971 if not is_valid(addr):
972 item.setExpanded(not item.isExpanded())
975 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
976 menu.addAction(_("QR code"), lambda: ElectrumWindow.show_qrcode("bitcoin:" + addr, _("Address")) )
977 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
978 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
979 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
980 if addr in self.wallet.imported_keys:
981 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
984 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
985 menu.addAction(t, lambda: self.toggle_freeze(addr))
986 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
987 menu.addAction(t, lambda: self.toggle_priority(addr))
989 self.run_hook('receive_menu', (self, menu,))
990 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
993 def payto(self, x, is_alias):
1000 label = self.wallet.labels.get(addr)
1001 m_addr = label + ' <' + addr + '>' if label else addr
1002 self.tabs.setCurrentIndex(1)
1003 self.payto_e.setText(m_addr)
1004 self.amount_e.setFocus()
1006 def delete_contact(self, x, is_alias):
1007 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1008 if not is_alias and x in self.wallet.addressbook:
1009 self.wallet.addressbook.remove(x)
1010 if x in self.wallet.labels.keys():
1011 self.wallet.labels.pop(x)
1012 elif is_alias and x in self.wallet.aliases:
1013 self.wallet.aliases.pop(x)
1014 self.update_history_tab()
1015 self.update_contacts_tab()
1016 self.update_completions()
1018 def create_contact_menu(self, position):
1019 # fixme: this function apparently has a side effect.
1020 # if it is not called the menu pops up several times
1021 #self.contacts_list.selectedIndexes()
1023 item = self.contacts_list.itemAt(position)
1025 addr = unicode(item.text(0))
1026 label = unicode(item.text(1))
1027 is_alias = label in self.wallet.aliases.keys()
1028 x = label if is_alias else addr
1030 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1031 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1032 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1034 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1036 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1037 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1038 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1041 def update_receive_item(self, item):
1042 item.setFont(0, QFont(MONOSPACE_FONT))
1043 address = str(item.data(0,0).toString())
1044 label = self.wallet.labels.get(address,'')
1045 item.setData(1,0,label)
1047 self.run_hook('update_receive_item', (self, address, item))
1049 c, u = self.wallet.get_addr_balance(address)
1050 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1051 item.setData(2,0,balance)
1053 if self.expert_mode:
1054 if address in self.wallet.frozen_addresses:
1055 item.setBackgroundColor(0, QColor('lightblue'))
1056 elif address in self.wallet.prioritized_addresses:
1057 item.setBackgroundColor(0, QColor('lightgreen'))
1060 def update_receive_tab(self):
1061 l = self.receive_list
1064 l.setColumnHidden(2, not self.expert_mode)
1065 l.setColumnHidden(3, not self.expert_mode)
1066 if not self.expert_mode:
1067 width = self.column_widths['receive'][0][0]
1068 l.setColumnWidth(0, width)
1070 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1071 l.setColumnWidth(i, width)
1074 for k, account in self.wallet.accounts.items():
1075 name = account.get('name',str(k))
1076 c,u = self.wallet.get_account_balance(k)
1077 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1078 l.addTopLevelItem(account_item)
1079 account_item.setExpanded(True)
1082 for is_change in [0,1]:
1083 name = "Receiving" if not is_change else "Change"
1084 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1085 account_item.addChild(seq_item)
1086 if not is_change: seq_item.setExpanded(True)
1090 for address in account[is_change]:
1091 h = self.wallet.history.get(address,[])
1096 if gap > self.wallet.gap_limit:
1101 num_tx = '*' if h == ['*'] else "%d"%len(h)
1102 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1103 self.update_receive_item(item)
1105 item.setBackgroundColor(1, QColor('red'))
1106 seq_item.addChild(item)
1108 if self.wallet.imported_keys:
1109 c,u = self.wallet.get_imported_balance()
1110 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1111 l.addTopLevelItem(account_item)
1112 account_item.setExpanded(True)
1113 for address in self.wallet.imported_keys.keys():
1114 item = QTreeWidgetItem( [ address, '', '', ''] )
1115 self.update_receive_item(item)
1116 account_item.addChild(item)
1119 # we use column 1 because column 0 may be hidden
1120 l.setCurrentItem(l.topLevelItem(0),1)
1122 def show_contact_details(self, m):
1123 a = self.wallet.aliases.get(m)
1125 if a[0] in self.wallet.authorities.keys():
1126 s = self.wallet.authorities.get(a[0])
1129 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1130 QMessageBox.information(self, 'Alias', msg, 'OK')
1132 def update_contacts_tab(self):
1134 l = self.contacts_list
1138 for alias, v in self.wallet.aliases.items():
1140 alias_targets.append(target)
1141 item = QTreeWidgetItem( [ target, alias, '-'] )
1142 item.setBackgroundColor(0, QColor('lightgray'))
1143 l.addTopLevelItem(item)
1145 for address in self.wallet.addressbook:
1146 if address in alias_targets: continue
1147 label = self.wallet.labels.get(address,'')
1149 for tx in self.wallet.transactions.values():
1150 if address in map(lambda x: x[0], tx.outputs): n += 1
1152 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1153 item.setFont(0, QFont(MONOSPACE_FONT))
1154 l.addTopLevelItem(item)
1156 l.setCurrentItem(l.topLevelItem(0))
1159 def create_console_tab(self):
1160 from qt_console import Console
1161 from electrum import util, bitcoin, commands
1162 self.console = console = Console()
1163 self.console.history = self.config.get("console-history",[])
1164 self.console.history_index = len(self.console.history)
1166 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1167 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1169 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1171 def mkfunc(f, method):
1172 return lambda *args: apply( f, (method, args, self.password_dialog ))
1174 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1175 methods[m] = mkfunc(c._run, m)
1177 console.updateNamespace(methods)
1181 def create_status_bar(self):
1182 self.status_text = ""
1184 sb.setFixedHeight(35)
1185 qtVersion = qVersion()
1187 update_notification = UpdateLabel(self.config)
1188 if(update_notification.new_version):
1189 sb.addPermanentWidget(update_notification)
1191 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1192 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1193 if self.wallet.seed:
1194 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1195 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1196 if self.wallet.seed:
1197 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1198 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1199 sb.addPermanentWidget( self.status_button )
1201 self.setStatusBar(sb)
1205 self.config.set_key('gui', 'lite', True)
1208 self.lite.mini.show()
1210 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1211 self.lite.main(None)
1213 def new_contact_dialog(self):
1214 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1215 address = unicode(text)
1217 if is_valid(address):
1218 self.wallet.addressbook.append(address)
1220 self.update_contacts_tab()
1221 self.update_history_tab()
1222 self.update_completions()
1224 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1226 def show_master_public_key(self):
1227 dialog = QDialog(None)
1229 dialog.setWindowTitle(_("Master Public Key"))
1231 main_text = QTextEdit()
1232 main_text.setText(self.wallet.get_master_public_key())
1233 main_text.setReadOnly(True)
1234 main_text.setMaximumHeight(170)
1235 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1237 ok_button = QPushButton(_("OK"))
1238 ok_button.setDefault(True)
1239 ok_button.clicked.connect(dialog.accept)
1241 main_layout = QGridLayout()
1242 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1244 main_layout.addWidget(main_text, 1, 0)
1245 main_layout.addWidget(qrw, 1, 1 )
1247 vbox = QVBoxLayout()
1248 vbox.addLayout(main_layout)
1249 hbox = QHBoxLayout()
1251 hbox.addWidget(ok_button)
1252 vbox.addLayout(hbox)
1254 dialog.setLayout(vbox)
1259 def show_seed_dialog(self, wallet, parent=None):
1261 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1264 if wallet.use_encryption:
1265 password = parent.password_dialog()
1272 seed = wallet.decode_seed(password)
1274 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1277 self.show_seed(seed)
1280 def show_seed(self, seed):
1281 dialog = QDialog(None)
1283 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1285 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1287 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1289 seed_text = QTextEdit(brainwallet)
1290 seed_text.setReadOnly(True)
1291 seed_text.setMaximumHeight(130)
1293 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1294 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1295 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1296 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1297 label2 = QLabel(msg2)
1298 label2.setWordWrap(True)
1301 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1302 logo.setMaximumWidth(60)
1304 qrw = QRCodeWidget(seed, 4)
1306 ok_button = QPushButton(_("OK"))
1307 ok_button.setDefault(True)
1308 ok_button.clicked.connect(dialog.accept)
1310 grid = QGridLayout()
1311 #main_layout.addWidget(logo, 0, 0)
1313 grid.addWidget(logo, 0, 0)
1314 grid.addWidget(label1, 0, 1)
1316 grid.addWidget(seed_text, 1, 0, 1, 2)
1318 grid.addWidget(qrw, 0, 2, 2, 1)
1320 vbox = QVBoxLayout()
1321 vbox.addLayout(grid)
1322 vbox.addWidget(label2)
1324 hbox = QHBoxLayout()
1326 hbox.addWidget(ok_button)
1327 vbox.addLayout(hbox)
1329 dialog.setLayout(vbox)
1333 def show_qrcode(data, title = "QR code"):
1337 d.setWindowTitle(title)
1338 d.setMinimumSize(270, 300)
1339 vbox = QVBoxLayout()
1340 qrw = QRCodeWidget(data)
1341 vbox.addWidget(qrw, 1)
1342 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1343 hbox = QHBoxLayout()
1347 filename = "qrcode.bmp"
1348 bmp.save_qrcode(qrw.qr, filename)
1349 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1351 b = QPushButton(_("Save"))
1353 b.clicked.connect(print_qr)
1355 b = QPushButton(_("Close"))
1357 b.clicked.connect(d.accept)
1360 vbox.addLayout(hbox)
1364 def view_private_key(self,address):
1365 if not address: return
1366 if self.wallet.use_encryption:
1367 password = self.password_dialog()
1374 pk = self.wallet.get_private_key(address, password)
1375 except BaseException, e:
1376 self.show_message(str(e))
1379 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1382 def sign_message(self,address):
1383 if not address: return
1386 d.setWindowTitle(_('Sign Message'))
1387 d.setMinimumSize(410, 290)
1389 tab_widget = QTabWidget()
1391 layout = QGridLayout(tab)
1393 sign_address = QLineEdit()
1395 sign_address.setText(address)
1396 layout.addWidget(QLabel(_('Address')), 1, 0)
1397 layout.addWidget(sign_address, 1, 1)
1399 sign_message = QTextEdit()
1400 layout.addWidget(QLabel(_('Message')), 2, 0)
1401 layout.addWidget(sign_message, 2, 1)
1402 layout.setRowStretch(2,3)
1404 sign_signature = QTextEdit()
1405 layout.addWidget(QLabel(_('Signature')), 3, 0)
1406 layout.addWidget(sign_signature, 3, 1)
1407 layout.setRowStretch(3,1)
1410 if self.wallet.use_encryption:
1411 password = self.password_dialog()
1418 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1419 sign_signature.setText(signature)
1420 except BaseException, e:
1421 self.show_message(str(e))
1424 hbox = QHBoxLayout()
1425 b = QPushButton(_("Sign"))
1427 b.clicked.connect(do_sign)
1428 b = QPushButton(_("Close"))
1429 b.clicked.connect(d.accept)
1431 layout.addLayout(hbox, 4, 1)
1432 tab_widget.addTab(tab, _("Sign"))
1436 layout = QGridLayout(tab)
1438 verify_address = QLineEdit()
1439 layout.addWidget(QLabel(_('Address')), 1, 0)
1440 layout.addWidget(verify_address, 1, 1)
1442 verify_message = QTextEdit()
1443 layout.addWidget(QLabel(_('Message')), 2, 0)
1444 layout.addWidget(verify_message, 2, 1)
1445 layout.setRowStretch(2,3)
1447 verify_signature = QTextEdit()
1448 layout.addWidget(QLabel(_('Signature')), 3, 0)
1449 layout.addWidget(verify_signature, 3, 1)
1450 layout.setRowStretch(3,1)
1454 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1455 self.show_message(_("Signature verified"))
1456 except BaseException, e:
1457 self.show_message(str(e))
1460 hbox = QHBoxLayout()
1461 b = QPushButton(_("Verify"))
1462 b.clicked.connect(do_verify)
1464 b = QPushButton(_("Close"))
1465 b.clicked.connect(d.accept)
1467 layout.addLayout(hbox, 4, 1)
1468 tab_widget.addTab(tab, _("Verify"))
1470 vbox = QVBoxLayout()
1471 vbox.addWidget(tab_widget)
1478 def question(self, msg):
1479 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1481 def show_message(self, msg):
1482 QMessageBox.information(self, _('Message'), msg, _('OK'))
1484 def password_dialog(self ):
1491 vbox = QVBoxLayout()
1492 msg = _('Please enter your password')
1493 vbox.addWidget(QLabel(msg))
1495 grid = QGridLayout()
1497 grid.addWidget(QLabel(_('Password')), 1, 0)
1498 grid.addWidget(pw, 1, 1)
1499 vbox.addLayout(grid)
1501 vbox.addLayout(ok_cancel_buttons(d))
1504 if not d.exec_(): return
1505 return unicode(pw.text())
1512 def change_password_dialog( wallet, parent=None ):
1515 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1523 new_pw = QLineEdit()
1524 new_pw.setEchoMode(2)
1525 conf_pw = QLineEdit()
1526 conf_pw.setEchoMode(2)
1528 vbox = QVBoxLayout()
1530 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1531 +_('To disable wallet encryption, enter an empty new password.')) \
1532 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1534 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1535 +_("Leave these fields empty if you want to disable encryption.")
1536 vbox.addWidget(QLabel(msg))
1538 grid = QGridLayout()
1541 if wallet.use_encryption:
1542 grid.addWidget(QLabel(_('Password')), 1, 0)
1543 grid.addWidget(pw, 1, 1)
1545 grid.addWidget(QLabel(_('New Password')), 2, 0)
1546 grid.addWidget(new_pw, 2, 1)
1548 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1549 grid.addWidget(conf_pw, 3, 1)
1550 vbox.addLayout(grid)
1552 vbox.addLayout(ok_cancel_buttons(d))
1555 if not d.exec_(): return
1557 password = unicode(pw.text()) if wallet.use_encryption else None
1558 new_password = unicode(new_pw.text())
1559 new_password2 = unicode(conf_pw.text())
1562 seed = wallet.decode_seed(password)
1564 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1567 if new_password != new_password2:
1568 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1569 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1571 wallet.update_password(seed, password, new_password)
1574 def seed_dialog(wallet, parent=None):
1578 vbox = QVBoxLayout()
1579 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1580 vbox.addWidget(QLabel(msg))
1582 grid = QGridLayout()
1585 seed_e = QLineEdit()
1586 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1587 grid.addWidget(seed_e, 1, 1)
1591 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1592 grid.addWidget(gap_e, 2, 1)
1593 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1594 vbox.addLayout(grid)
1596 vbox.addLayout(ok_cancel_buttons(d))
1599 if not d.exec_(): return
1602 gap = int(unicode(gap_e.text()))
1604 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1608 seed = str(seed_e.text())
1611 print_error("Warning: Not hex, trying decode")
1613 seed = mnemonic.mn_decode( seed.split(' ') )
1615 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1619 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1624 def generate_transaction_information_widget(self, tx):
1625 tabs = QTabWidget(self)
1628 grid_ui = QGridLayout(tab1)
1629 grid_ui.setColumnStretch(0,1)
1630 tabs.addTab(tab1, _('Outputs') )
1632 tree_widget = MyTreeWidget(self)
1633 tree_widget.setColumnCount(2)
1634 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1635 tree_widget.setColumnWidth(0, 300)
1636 tree_widget.setColumnWidth(1, 50)
1638 for output in tx.d["outputs"]:
1639 item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
1640 tree_widget.addTopLevelItem(item)
1642 tree_widget.setMaximumHeight(100)
1644 grid_ui.addWidget(tree_widget)
1647 grid_ui = QGridLayout(tab2)
1648 grid_ui.setColumnStretch(0,1)
1649 tabs.addTab(tab2, _('Inputs') )
1651 tree_widget = MyTreeWidget(self)
1652 tree_widget.setColumnCount(2)
1653 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1655 for input_line in tx.inputs:
1656 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1657 tree_widget.addTopLevelItem(item)
1659 tree_widget.setMaximumHeight(100)
1661 grid_ui.addWidget(tree_widget)
1665 def tx_dict_from_text(self, txt):
1667 tx_dict = json.loads(str(txt))
1668 assert "hex" in tx_dict.keys()
1669 assert "complete" in tx_dict.keys()
1670 if not tx_dict["complete"]:
1671 assert "input_info" in tx_dict.keys()
1673 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1678 def read_tx_from_file(self):
1679 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1683 with open(fileName, "r") as f:
1684 file_content = f.read()
1685 except (ValueError, IOError, os.error), reason:
1686 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1688 return self.tx_dict_from_text(file_content)
1691 def sign_raw_transaction(self, tx, input_info):
1692 if self.wallet.use_encryption:
1693 password = self.password_dialog()
1700 self.wallet.signrawtransaction(tx, input_info, [], password)
1702 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1704 with open(fileName, "w+") as f:
1705 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1706 self.show_message(_("Transaction saved succesfully"))
1707 except BaseException, e:
1708 self.show_message(str(e))
1711 def create_sign_transaction_window(self, tx_dict):
1712 tx = Transaction(tx_dict["hex"])
1714 dialog = QDialog(self)
1715 dialog.setMinimumWidth(500)
1716 dialog.setWindowTitle(_('Sign unsigned transaction'))
1719 vbox = QVBoxLayout()
1720 dialog.setLayout(vbox)
1721 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1723 if tx_dict["complete"] == True:
1724 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1726 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1727 vbox.addLayout(ok_cancel_buttons(dialog))
1728 input_info = json.loads(tx_dict["input_info"])
1731 self.sign_raw_transaction(tx, input_info)
1735 def do_sign_from_text(self):
1736 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1739 tx_dict = self.tx_dict_from_text(unicode(txt))
1741 self.create_sign_transaction_window(tx_dict)
1744 def do_sign_from_file(self):
1745 tx_dict = self.read_tx_from_file()
1747 self.create_sign_transaction_window(tx_dict)
1750 def send_raw_transaction(self, raw_tx):
1751 result, result_message = self.wallet.sendtx( raw_tx )
1753 self.show_message("Transaction succesfully sent: %s" % (result_message))
1755 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1758 def create_send_transaction_window(self, tx_dict):
1759 tx = Transaction(tx_dict["hex"])
1761 dialog = QDialog(self)
1762 dialog.setMinimumWidth(500)
1763 dialog.setWindowTitle(_('Send raw transaction'))
1766 vbox = QVBoxLayout()
1767 dialog.setLayout(vbox)
1768 vbox.addWidget( self.generate_transaction_information_widget(tx))
1770 if tx_dict["complete"] == False:
1771 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1773 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1774 vbox.addLayout(ok_cancel_buttons(dialog))
1777 self.send_raw_transaction(tx_dict["hex"])
1780 def do_send_from_file(self):
1781 tx_dict = self.read_tx_from_file()
1783 self.create_send_transaction_window(tx_dict)
1786 def do_send_from_text(self):
1787 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1790 tx_dict = self.tx_dict_from_text(unicode(txt))
1792 self.create_send_transaction_window(tx_dict)
1795 def do_export_privkeys(self):
1796 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.")))
1798 if self.wallet.use_encryption:
1799 password = self.password_dialog()
1805 select_export = _('Select file to export your private keys to')
1806 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1808 with open(fileName, "w+") as csvfile:
1809 transaction = csv.writer(csvfile)
1810 transaction.writerow(["address", "private_key"])
1813 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1814 transaction.writerow(["%34s"%addr,pk])
1816 self.show_message(_("Private keys exported."))
1818 except (IOError, os.error), reason:
1819 export_error_label = _("Electrum was unable to produce a private key-export.")
1820 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1822 except BaseException, e:
1823 self.show_message(str(e))
1827 def do_import_labels(self):
1828 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1829 if not labelsFile: return
1831 f = open(labelsFile, 'r')
1834 for key, value in json.loads(data).items():
1835 self.wallet.labels[key] = value
1837 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1838 except (IOError, os.error), reason:
1839 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1843 def do_export_labels(self):
1844 labels = self.wallet.labels
1846 labelsFile = util.user_dir() + '/labels.dat'
1847 f = open(labelsFile, 'w+')
1848 json.dump(labels, f)
1850 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1851 except (IOError, os.error), reason:
1852 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1854 def do_export_history(self):
1855 from gui_lite import csv_transaction
1856 csv_transaction(self.wallet)
1858 def do_import_privkey(self):
1859 if not self.wallet.imported_keys:
1860 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1861 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1862 + _('Are you sure you understand what you are doing?'), 3, 4)
1865 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1867 sec = str(text).strip()
1868 if self.wallet.use_encryption:
1869 password = self.password_dialog()
1875 addr = self.wallet.import_key(sec, password)
1877 QMessageBox.critical(None, _("Unable to import key"), "error")
1879 QMessageBox.information(None, _("Key imported"), addr)
1880 self.update_receive_tab()
1881 self.update_history_tab()
1882 except BaseException as e:
1883 QMessageBox.critical(None, _("Unable to import key"), str(e))
1885 def settings_dialog(self):
1887 d.setWindowTitle(_('Electrum Settings'))
1889 vbox = QVBoxLayout()
1891 tabs = QTabWidget(self)
1892 vbox.addWidget(tabs)
1895 grid_ui = QGridLayout(tab1)
1896 grid_ui.setColumnStretch(0,1)
1897 tabs.addTab(tab1, _('Display') )
1899 nz_label = QLabel(_('Display zeros'))
1900 grid_ui.addWidget(nz_label, 3, 0)
1902 nz_e.setText("%d"% self.wallet.num_zeros)
1903 grid_ui.addWidget(nz_e, 3, 1)
1904 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1905 grid_ui.addWidget(HelpButton(msg), 3, 2)
1906 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1907 if not self.config.is_modifiable('num_zeros'):
1908 for w in [nz_e, nz_label]: w.setEnabled(False)
1910 lang_label=QLabel(_('Language') + ':')
1911 grid_ui.addWidget(lang_label , 8, 0)
1912 lang_combo = QComboBox()
1913 from i18n import languages
1914 lang_combo.addItems(languages.values())
1916 index = languages.keys().index(self.config.get("language",''))
1919 lang_combo.setCurrentIndex(index)
1920 grid_ui.addWidget(lang_combo, 8, 1)
1921 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1922 if not self.config.is_modifiable('language'):
1923 for w in [lang_combo, lang_label]: w.setEnabled(False)
1925 currencies = self.exchanger.get_currencies()
1926 currencies.insert(0, "None")
1928 cur_label=QLabel(_('Currency') + ':')
1929 grid_ui.addWidget(cur_label , 9, 0)
1930 cur_combo = QComboBox()
1931 cur_combo.addItems(currencies)
1933 index = currencies.index(self.config.get('currency', "None"))
1936 cur_combo.setCurrentIndex(index)
1937 grid_ui.addWidget(cur_combo, 9, 1)
1938 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1940 view_label=QLabel(_('Receive Tab') + ':')
1941 grid_ui.addWidget(view_label , 10, 0)
1942 view_combo = QComboBox()
1943 view_combo.addItems([_('Simple'), _('Advanced')])
1944 view_combo.setCurrentIndex(self.expert_mode)
1945 grid_ui.addWidget(view_combo, 10, 1)
1946 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1947 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1948 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1950 grid_ui.addWidget(HelpButton(hh), 10, 2)
1954 grid_wallet = QGridLayout(tab2)
1955 grid_wallet.setColumnStretch(0,1)
1956 tabs.addTab(tab2, _('Wallet') )
1958 fee_label = QLabel(_('Transaction fee'))
1959 grid_wallet.addWidget(fee_label, 0, 0)
1961 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1962 grid_wallet.addWidget(fee_e, 0, 1)
1963 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1964 + _('Recommended value') + ': 0.001'
1965 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1966 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1967 if not self.config.is_modifiable('fee'):
1968 for w in [fee_e, fee_label]: w.setEnabled(False)
1970 usechange_label = QLabel(_('Use change addresses'))
1971 grid_wallet.addWidget(usechange_label, 1, 0)
1972 usechange_combo = QComboBox()
1973 usechange_combo.addItems([_('Yes'), _('No')])
1974 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1975 grid_wallet.addWidget(usechange_combo, 1, 1)
1976 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1977 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1979 gap_label = QLabel(_('Gap limit'))
1980 grid_wallet.addWidget(gap_label, 2, 0)
1982 gap_e.setText("%d"% self.wallet.gap_limit)
1983 grid_wallet.addWidget(gap_e, 2, 1)
1984 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1985 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1986 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1987 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1988 + _('Warning') + ': ' \
1989 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1990 + _('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'
1991 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1992 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1993 if not self.config.is_modifiable('gap_limit'):
1994 for w in [gap_e, gap_label]: w.setEnabled(False)
1996 grid_wallet.setRowStretch(3,1)
2001 grid_io = QGridLayout(tab3)
2002 grid_io.setColumnStretch(0,1)
2003 tabs.addTab(tab3, _('Import/Export') )
2005 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2006 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2007 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2008 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2010 grid_io.addWidget(QLabel(_('History')), 2, 0)
2011 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2012 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2014 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2016 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2017 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2018 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2020 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2021 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2022 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2023 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2024 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2026 grid_io.setRowStretch(4,1)
2029 grid_raw = QGridLayout(tab4)
2030 grid_raw.setColumnStretch(0,1)
2031 tabs.addTab(tab4, _('Raw tx') ) # move this to wallet tab
2033 if self.wallet.seed:
2034 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2035 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2036 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2037 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2039 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2040 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2041 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2042 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2043 grid_raw.setRowStretch(3,1)
2048 grid_plugins = QGridLayout(tab5)
2049 grid_plugins.setColumnStretch(0,1)
2050 tabs.addTab(tab5, _('Plugins') )
2051 def mk_toggle(cb, p):
2052 return lambda: cb.setChecked(p.toggle(self))
2053 for i, p in enumerate(self.plugins):
2055 name, description = p.get_info()
2056 cb = QCheckBox(name)
2057 cb.setChecked(p.is_enabled())
2058 cb.stateChanged.connect(mk_toggle(cb,p))
2059 grid_plugins.addWidget(cb, i, 0)
2060 grid_plugins.addWidget(HelpButton(description), i, 2)
2062 print_msg("Error: cannot display plugin", p)
2063 traceback.print_exc(file=sys.stdout)
2064 grid_plugins.setRowStretch(i+1,1)
2066 vbox.addLayout(ok_cancel_buttons(d))
2070 if not d.exec_(): return
2072 fee = unicode(fee_e.text())
2074 fee = int( 100000000 * Decimal(fee) )
2076 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2079 if self.wallet.fee != fee:
2080 self.wallet.fee = fee
2083 nz = unicode(nz_e.text())
2088 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2091 if self.wallet.num_zeros != nz:
2092 self.wallet.num_zeros = nz
2093 self.config.set_key('num_zeros', nz, True)
2094 self.update_history_tab()
2095 self.update_receive_tab()
2097 usechange_result = usechange_combo.currentIndex() == 0
2098 if self.wallet.use_change != usechange_result:
2099 self.wallet.use_change = usechange_result
2100 self.config.set_key('use_change', self.wallet.use_change, True)
2103 n = int(gap_e.text())
2105 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2108 if self.wallet.gap_limit != n:
2109 r = self.wallet.change_gap_limit(n)
2111 self.update_receive_tab()
2112 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2114 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2116 need_restart = False
2118 lang_request = languages.keys()[lang_combo.currentIndex()]
2119 if lang_request != self.config.get('language'):
2120 self.config.set_key("language", lang_request, True)
2123 cur_request = str(currencies[cur_combo.currentIndex()])
2124 if cur_request != self.config.get('currency', "None"):
2125 self.config.set_key('currency', cur_request, True)
2126 self.update_wallet()
2129 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2131 self.receive_tab_set_mode(view_combo.currentIndex())
2135 def network_dialog(wallet, parent=None):
2136 interface = wallet.interface
2138 if interface.is_connected:
2139 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2141 status = _("Not connected")
2142 server = interface.server
2145 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2146 server = interface.server
2148 plist, servers_list = interface.get_servers_list()
2152 d.setWindowTitle(_('Server'))
2153 d.setMinimumSize(375, 20)
2155 vbox = QVBoxLayout()
2158 hbox = QHBoxLayout()
2160 l.setPixmap(QPixmap(":icons/network.png"))
2163 hbox.addWidget(QLabel(status))
2165 vbox.addLayout(hbox)
2169 grid = QGridLayout()
2171 vbox.addLayout(grid)
2174 server_protocol = QComboBox()
2175 server_host = QLineEdit()
2176 server_host.setFixedWidth(200)
2177 server_port = QLineEdit()
2178 server_port.setFixedWidth(60)
2180 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2181 protocol_letters = 'thsg'
2182 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2183 server_protocol.addItems(protocol_names)
2185 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2186 grid.addWidget(server_protocol, 0, 1)
2187 grid.addWidget(server_host, 0, 2)
2188 grid.addWidget(server_port, 0, 3)
2190 def change_protocol(p):
2191 protocol = protocol_letters[p]
2192 host = unicode(server_host.text())
2193 pp = plist.get(host,DEFAULT_PORTS)
2194 if protocol not in pp.keys():
2195 protocol = pp.keys()[0]
2197 server_host.setText( host )
2198 server_port.setText( port )
2200 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2202 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2203 servers_list_widget = QTreeWidget(parent)
2204 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2205 servers_list_widget.setMaximumHeight(150)
2206 servers_list_widget.setColumnWidth(0, 240)
2207 for _host in servers_list.keys():
2208 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2209 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2211 def change_server(host, protocol=None):
2212 pp = plist.get(host,DEFAULT_PORTS)
2214 port = pp.get(protocol)
2215 if not port: protocol = None
2218 if 't' in pp.keys():
2220 port = pp.get(protocol)
2222 protocol = pp.keys()[0]
2223 port = pp.get(protocol)
2225 server_host.setText( host )
2226 server_port.setText( port )
2227 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2229 if not plist: return
2230 for p in protocol_letters:
2231 i = protocol_letters.index(p)
2232 j = server_protocol.model().index(i,0)
2233 if p not in pp.keys():
2234 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2236 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2240 host, port, protocol = server.split(':')
2241 change_server(host,protocol)
2243 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2244 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2246 if not wallet.config.is_modifiable('server'):
2247 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2250 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2251 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2252 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2253 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2256 proxy_mode = QComboBox()
2257 proxy_host = QLineEdit()
2258 proxy_host.setFixedWidth(200)
2259 proxy_port = QLineEdit()
2260 proxy_port.setFixedWidth(60)
2261 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2263 def check_for_disable(index = False):
2264 if proxy_mode.currentText() != 'NONE':
2265 proxy_host.setEnabled(True)
2266 proxy_port.setEnabled(True)
2268 proxy_host.setEnabled(False)
2269 proxy_port.setEnabled(False)
2272 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2274 if not wallet.config.is_modifiable('proxy'):
2275 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2277 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2278 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2279 proxy_host.setText(proxy_config.get("host"))
2280 proxy_port.setText(proxy_config.get("port"))
2282 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2283 grid.addWidget(proxy_mode, 2, 1)
2284 grid.addWidget(proxy_host, 2, 2)
2285 grid.addWidget(proxy_port, 2, 3)
2288 vbox.addLayout(ok_cancel_buttons(d))
2291 if not d.exec_(): return
2293 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2294 if proxy_mode.currentText() != 'NONE':
2295 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2299 wallet.config.set_key("proxy", proxy, True)
2300 wallet.config.set_key("server", server, True)
2301 interface.set_server(server, proxy)
2302 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2305 def closeEvent(self, event):
2307 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2308 self.save_column_widths()
2309 self.config.set_key("column-widths", self.column_widths, True)
2310 self.config.set_key("console-history",self.console.history[-50:])
2316 def __init__(self, wallet, config, app=None):
2317 self.wallet = wallet
2318 self.config = config
2320 self.app = QApplication(sys.argv)
2323 def restore_or_create(self):
2324 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2325 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2326 if r==2: return None
2327 return 'restore' if r==1 else 'create'
2329 def seed_dialog(self):
2330 return ElectrumWindow.seed_dialog( self.wallet )
2332 def network_dialog(self):
2333 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2336 def show_seed(self):
2337 ElectrumWindow.show_seed_dialog(self.wallet)
2340 def password_dialog(self):
2341 ElectrumWindow.change_password_dialog(self.wallet)
2344 def restore_wallet(self):
2345 wallet = self.wallet
2346 # wait until we are connected, because the user might have selected another server
2347 if not wallet.interface.is_connected:
2348 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2349 waiting_dialog(waiting)
2351 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2352 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2354 wallet.set_up_to_date(False)
2355 wallet.interface.poke('synchronizer')
2356 waiting_dialog(waiting)
2357 if wallet.is_found():
2358 print_error( "Recovery successful" )
2360 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2367 w = ElectrumWindow(self.wallet, self.config)
2368 if url: w.set_url(url)