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, DEFAULT_PORTS
35 from electrum.bitcoin import MIN_RELAY_TX_FEE
40 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
42 from electrum.wallet import format_satoshis
43 from electrum.bitcoin import Transaction, is_valid
44 from electrum import mnemonic
45 from electrum import util, bitcoin, commands
47 import bmp, pyqrnative
50 from decimal import Decimal
58 if platform.system() == 'Windows':
59 MONOSPACE_FONT = 'Lucida Console'
60 elif platform.system() == 'Darwin':
61 MONOSPACE_FONT = 'Monaco'
63 MONOSPACE_FONT = 'monospace'
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:
228 def waiting_dialog(f):
234 w.setWindowTitle('Electrum')
244 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
249 def ok_cancel_buttons(dialog, ok_label=_("OK") ):
252 b = QPushButton(_("Cancel"))
254 b.clicked.connect(dialog.reject)
255 b = QPushButton(ok_label)
257 b.clicked.connect(dialog.accept)
262 def text_dialog(parent, title, label, ok_label):
263 dialog = QDialog(parent)
264 dialog.setMinimumWidth(500)
265 dialog.setWindowTitle(title)
269 l.addWidget(QLabel(label))
272 l.addLayout(ok_cancel_buttons(dialog, ok_label))
274 return unicode(txt.toPlainText())
278 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
279 "receive":[[370],[370,200,130]] }
281 class ElectrumWindow(QMainWindow):
283 def __init__(self, wallet, config):
284 QMainWindow.__init__(self)
288 self.current_account = self.config.get("current_account", None)
291 self.create_status_bar()
293 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
294 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
295 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
296 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
298 self.expert_mode = config.get('classic_expert_mode', False)
300 set_language(config.get('language'))
302 self.funds_error = False
303 self.completions = QStringListModel()
305 self.tabs = tabs = QTabWidget(self)
306 self.column_widths = self.config.get("column_widths", default_column_widths )
307 tabs.addTab(self.create_history_tab(), _('History') )
308 tabs.addTab(self.create_send_tab(), _('Send') )
309 tabs.addTab(self.create_receive_tab(), _('Receive') )
310 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
311 tabs.addTab(self.create_console_tab(), _('Console') )
312 tabs.setMinimumSize(600, 400)
313 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
314 self.setCentralWidget(tabs)
316 g = self.config.get("winpos-qt",[100, 100, 840, 400])
317 self.setGeometry(g[0], g[1], g[2], g[3])
318 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
319 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
320 self.setWindowTitle( title )
322 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
323 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
324 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
325 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
327 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
328 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
329 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
330 self.history_list.setFocus(True)
332 self.exchanger = exchange_rate.Exchanger(self)
333 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
335 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
336 if platform.system() == 'Windows':
337 n = 3 if self.wallet.seed else 2
338 tabs.setCurrentIndex (n)
339 tabs.setCurrentIndex (0)
341 # set initial message
342 self.console.showMessage(self.wallet.interface.banner)
344 # plugins that need to change the GUI do it here
345 self.run_hook('init_gui')
349 def init_plugins(self):
350 import imp, pkgutil, __builtin__
351 if __builtin__.use_local_modules:
352 fp, pathname, description = imp.find_module('plugins')
353 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
354 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
355 imp.load_module('electrum_plugins', fp, pathname, description)
356 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
358 import electrum_plugins
359 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
360 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
365 self.plugins.append( p.Plugin(self) )
367 print_msg("Error:cannot initialize plugin",p)
368 traceback.print_exc(file=sys.stdout)
371 def run_hook(self, name, *args):
372 for p in self.plugins:
373 if not p.is_enabled():
383 def set_label(self, name, text = None):
385 old_text = self.wallet.labels.get(name)
388 self.wallet.labels[name] = text
392 self.wallet.labels.pop(name)
394 self.run_hook('set_label', name, text, changed)
398 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
399 def getOpenFileName(self, title, filter = None):
400 directory = self.config.get('io_dir', os.path.expanduser('~'))
401 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
402 if fileName and directory != os.path.dirname(fileName):
403 self.config.set_key('io_dir', os.path.dirname(fileName), True)
406 def getSaveFileName(self, title, filename, filter = None):
407 directory = self.config.get('io_dir', os.path.expanduser('~'))
408 path = os.path.join( directory, filename )
409 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
410 if fileName and directory != os.path.dirname(fileName):
411 self.config.set_key('io_dir', os.path.dirname(fileName), True)
417 QMainWindow.close(self)
418 self.run_hook('close_main_window')
420 def connect_slots(self, sender):
421 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
422 self.previous_payto_e=''
424 def timer_actions(self):
425 self.run_hook('timer_actions')
427 def update_status(self):
428 if self.wallet.interface and self.wallet.interface.is_connected:
429 if not self.wallet.up_to_date:
430 text = _("Synchronizing...")
431 icon = QIcon(":icons/status_waiting.png")
433 c, u = self.wallet.get_account_balance(self.current_account)
434 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
435 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
436 text += self.create_quote_text(Decimal(c+u)/100000000)
437 icon = QIcon(":icons/status_connected.png")
439 text = _("Not connected")
440 icon = QIcon(":icons/status_disconnected.png")
442 self.status_text = text
443 self.statusBar().showMessage(text)
444 self.status_button.setIcon( icon )
446 def update_wallet(self):
448 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
449 self.update_history_tab()
450 self.update_receive_tab()
451 self.update_contacts_tab()
452 self.update_completions()
455 def create_quote_text(self, btc_balance):
456 quote_currency = self.config.get("currency", "None")
457 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
458 if quote_balance is None:
461 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
464 def create_history_tab(self):
465 self.history_list = l = MyTreeWidget(self)
467 for i,width in enumerate(self.column_widths['history']):
468 l.setColumnWidth(i, width)
469 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
470 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
471 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
473 l.setContextMenuPolicy(Qt.CustomContextMenu)
474 l.customContextMenuRequested.connect(self.create_history_menu)
478 def create_history_menu(self, position):
479 self.history_list.selectedIndexes()
480 item = self.history_list.currentItem()
482 tx_hash = str(item.data(0, Qt.UserRole).toString())
483 if not tx_hash: return
485 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
486 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
487 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
488 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
491 def show_tx_details(self, tx):
492 dialog = QDialog(self)
494 dialog.setWindowTitle(_("Transaction Details"))
496 dialog.setLayout(vbox)
497 dialog.setMinimumSize(600,300)
500 if tx_hash in self.wallet.transactions.keys():
501 is_mine, v, fee = self.wallet.get_tx_value(tx)
502 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
504 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
510 vbox.addWidget(QLabel("Transaction ID:"))
511 e = QLineEdit(tx_hash)
515 vbox.addWidget(QLabel("Date: %s"%time_str))
516 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
519 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
520 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
522 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
523 vbox.addWidget(QLabel("Transaction fee: unknown"))
525 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
527 vbox.addWidget( self.generate_transaction_information_widget(tx) )
529 ok_button = QPushButton(_("Close"))
530 ok_button.setDefault(True)
531 ok_button.clicked.connect(dialog.accept)
535 hbox.addWidget(ok_button)
539 def tx_label_clicked(self, item, column):
540 if column==2 and item.isSelected():
542 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
543 self.history_list.editItem( item, column )
544 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
547 def tx_label_changed(self, item, column):
551 tx_hash = str(item.data(0, Qt.UserRole).toString())
552 tx = self.wallet.transactions.get(tx_hash)
553 text = unicode( item.text(2) )
554 self.set_label(tx_hash, text)
556 item.setForeground(2, QBrush(QColor('black')))
558 text = self.wallet.get_default_label(tx_hash)
559 item.setText(2, text)
560 item.setForeground(2, QBrush(QColor('gray')))
564 def edit_label(self, is_recv):
565 l = self.receive_list if is_recv else self.contacts_list
566 item = l.currentItem()
567 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
568 l.editItem( item, 1 )
569 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
573 def address_label_clicked(self, item, column, l, column_addr, column_label):
574 if column == column_label and item.isSelected():
575 is_editable = item.data(0, 32).toBool()
578 addr = unicode( item.text(column_addr) )
579 label = unicode( item.text(column_label) )
580 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
581 l.editItem( item, column )
582 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
585 def address_label_changed(self, item, column, l, column_addr, column_label):
586 if column == column_label:
587 addr = unicode( item.text(column_addr) )
588 text = unicode( item.text(column_label) )
589 is_editable = item.data(0, 32).toBool()
593 changed = self.set_label(addr, text)
595 self.update_history_tab()
596 self.update_completions()
598 self.current_item_changed(item)
600 self.run_hook('item_changed', item, column)
603 def current_item_changed(self, a):
604 self.run_hook('current_item_changed', a)
608 def update_history_tab(self):
610 self.history_list.clear()
611 for item in self.wallet.get_tx_history(self.current_account):
612 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
615 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
621 icon = QIcon(":icons/unconfirmed.png")
623 icon = QIcon(":icons/clock%d.png"%conf)
625 icon = QIcon(":icons/confirmed.png")
628 icon = QIcon(":icons/unconfirmed.png")
630 if value is not None:
631 v_str = format_satoshis(value, True, self.wallet.num_zeros)
635 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
638 label, is_default_label = self.wallet.get_label(tx_hash)
640 label = _('Pruned transaction outputs')
641 is_default_label = False
643 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
644 item.setFont(2, QFont(MONOSPACE_FONT))
645 item.setFont(3, QFont(MONOSPACE_FONT))
646 item.setFont(4, QFont(MONOSPACE_FONT))
648 item.setForeground(3, QBrush(QColor("#BC1E1E")))
650 item.setData(0, Qt.UserRole, tx_hash)
651 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
653 item.setForeground(2, QBrush(QColor('grey')))
655 item.setIcon(0, icon)
656 self.history_list.insertTopLevelItem(0,item)
659 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
662 def create_send_tab(self):
667 grid.setColumnMinimumWidth(3,300)
668 grid.setColumnStretch(5,1)
671 self.payto_e = QLineEdit()
672 grid.addWidget(QLabel(_('Pay to')), 1, 0)
673 grid.addWidget(self.payto_e, 1, 1, 1, 3)
675 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)
677 completer = QCompleter()
678 completer.setCaseSensitivity(False)
679 self.payto_e.setCompleter(completer)
680 completer.setModel(self.completions)
682 self.message_e = QLineEdit()
683 grid.addWidget(QLabel(_('Description')), 2, 0)
684 grid.addWidget(self.message_e, 2, 1, 1, 3)
685 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)
687 self.amount_e = QLineEdit()
688 grid.addWidget(QLabel(_('Amount')), 3, 0)
689 grid.addWidget(self.amount_e, 3, 1, 1, 2)
690 grid.addWidget(HelpButton(
691 _('Amount to be sent.') + '\n\n' \
692 + _('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.')
693 + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
695 self.fee_e = QLineEdit()
696 grid.addWidget(QLabel(_('Fee')), 4, 0)
697 grid.addWidget(self.fee_e, 4, 1, 1, 2)
698 grid.addWidget(HelpButton(
699 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
700 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
701 + _('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)
704 b = EnterButton(_("Send"), self.do_send)
706 b = EnterButton(_("Create unsigned transaction"), self.do_send)
707 grid.addWidget(b, 6, 1)
709 b = EnterButton(_("Clear"),self.do_clear)
710 grid.addWidget(b, 6, 2)
712 self.payto_sig = QLabel('')
713 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
715 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
716 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
725 def entry_changed( is_fee ):
726 self.funds_error = False
728 if self.amount_e.text() == '!':
729 c, u = self.wallet.get_account_balance(self.current_account)
730 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
731 fee = self.wallet.estimated_fee(inputs)
733 self.amount_e.setText( str( Decimal( amount ) / 100000000 ) )
734 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
737 amount = numbify(self.amount_e)
738 fee = numbify(self.fee_e)
739 if not is_fee: fee = None
742 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
744 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
747 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
748 text = self.status_text
751 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
752 self.funds_error = True
753 text = _( "Not enough funds" )
755 self.statusBar().showMessage(text)
756 self.amount_e.setPalette(palette)
757 self.fee_e.setPalette(palette)
759 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
760 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
762 self.run_hook('create_send_tab', grid)
766 def update_completions(self):
768 for addr,label in self.wallet.labels.items():
769 if addr in self.wallet.addressbook:
770 l.append( label + ' <' + addr + '>')
772 self.run_hook('update_completions', l)
773 self.completions.setStringList(l)
777 return lambda s, *args: s.do_protect(func, args)
781 def do_send(self, password):
783 label = unicode( self.message_e.text() )
784 r = unicode( self.payto_e.text() )
787 # label or alias, with address in brackets
788 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
789 to_address = m.group(2) if m else r
791 if not is_valid(to_address):
792 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
796 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
798 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
801 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
803 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
807 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
808 except BaseException, e:
809 self.show_message(str(e))
812 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
813 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
816 self.run_hook('send_tx', tx)
819 self.set_label(tx.hash(), label)
822 h = self.wallet.send_tx(tx)
823 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
824 status, msg = self.wallet.receive_tx( h )
826 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
828 self.update_contacts_tab()
830 QMessageBox.warning(self, _('Error'), msg, _('OK'))
832 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
834 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
835 with open(fileName,'w') as f:
836 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
837 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
839 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
844 def set_url(self, url):
845 address, amount, label, message, signature, identity, url = util.parse_url(url)
847 if label and self.wallet.labels.get(address) != label:
848 if self.question('Give label "%s" to address %s ?'%(label,address)):
849 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
850 self.wallet.addressbook.append(address)
851 self.set_label(address, label)
853 self.run_hook('set_url', url, self.show_message, self.question)
855 self.tabs.setCurrentIndex(1)
856 label = self.wallet.labels.get(address)
857 m_addr = label + ' <'+ address +'>' if label else address
858 self.payto_e.setText(m_addr)
860 self.message_e.setText(message)
861 self.amount_e.setText(amount)
863 self.set_frozen(self.payto_e,True)
864 self.set_frozen(self.amount_e,True)
865 self.set_frozen(self.message_e,True)
866 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
868 self.payto_sig.setVisible(False)
871 self.payto_sig.setVisible(False)
872 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
874 self.set_frozen(e,False)
876 def set_frozen(self,entry,frozen):
878 entry.setReadOnly(True)
879 entry.setFrame(False)
881 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
882 entry.setPalette(palette)
884 entry.setReadOnly(False)
887 palette.setColor(entry.backgroundRole(), QColor('white'))
888 entry.setPalette(palette)
891 def toggle_freeze(self,addr):
893 if addr in self.wallet.frozen_addresses:
894 self.wallet.unfreeze(addr)
896 self.wallet.freeze(addr)
897 self.update_receive_tab()
899 def toggle_priority(self,addr):
901 if addr in self.wallet.prioritized_addresses:
902 self.wallet.unprioritize(addr)
904 self.wallet.prioritize(addr)
905 self.update_receive_tab()
908 def create_list_tab(self, headers):
909 "generic tab creation method"
910 l = MyTreeWidget(self)
911 l.setColumnCount( len(headers) )
912 l.setHeaderLabels( headers )
922 vbox.addWidget(buttons)
927 buttons.setLayout(hbox)
932 def create_receive_tab(self):
933 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
934 l.setContextMenuPolicy(Qt.CustomContextMenu)
935 l.customContextMenuRequested.connect(self.create_receive_menu)
936 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
937 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
938 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
939 self.receive_list = l
940 self.receive_buttons_hbox = hbox
945 def receive_tab_set_mode(self, i):
946 self.save_column_widths()
947 self.expert_mode = (i == 1)
948 self.config.set_key('classic_expert_mode', self.expert_mode, True)
950 self.update_receive_tab()
953 def save_column_widths(self):
954 if not self.expert_mode:
955 widths = [ self.receive_list.columnWidth(0) ]
958 for i in range(self.receive_list.columnCount() -1):
959 widths.append(self.receive_list.columnWidth(i))
960 self.column_widths["receive"][self.expert_mode] = widths
962 self.column_widths["history"] = []
963 for i in range(self.history_list.columnCount() - 1):
964 self.column_widths["history"].append(self.history_list.columnWidth(i))
966 self.column_widths["contacts"] = []
967 for i in range(self.contacts_list.columnCount() - 1):
968 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
971 def create_contacts_tab(self):
972 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
973 l.setContextMenuPolicy(Qt.CustomContextMenu)
974 l.customContextMenuRequested.connect(self.create_contact_menu)
975 for i,width in enumerate(self.column_widths['contacts']):
976 l.setColumnWidth(i, width)
978 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
979 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
980 self.contacts_list = l
981 self.contacts_buttons_hbox = hbox
982 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
987 def delete_imported_key(self, addr):
988 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
989 self.wallet.imported_keys.pop(addr)
990 self.update_receive_tab()
991 self.update_history_tab()
995 def create_receive_menu(self, position):
996 # fixme: this function apparently has a side effect.
997 # if it is not called the menu pops up several times
998 #self.receive_list.selectedIndexes()
1000 item = self.receive_list.itemAt(position)
1002 addr = unicode(item.text(0))
1003 if not is_valid(addr):
1004 item.setExpanded(not item.isExpanded())
1007 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1008 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1009 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1010 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1011 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1012 if addr in self.wallet.imported_keys:
1013 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1015 if self.expert_mode:
1016 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1017 menu.addAction(t, lambda: self.toggle_freeze(addr))
1018 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1019 menu.addAction(t, lambda: self.toggle_priority(addr))
1021 self.run_hook('receive_menu', menu)
1022 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1025 def payto(self, addr):
1027 label = self.wallet.labels.get(addr)
1028 m_addr = label + ' <' + addr + '>' if label else addr
1029 self.tabs.setCurrentIndex(1)
1030 self.payto_e.setText(m_addr)
1031 self.amount_e.setFocus()
1034 def delete_contact(self, x):
1035 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1036 if x in self.wallet.addressbook:
1037 self.wallet.addressbook.remove(x)
1038 self.set_label(x, None)
1039 self.update_history_tab()
1040 self.update_contacts_tab()
1041 self.update_completions()
1044 def create_contact_menu(self, position):
1045 item = self.contacts_list.itemAt(position)
1047 addr = unicode(item.text(0))
1048 label = unicode(item.text(1))
1049 is_editable = item.data(0,32).toBool()
1050 payto_addr = item.data(0,33).toString()
1052 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1053 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1054 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1056 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1057 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1059 self.run_hook('create_contact_menu', menu, item)
1060 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1063 def update_receive_item(self, item):
1064 item.setFont(0, QFont(MONOSPACE_FONT))
1065 address = str(item.data(0,0).toString())
1066 label = self.wallet.labels.get(address,'')
1067 item.setData(1,0,label)
1068 item.setData(0,32, True) # is editable
1070 self.run_hook('update_receive_item', address, item)
1072 c, u = self.wallet.get_addr_balance(address)
1073 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1074 item.setData(2,0,balance)
1076 if self.expert_mode:
1077 if address in self.wallet.frozen_addresses:
1078 item.setBackgroundColor(0, QColor('lightblue'))
1079 elif address in self.wallet.prioritized_addresses:
1080 item.setBackgroundColor(0, QColor('lightgreen'))
1083 def update_receive_tab(self):
1084 l = self.receive_list
1087 l.setColumnHidden(2, not self.expert_mode)
1088 l.setColumnHidden(3, not self.expert_mode)
1089 if not self.expert_mode:
1090 width = self.column_widths['receive'][0][0]
1091 l.setColumnWidth(0, width)
1093 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1094 l.setColumnWidth(i, width)
1096 if self.current_account is None:
1097 account_items = self.wallet.accounts.items()
1098 elif self.current_account != -1:
1099 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1103 for k, account in account_items:
1104 name = account.get('name',str(k))
1105 c,u = self.wallet.get_account_balance(k)
1106 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1107 l.addTopLevelItem(account_item)
1108 account_item.setExpanded(True)
1110 for is_change in ([0,1] if self.expert_mode else [0]):
1111 if self.expert_mode:
1112 name = "Receiving" if not is_change else "Change"
1113 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1114 account_item.addChild(seq_item)
1115 if not is_change: seq_item.setExpanded(True)
1117 seq_item = account_item
1121 for address in account[is_change]:
1122 h = self.wallet.history.get(address,[])
1126 if gap > self.wallet.gap_limit:
1131 num_tx = '*' if h == ['*'] else "%d"%len(h)
1132 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1133 self.update_receive_item(item)
1135 item.setBackgroundColor(1, QColor('red'))
1136 seq_item.addChild(item)
1139 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1140 c,u = self.wallet.get_imported_balance()
1141 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1142 l.addTopLevelItem(account_item)
1143 account_item.setExpanded(True)
1144 for address in self.wallet.imported_keys.keys():
1145 item = QTreeWidgetItem( [ address, '', '', ''] )
1146 self.update_receive_item(item)
1147 account_item.addChild(item)
1150 # we use column 1 because column 0 may be hidden
1151 l.setCurrentItem(l.topLevelItem(0),1)
1154 def update_contacts_tab(self):
1156 l = self.contacts_list
1159 for address in self.wallet.addressbook:
1160 label = self.wallet.labels.get(address,'')
1161 n = self.wallet.get_num_tx(address)
1162 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1163 item.setFont(0, QFont(MONOSPACE_FONT))
1164 # 32 = label can be edited (bool)
1165 item.setData(0,32, True)
1167 item.setData(0,33, address)
1168 l.addTopLevelItem(item)
1170 self.run_hook('update_contacts_tab', l)
1171 l.setCurrentItem(l.topLevelItem(0))
1175 def create_console_tab(self):
1176 from qt_console import Console
1177 self.console = console = Console()
1178 self.console.history = self.config.get("console-history",[])
1179 self.console.history_index = len(self.console.history)
1181 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1182 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1184 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1186 def mkfunc(f, method):
1187 return lambda *args: apply( f, (method, args, self.password_dialog ))
1189 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1190 methods[m] = mkfunc(c._run, m)
1192 console.updateNamespace(methods)
1195 def change_account(self,s):
1196 if s == _("All accounts"):
1197 self.current_account = None
1199 accounts = self.wallet.get_accounts()
1200 for k, v in accounts.items():
1202 self.current_account = k
1203 self.update_history_tab()
1204 self.update_status()
1205 self.update_receive_tab()
1207 def create_status_bar(self):
1208 self.status_text = ""
1210 sb.setFixedHeight(35)
1211 qtVersion = qVersion()
1213 update_notification = UpdateLabel(self.config)
1214 if(update_notification.new_version):
1215 sb.addPermanentWidget(update_notification)
1217 accounts = self.wallet.get_accounts()
1218 if len(accounts) > 1:
1219 from_combo = QComboBox()
1220 from_combo.addItems([_("All accounts")] + accounts.values())
1221 from_combo.setCurrentIndex(0)
1222 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1223 sb.addPermanentWidget(from_combo)
1225 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1226 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1227 if self.wallet.seed:
1228 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1229 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1230 sb.addPermanentWidget( self.password_button )
1231 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1232 if self.wallet.seed:
1233 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1234 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1235 sb.addPermanentWidget( self.status_button )
1237 self.run_hook('create_status_bar', (sb,))
1239 self.setStatusBar(sb)
1243 self.config.set_key('gui', 'lite', True)
1246 self.lite.mini.show()
1248 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1249 self.lite.main(None)
1251 def new_contact_dialog(self):
1252 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1253 address = unicode(text)
1255 if is_valid(address):
1256 self.wallet.addressbook.append(address)
1258 self.update_contacts_tab()
1259 self.update_history_tab()
1260 self.update_completions()
1262 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1264 def show_master_public_key(self):
1265 dialog = QDialog(self)
1267 dialog.setWindowTitle(_("Master Public Key"))
1269 main_text = QTextEdit()
1270 main_text.setText(self.wallet.get_master_public_key())
1271 main_text.setReadOnly(True)
1272 main_text.setMaximumHeight(170)
1273 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1275 ok_button = QPushButton(_("OK"))
1276 ok_button.setDefault(True)
1277 ok_button.clicked.connect(dialog.accept)
1279 main_layout = QGridLayout()
1280 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1282 main_layout.addWidget(main_text, 1, 0)
1283 main_layout.addWidget(qrw, 1, 1 )
1285 vbox = QVBoxLayout()
1286 vbox.addLayout(main_layout)
1287 hbox = QHBoxLayout()
1289 hbox.addWidget(ok_button)
1290 vbox.addLayout(hbox)
1292 dialog.setLayout(vbox)
1297 def show_seed_dialog(self, password):
1298 if not self.wallet.seed:
1299 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1302 seed = self.wallet.decode_seed(password)
1304 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1306 self.show_seed(seed, self.wallet.imported_keys, self)
1310 def show_seed(self, seed, imported_keys, parent=None):
1311 dialog = QDialog(parent)
1313 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1315 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1317 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1319 seed_text = QTextEdit(brainwallet)
1320 seed_text.setReadOnly(True)
1321 seed_text.setMaximumHeight(130)
1323 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1324 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1325 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1326 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1328 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1329 label2 = QLabel(msg2)
1330 label2.setWordWrap(True)
1333 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1334 logo.setMaximumWidth(60)
1336 qrw = QRCodeWidget(seed)
1338 ok_button = QPushButton(_("OK"))
1339 ok_button.setDefault(True)
1340 ok_button.clicked.connect(dialog.accept)
1342 grid = QGridLayout()
1343 #main_layout.addWidget(logo, 0, 0)
1345 grid.addWidget(logo, 0, 0)
1346 grid.addWidget(label1, 0, 1)
1348 grid.addWidget(seed_text, 1, 0, 1, 2)
1350 grid.addWidget(qrw, 0, 2, 2, 1)
1352 vbox = QVBoxLayout()
1353 vbox.addLayout(grid)
1354 vbox.addWidget(label2)
1356 hbox = QHBoxLayout()
1358 hbox.addWidget(ok_button)
1359 vbox.addLayout(hbox)
1361 dialog.setLayout(vbox)
1364 def show_qrcode(self, data, title = "QR code"):
1368 d.setWindowTitle(title)
1369 d.setMinimumSize(270, 300)
1370 vbox = QVBoxLayout()
1371 qrw = QRCodeWidget(data)
1372 vbox.addWidget(qrw, 1)
1373 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1374 hbox = QHBoxLayout()
1378 filename = "qrcode.bmp"
1379 bmp.save_qrcode(qrw.qr, filename)
1380 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1382 b = QPushButton(_("Save"))
1384 b.clicked.connect(print_qr)
1386 b = QPushButton(_("Close"))
1388 b.clicked.connect(d.accept)
1391 vbox.addLayout(hbox)
1396 def do_protect(self, func, args):
1397 if self.wallet.use_encryption:
1398 password = self.password_dialog()
1404 if args != (False,):
1405 args = (self,) + args + (password,)
1407 args = (self,password)
1412 def show_private_key(self, address, password):
1413 if not address: return
1415 pk = self.wallet.get_private_key(address, password)
1416 except BaseException, e:
1417 self.show_message(str(e))
1419 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1423 def do_sign(self, address, message, signature, password):
1425 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1426 signature.setText(sig)
1427 except BaseException, e:
1428 self.show_message(str(e))
1430 def sign_message(self, address):
1431 if not address: return
1434 d.setWindowTitle(_('Sign Message'))
1435 d.setMinimumSize(410, 290)
1437 tab_widget = QTabWidget()
1439 layout = QGridLayout(tab)
1441 sign_address = QLineEdit()
1443 sign_address.setText(address)
1444 layout.addWidget(QLabel(_('Address')), 1, 0)
1445 layout.addWidget(sign_address, 1, 1)
1447 sign_message = QTextEdit()
1448 layout.addWidget(QLabel(_('Message')), 2, 0)
1449 layout.addWidget(sign_message, 2, 1)
1450 layout.setRowStretch(2,3)
1452 sign_signature = QTextEdit()
1453 layout.addWidget(QLabel(_('Signature')), 3, 0)
1454 layout.addWidget(sign_signature, 3, 1)
1455 layout.setRowStretch(3,1)
1458 hbox = QHBoxLayout()
1459 b = QPushButton(_("Sign"))
1461 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1462 b = QPushButton(_("Close"))
1463 b.clicked.connect(d.accept)
1465 layout.addLayout(hbox, 4, 1)
1466 tab_widget.addTab(tab, _("Sign"))
1470 layout = QGridLayout(tab)
1472 verify_address = QLineEdit()
1473 layout.addWidget(QLabel(_('Address')), 1, 0)
1474 layout.addWidget(verify_address, 1, 1)
1476 verify_message = QTextEdit()
1477 layout.addWidget(QLabel(_('Message')), 2, 0)
1478 layout.addWidget(verify_message, 2, 1)
1479 layout.setRowStretch(2,3)
1481 verify_signature = QTextEdit()
1482 layout.addWidget(QLabel(_('Signature')), 3, 0)
1483 layout.addWidget(verify_signature, 3, 1)
1484 layout.setRowStretch(3,1)
1488 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1489 self.show_message(_("Signature verified"))
1490 except BaseException, e:
1491 self.show_message(str(e))
1494 hbox = QHBoxLayout()
1495 b = QPushButton(_("Verify"))
1496 b.clicked.connect(do_verify)
1498 b = QPushButton(_("Close"))
1499 b.clicked.connect(d.accept)
1501 layout.addLayout(hbox, 4, 1)
1502 tab_widget.addTab(tab, _("Verify"))
1504 vbox = QVBoxLayout()
1505 vbox.addWidget(tab_widget)
1512 def question(self, msg):
1513 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1515 def show_message(self, msg):
1516 QMessageBox.information(self, _('Message'), msg, _('OK'))
1518 def password_dialog(self ):
1525 vbox = QVBoxLayout()
1526 msg = _('Please enter your password')
1527 vbox.addWidget(QLabel(msg))
1529 grid = QGridLayout()
1531 grid.addWidget(QLabel(_('Password')), 1, 0)
1532 grid.addWidget(pw, 1, 1)
1533 vbox.addLayout(grid)
1535 vbox.addLayout(ok_cancel_buttons(d))
1538 self.run_hook('password_dialog', pw, grid, 1)
1539 if not d.exec_(): return
1540 return unicode(pw.text())
1547 def change_password_dialog( wallet, parent=None ):
1550 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1558 new_pw = QLineEdit()
1559 new_pw.setEchoMode(2)
1560 conf_pw = QLineEdit()
1561 conf_pw.setEchoMode(2)
1563 vbox = QVBoxLayout()
1565 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1566 +_('To disable wallet encryption, enter an empty new password.')) \
1567 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1569 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1570 +_("Leave these fields empty if you want to disable encryption.")
1571 vbox.addWidget(QLabel(msg))
1573 grid = QGridLayout()
1576 if wallet.use_encryption:
1577 grid.addWidget(QLabel(_('Password')), 1, 0)
1578 grid.addWidget(pw, 1, 1)
1580 grid.addWidget(QLabel(_('New Password')), 2, 0)
1581 grid.addWidget(new_pw, 2, 1)
1583 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1584 grid.addWidget(conf_pw, 3, 1)
1585 vbox.addLayout(grid)
1587 vbox.addLayout(ok_cancel_buttons(d))
1590 if not d.exec_(): return
1592 password = unicode(pw.text()) if wallet.use_encryption else None
1593 new_password = unicode(new_pw.text())
1594 new_password2 = unicode(conf_pw.text())
1597 seed = wallet.decode_seed(password)
1599 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1602 if new_password != new_password2:
1603 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1604 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1606 wallet.update_password(seed, password, new_password)
1608 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1609 parent.password_button.setIcon( icon )
1613 def seed_dialog(wallet, parent=None):
1617 vbox = QVBoxLayout()
1618 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1619 vbox.addWidget(QLabel(msg))
1621 grid = QGridLayout()
1624 seed_e = QLineEdit()
1625 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1626 grid.addWidget(seed_e, 1, 1)
1627 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1631 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1632 grid.addWidget(gap_e, 2, 1)
1633 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1634 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1635 vbox.addLayout(grid)
1637 vbox.addLayout(ok_cancel_buttons(d))
1640 if not d.exec_(): return
1643 gap = int(unicode(gap_e.text()))
1645 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1649 seed = str(seed_e.text())
1652 print_error("Warning: Not hex, trying decode")
1654 seed = mnemonic.mn_decode( seed.split(' ') )
1656 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1660 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1665 def generate_transaction_information_widget(self, tx):
1666 tabs = QTabWidget(self)
1669 grid_ui = QGridLayout(tab1)
1670 grid_ui.setColumnStretch(0,1)
1671 tabs.addTab(tab1, _('Outputs') )
1673 tree_widget = MyTreeWidget(self)
1674 tree_widget.setColumnCount(2)
1675 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1676 tree_widget.setColumnWidth(0, 300)
1677 tree_widget.setColumnWidth(1, 50)
1679 for address, value in tx.outputs:
1680 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1681 tree_widget.addTopLevelItem(item)
1683 tree_widget.setMaximumHeight(100)
1685 grid_ui.addWidget(tree_widget)
1688 grid_ui = QGridLayout(tab2)
1689 grid_ui.setColumnStretch(0,1)
1690 tabs.addTab(tab2, _('Inputs') )
1692 tree_widget = MyTreeWidget(self)
1693 tree_widget.setColumnCount(2)
1694 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1696 for input_line in tx.inputs:
1697 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1698 tree_widget.addTopLevelItem(item)
1700 tree_widget.setMaximumHeight(100)
1702 grid_ui.addWidget(tree_widget)
1706 def tx_dict_from_text(self, txt):
1708 tx_dict = json.loads(str(txt))
1709 assert "hex" in tx_dict.keys()
1710 assert "complete" in tx_dict.keys()
1711 if not tx_dict["complete"]:
1712 assert "input_info" in tx_dict.keys()
1714 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1719 def read_tx_from_file(self):
1720 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1724 with open(fileName, "r") as f:
1725 file_content = f.read()
1726 except (ValueError, IOError, os.error), reason:
1727 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1729 return self.tx_dict_from_text(file_content)
1733 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1735 self.wallet.signrawtransaction(tx, input_info, [], password)
1737 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1739 with open(fileName, "w+") as f:
1740 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1741 self.show_message(_("Transaction saved successfully"))
1744 except BaseException, e:
1745 self.show_message(str(e))
1748 def send_raw_transaction(self, raw_tx, dialog = ""):
1749 result, result_message = self.wallet.sendtx( raw_tx )
1751 self.show_message("Transaction successfully sent: %s" % (result_message))
1755 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1757 def do_process_from_text(self):
1758 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1761 tx_dict = self.tx_dict_from_text(text)
1763 self.create_process_transaction_window(tx_dict)
1765 def do_process_from_file(self):
1766 tx_dict = self.read_tx_from_file()
1768 self.create_process_transaction_window(tx_dict)
1770 def create_process_transaction_window(self, tx_dict):
1771 tx = Transaction(tx_dict["hex"])
1773 dialog = QDialog(self)
1774 dialog.setMinimumWidth(500)
1775 dialog.setWindowTitle(_('Process raw transaction'))
1781 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1782 l.addWidget(QLabel(_("Actions")), 4,0)
1784 if tx_dict["complete"] == False:
1785 l.addWidget(QLabel(_("Unsigned")), 3,1)
1786 if self.wallet.seed :
1787 b = QPushButton("Sign transaction")
1788 input_info = json.loads(tx_dict["input_info"])
1789 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1790 l.addWidget(b, 4, 1)
1792 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1794 l.addWidget(QLabel(_("Signed")), 3,1)
1795 b = QPushButton("Broadcast transaction")
1796 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1799 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1800 cancelButton = QPushButton(_("Cancel"))
1801 cancelButton.clicked.connect(lambda: dialog.done(0))
1802 l.addWidget(cancelButton, 4,2)
1808 def do_export_privkeys(self, password):
1809 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.")))
1812 select_export = _('Select file to export your private keys to')
1813 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1815 with open(fileName, "w+") as csvfile:
1816 transaction = csv.writer(csvfile)
1817 transaction.writerow(["address", "private_key"])
1820 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1821 transaction.writerow(["%34s"%addr,pk])
1823 self.show_message(_("Private keys exported."))
1825 except (IOError, os.error), reason:
1826 export_error_label = _("Electrum was unable to produce a private key-export.")
1827 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1829 except BaseException, e:
1830 self.show_message(str(e))
1834 def do_import_labels(self):
1835 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1836 if not labelsFile: return
1838 f = open(labelsFile, 'r')
1841 for key, value in json.loads(data).items():
1842 self.wallet.labels[key] = value
1844 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1845 except (IOError, os.error), reason:
1846 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1849 def do_export_labels(self):
1850 labels = self.wallet.labels
1852 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1854 with open(fileName, 'w+') as f:
1855 json.dump(labels, f)
1856 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1857 except (IOError, os.error), reason:
1858 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1861 def do_export_history(self):
1862 from gui_lite import csv_transaction
1863 csv_transaction(self.wallet)
1867 def do_import_privkey(self, password):
1868 if not self.wallet.imported_keys:
1869 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1870 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1871 + _('In addition, when you send bitcoins from one of your imported addresses, the "change" will be sent to an address derived from your seed, unless you disabled this option.') + '<p>' \
1872 + _('Are you sure you understand what you are doing?'), 3, 4)
1875 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1878 text = str(text).split()
1883 addr = self.wallet.import_key(key, password)
1884 except BaseException as e:
1890 addrlist.append(addr)
1892 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1894 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1895 self.update_receive_tab()
1896 self.update_history_tab()
1899 def settings_dialog(self):
1901 d.setWindowTitle(_('Electrum Settings'))
1903 vbox = QVBoxLayout()
1905 tabs = QTabWidget(self)
1906 self.settings_tab = tabs
1907 vbox.addWidget(tabs)
1910 grid_ui = QGridLayout(tab1)
1911 grid_ui.setColumnStretch(0,1)
1912 tabs.addTab(tab1, _('Display') )
1914 nz_label = QLabel(_('Display zeros'))
1915 grid_ui.addWidget(nz_label, 0, 0)
1917 nz_e.setText("%d"% self.wallet.num_zeros)
1918 grid_ui.addWidget(nz_e, 0, 1)
1919 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1920 grid_ui.addWidget(HelpButton(msg), 0, 2)
1921 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1922 if not self.config.is_modifiable('num_zeros'):
1923 for w in [nz_e, nz_label]: w.setEnabled(False)
1925 lang_label=QLabel(_('Language') + ':')
1926 grid_ui.addWidget(lang_label, 1, 0)
1927 lang_combo = QComboBox()
1928 from i18n import languages
1929 lang_combo.addItems(languages.values())
1931 index = languages.keys().index(self.config.get("language",''))
1934 lang_combo.setCurrentIndex(index)
1935 grid_ui.addWidget(lang_combo, 1, 1)
1936 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1937 if not self.config.is_modifiable('language'):
1938 for w in [lang_combo, lang_label]: w.setEnabled(False)
1940 currencies = self.exchanger.get_currencies()
1941 currencies.insert(0, "None")
1943 cur_label=QLabel(_('Currency') + ':')
1944 grid_ui.addWidget(cur_label , 2, 0)
1945 cur_combo = QComboBox()
1946 cur_combo.addItems(currencies)
1948 index = currencies.index(self.config.get('currency', "None"))
1951 cur_combo.setCurrentIndex(index)
1952 grid_ui.addWidget(cur_combo, 2, 1)
1953 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1955 expert_cb = QCheckBox(_('Expert mode'))
1956 expert_cb.setChecked(self.expert_mode)
1957 grid_ui.addWidget(expert_cb, 3, 0)
1958 hh = _('In expert mode, your client will:') + '\n' \
1959 + _(' - Show change addresses in the Receive tab') + '\n' \
1960 + _(' - Display the balance of each address') + '\n' \
1961 + _(' - Add freeze/prioritize actions to addresses.')
1962 grid_ui.addWidget(HelpButton(hh), 3, 2)
1963 grid_ui.setRowStretch(4,1)
1967 grid_wallet = QGridLayout(tab2)
1968 grid_wallet.setColumnStretch(0,1)
1969 tabs.addTab(tab2, _('Wallet') )
1971 fee_label = QLabel(_('Transaction fee'))
1972 grid_wallet.addWidget(fee_label, 0, 0)
1974 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1975 grid_wallet.addWidget(fee_e, 0, 2)
1976 msg = _('Fee per kilobyte of transaction.') + ' ' \
1977 + _('Recommended value') + ': 0.0002'
1978 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1979 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1980 if not self.config.is_modifiable('fee_per_kb'):
1981 for w in [fee_e, fee_label]: w.setEnabled(False)
1983 usechange_label = QLabel(_('Use change addresses'))
1984 grid_wallet.addWidget(usechange_label, 1, 0)
1985 usechange_combo = QComboBox()
1986 usechange_combo.addItems([_('Yes'), _('No')])
1987 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1988 grid_wallet.addWidget(usechange_combo, 1, 2)
1989 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1990 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1992 gap_label = QLabel(_('Gap limit'))
1993 grid_wallet.addWidget(gap_label, 2, 0)
1995 gap_e.setText("%d"% self.wallet.gap_limit)
1996 grid_wallet.addWidget(gap_e, 2, 2)
1997 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1998 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1999 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2000 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2001 + _('Warning') + ': ' \
2002 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2003 + _('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'
2004 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2005 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
2006 if not self.config.is_modifiable('gap_limit'):
2007 for w in [gap_e, gap_label]: w.setEnabled(False)
2009 grid_wallet.setRowStretch(3,1)
2014 grid_io = QGridLayout(tab3)
2015 grid_io.setColumnStretch(0,1)
2016 tabs.addTab(tab3, _('Import/Export') )
2018 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2019 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2020 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2021 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2023 grid_io.addWidget(QLabel(_('History')), 2, 0)
2024 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2025 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2027 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2029 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2030 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2031 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2033 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2034 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2035 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2036 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2037 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2040 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2041 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2042 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2043 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2045 grid_io.setRowStretch(5,1)
2050 tab5 = QScrollArea()
2051 grid_plugins = QGridLayout(tab5)
2052 grid_plugins.setColumnStretch(0,1)
2053 tabs.addTab(tab5, _('Plugins') )
2054 def mk_toggle(cb, p):
2055 return lambda: cb.setChecked(p.toggle())
2056 for i, p in enumerate(self.plugins):
2058 name, description = p.get_info()
2059 cb = QCheckBox(name)
2060 cb.setDisabled(not p.is_available())
2061 cb.setChecked(p.is_enabled())
2062 cb.clicked.connect(mk_toggle(cb,p))
2063 grid_plugins.addWidget(cb, i, 0)
2064 if p.requires_settings():
2065 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2066 grid_plugins.addWidget(HelpButton(description), i, 2)
2068 print_msg("Error: cannot display plugin", p)
2069 traceback.print_exc(file=sys.stdout)
2070 grid_plugins.setRowStretch(i+1,1)
2072 self.run_hook('create_settings_tab', tabs)
2074 vbox.addLayout(ok_cancel_buttons(d))
2078 if not d.exec_(): return
2080 fee = unicode(fee_e.text())
2082 fee = int( 100000000 * Decimal(fee) )
2084 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2087 if self.wallet.fee != fee:
2088 self.wallet.fee = fee
2091 nz = unicode(nz_e.text())
2096 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2099 if self.wallet.num_zeros != nz:
2100 self.wallet.num_zeros = nz
2101 self.config.set_key('num_zeros', nz, True)
2102 self.update_history_tab()
2103 self.update_receive_tab()
2105 usechange_result = usechange_combo.currentIndex() == 0
2106 if self.wallet.use_change != usechange_result:
2107 self.wallet.use_change = usechange_result
2108 self.config.set_key('use_change', self.wallet.use_change, True)
2111 n = int(gap_e.text())
2113 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2116 if self.wallet.gap_limit != n:
2117 r = self.wallet.change_gap_limit(n)
2119 self.update_receive_tab()
2120 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2122 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2124 need_restart = False
2126 lang_request = languages.keys()[lang_combo.currentIndex()]
2127 if lang_request != self.config.get('language'):
2128 self.config.set_key("language", lang_request, True)
2131 cur_request = str(currencies[cur_combo.currentIndex()])
2132 if cur_request != self.config.get('currency', "None"):
2133 self.config.set_key('currency', cur_request, True)
2134 self.update_wallet()
2136 self.run_hook('close_settings_dialog')
2139 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2141 self.receive_tab_set_mode(expert_cb.isChecked())
2145 def network_dialog(wallet, parent=None):
2146 interface = wallet.interface
2148 if interface.is_connected:
2149 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2151 status = _("Not connected")
2152 server = interface.server
2155 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2156 server = interface.server
2158 plist, servers_list = interface.get_servers_list()
2162 d.setWindowTitle(_('Server'))
2163 d.setMinimumSize(375, 20)
2165 vbox = QVBoxLayout()
2168 hbox = QHBoxLayout()
2170 l.setPixmap(QPixmap(":icons/network.png"))
2173 hbox.addWidget(QLabel(status))
2175 vbox.addLayout(hbox)
2179 grid = QGridLayout()
2181 vbox.addLayout(grid)
2184 server_protocol = QComboBox()
2185 server_host = QLineEdit()
2186 server_host.setFixedWidth(200)
2187 server_port = QLineEdit()
2188 server_port.setFixedWidth(60)
2190 protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2191 protocol_letters = 'thsg'
2192 server_protocol.addItems(protocol_names)
2194 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2195 grid.addWidget(server_protocol, 0, 1)
2196 grid.addWidget(server_host, 0, 2)
2197 grid.addWidget(server_port, 0, 3)
2199 def change_protocol(p):
2200 protocol = protocol_letters[p]
2201 host = unicode(server_host.text())
2202 pp = plist.get(host,DEFAULT_PORTS)
2203 if protocol not in pp.keys():
2204 protocol = pp.keys()[0]
2206 server_host.setText( host )
2207 server_port.setText( port )
2209 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2211 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2212 servers_list_widget = QTreeWidget(parent)
2213 servers_list_widget.setHeaderLabels( [ label, _('Pruning') ] )
2214 servers_list_widget.setMaximumHeight(150)
2215 servers_list_widget.setColumnWidth(0, 240)
2216 for _host in servers_list.keys():
2217 pruning_level = servers_list[_host].get('pruning')
2218 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2219 servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2221 def change_server(host, protocol=None):
2222 pp = plist.get(host,DEFAULT_PORTS)
2224 port = pp.get(protocol)
2225 if not port: protocol = None
2228 if 's' in pp.keys():
2230 port = pp.get(protocol)
2232 protocol = pp.keys()[0]
2233 port = pp.get(protocol)
2235 server_host.setText( host )
2236 server_port.setText( port )
2237 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2239 if not plist: return
2240 for p in protocol_letters:
2241 i = protocol_letters.index(p)
2242 j = server_protocol.model().index(i,0)
2243 if p not in pp.keys() and interface.is_connected:
2244 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2246 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2250 host, port, protocol = server.split(':')
2251 change_server(host,protocol)
2253 servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'),
2254 lambda x,y: change_server(unicode(x.text(0))))
2255 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2257 if not wallet.config.is_modifiable('server'):
2258 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2261 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2262 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2263 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2264 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2267 proxy_mode = QComboBox()
2268 proxy_host = QLineEdit()
2269 proxy_host.setFixedWidth(200)
2270 proxy_port = QLineEdit()
2271 proxy_port.setFixedWidth(60)
2272 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2274 def check_for_disable(index = False):
2275 if proxy_mode.currentText() != 'NONE':
2276 proxy_host.setEnabled(True)
2277 proxy_port.setEnabled(True)
2279 proxy_host.setEnabled(False)
2280 proxy_port.setEnabled(False)
2283 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2285 if not wallet.config.is_modifiable('proxy'):
2286 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2288 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2289 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2290 proxy_host.setText(proxy_config.get("host"))
2291 proxy_port.setText(proxy_config.get("port"))
2293 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2294 grid.addWidget(proxy_mode, 2, 1)
2295 grid.addWidget(proxy_host, 2, 2)
2296 grid.addWidget(proxy_port, 2, 3)
2299 vbox.addLayout(ok_cancel_buttons(d))
2302 if not d.exec_(): return
2304 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2305 if proxy_mode.currentText() != 'NONE':
2306 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2310 wallet.config.set_key("proxy", proxy, True)
2311 wallet.config.set_key("server", server, True)
2312 interface.set_server(server, proxy)
2313 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2316 def closeEvent(self, event):
2318 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2319 self.save_column_widths()
2320 self.config.set_key("column_widths", self.column_widths, True)
2321 self.config.set_key("console-history",self.console.history[-50:])
2327 def __init__(self, wallet, config, app=None):
2328 self.wallet = wallet
2329 self.config = config
2331 self.app = QApplication(sys.argv)
2334 def restore_or_create(self):
2335 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2336 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2337 if r==2: return None
2338 return 'restore' if r==1 else 'create'
2340 def seed_dialog(self):
2341 return ElectrumWindow.seed_dialog( self.wallet )
2343 def network_dialog(self):
2344 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2347 def show_seed(self):
2348 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2351 def password_dialog(self):
2352 if self.wallet.seed:
2353 ElectrumWindow.change_password_dialog(self.wallet)
2356 def restore_wallet(self):
2357 wallet = self.wallet
2358 # wait until we are connected, because the user might have selected another server
2359 if not wallet.interface.is_connected:
2360 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2361 waiting_dialog(waiting)
2363 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2364 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2366 wallet.set_up_to_date(False)
2367 wallet.interface.poke('synchronizer')
2368 waiting_dialog(waiting)
2369 if wallet.is_found():
2370 print_error( "Recovery successful" )
2372 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2379 w = ElectrumWindow(self.wallet, self.config)
2380 if url: w.set_url(url)