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, threading
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 amountedit import AmountEdit
52 from decimal import Decimal
60 if platform.system() == 'Windows':
61 MONOSPACE_FONT = 'Lucida Console'
62 elif platform.system() == 'Darwin':
63 MONOSPACE_FONT = 'Monaco'
65 MONOSPACE_FONT = 'monospace'
67 from electrum import ELECTRUM_VERSION
70 class UpdateLabel(QtGui.QLabel):
71 def __init__(self, config, parent=None):
72 QtGui.QLabel.__init__(self, parent)
73 self.new_version = False
76 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
77 con.request("GET", "/version")
78 res = con.getresponse()
79 except socket.error as msg:
80 print_error("Could not retrieve version information")
84 self.latest_version = res.read()
85 self.latest_version = self.latest_version.replace("\n","")
86 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
88 self.current_version = ELECTRUM_VERSION
89 if(self.compare_versions(self.latest_version, self.current_version) == 1):
90 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
91 if(self.compare_versions(self.latest_version, latest_seen) == 1):
92 self.new_version = True
93 self.setText(_("New version available") + ": " + self.latest_version)
96 def compare_versions(self, version1, version2):
98 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
99 return cmp(normalize(version1), normalize(version2))
101 def ignore_this_version(self):
103 self.config.set_key("last_seen_version", self.latest_version, True)
104 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
107 def ignore_all_version(self):
109 self.config.set_key("last_seen_version", "9.9.9", True)
110 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
113 def open_website(self):
114 webbrowser.open("http://electrum.org/download.html")
117 def mouseReleaseEvent(self, event):
118 dialog = QDialog(self)
119 dialog.setWindowTitle(_('Electrum update'))
122 main_layout = QGridLayout()
123 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
125 ignore_version = QPushButton(_("Ignore this version"))
126 ignore_version.clicked.connect(self.ignore_this_version)
128 ignore_all_versions = QPushButton(_("Ignore all versions"))
129 ignore_all_versions.clicked.connect(self.ignore_all_version)
131 open_website = QPushButton(_("Goto download page"))
132 open_website.clicked.connect(self.open_website)
134 main_layout.addWidget(ignore_version, 1, 0)
135 main_layout.addWidget(ignore_all_versions, 1, 1)
136 main_layout.addWidget(open_website, 1, 2)
138 dialog.setLayout(main_layout)
142 if not dialog.exec_(): return
146 class Timer(QtCore.QThread):
149 self.emit(QtCore.SIGNAL('timersignal'))
152 class HelpButton(QPushButton):
153 def __init__(self, text):
154 QPushButton.__init__(self, '?')
155 self.setFocusPolicy(Qt.NoFocus)
156 self.setFixedWidth(20)
157 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
160 class EnterButton(QPushButton):
161 def __init__(self, text, func):
162 QPushButton.__init__(self, text)
164 self.clicked.connect(func)
166 def keyPressEvent(self, e):
167 if e.key() == QtCore.Qt.Key_Return:
170 class MyTreeWidget(QTreeWidget):
171 def __init__(self, parent):
172 QTreeWidget.__init__(self, parent)
175 for i in range(0,self.viewport().height()/5):
176 if self.itemAt(QPoint(0,i*5)) == item:
180 for j in range(0,30):
181 if self.itemAt(QPoint(0,i*5 + j)) != item:
183 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
185 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
190 class StatusBarButton(QPushButton):
191 def __init__(self, icon, tooltip, func):
192 QPushButton.__init__(self, icon, '')
193 self.setToolTip(tooltip)
195 self.setMaximumWidth(25)
196 self.clicked.connect(func)
199 def keyPressEvent(self, e):
200 if e.key() == QtCore.Qt.Key_Return:
207 def waiting_dialog(f):
213 w.setWindowTitle('Electrum')
223 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
228 def ok_cancel_buttons(dialog, ok_label=_("OK") ):
231 b = QPushButton(_("Cancel"))
233 b.clicked.connect(dialog.reject)
234 b = QPushButton(ok_label)
236 b.clicked.connect(dialog.accept)
241 def text_dialog(parent, title, label, ok_label):
242 dialog = QDialog(parent)
243 dialog.setMinimumWidth(500)
244 dialog.setWindowTitle(title)
248 l.addWidget(QLabel(label))
251 l.addLayout(ok_cancel_buttons(dialog, ok_label))
253 return unicode(txt.toPlainText())
257 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
259 class ElectrumWindow(QMainWindow):
261 def __init__(self, wallet, config):
262 QMainWindow.__init__(self)
266 self.current_account = self.config.get("current_account", None)
269 self.create_status_bar()
271 self.need_update = threading.Event()
272 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
273 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
274 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
275 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
277 self.expert_mode = config.get('classic_expert_mode', False)
278 self.decimal_point = config.get('decimal_point', 8)
280 set_language(config.get('language'))
282 self.funds_error = False
283 self.completions = QStringListModel()
285 self.tabs = tabs = QTabWidget(self)
286 self.column_widths = self.config.get("column_widths", default_column_widths )
287 tabs.addTab(self.create_history_tab(), _('History') )
288 tabs.addTab(self.create_send_tab(), _('Send') )
289 tabs.addTab(self.create_receive_tab(), _('Receive') )
290 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
291 tabs.addTab(self.create_console_tab(), _('Console') )
292 tabs.setMinimumSize(600, 400)
293 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
294 self.setCentralWidget(tabs)
296 g = self.config.get("winpos-qt",[100, 100, 840, 400])
297 self.setGeometry(g[0], g[1], g[2], g[3])
298 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
299 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
300 self.setWindowTitle( title )
302 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
303 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
304 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
305 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
307 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
308 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
309 self.history_list.setFocus(True)
311 self.exchanger = exchange_rate.Exchanger(self)
312 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
314 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
315 if platform.system() == 'Windows':
316 n = 3 if self.wallet.seed else 2
317 tabs.setCurrentIndex (n)
318 tabs.setCurrentIndex (0)
320 # set initial message
321 self.console.showMessage(self.wallet.interface.banner)
323 # plugins that need to change the GUI do it here
324 self.run_hook('init_gui')
328 def init_plugins(self):
329 import imp, pkgutil, __builtin__
330 if __builtin__.use_local_modules:
331 fp, pathname, description = imp.find_module('plugins')
332 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
333 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
334 imp.load_module('electrum_plugins', fp, pathname, description)
335 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
337 import electrum_plugins
338 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
339 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
344 self.plugins.append( p.Plugin(self) )
346 print_msg("Error:cannot initialize plugin",p)
347 traceback.print_exc(file=sys.stdout)
350 def run_hook(self, name, *args):
351 for p in self.plugins:
352 if not p.is_enabled():
361 print_error("Plugin error")
362 traceback.print_exc(file=sys.stdout)
367 def set_label(self, name, text = None):
369 old_text = self.wallet.labels.get(name)
372 self.wallet.labels[name] = text
376 self.wallet.labels.pop(name)
378 self.run_hook('set_label', name, text, changed)
382 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
383 def getOpenFileName(self, title, filter = None):
384 directory = self.config.get('io_dir', os.path.expanduser('~'))
385 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
386 if fileName and directory != os.path.dirname(fileName):
387 self.config.set_key('io_dir', os.path.dirname(fileName), True)
390 def getSaveFileName(self, title, filename, filter = None):
391 directory = self.config.get('io_dir', os.path.expanduser('~'))
392 path = os.path.join( directory, filename )
393 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
394 if fileName and directory != os.path.dirname(fileName):
395 self.config.set_key('io_dir', os.path.dirname(fileName), True)
401 QMainWindow.close(self)
402 self.run_hook('close_main_window')
404 def connect_slots(self, sender):
405 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
406 self.previous_payto_e=''
408 def timer_actions(self):
409 if self.need_update.is_set():
411 self.need_update.clear()
412 self.run_hook('timer_actions')
414 def format_amount(self, x, is_diff=False):
415 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
417 def read_amount(self, x):
418 if x in['.', '']: return None
419 p = pow(10, self.decimal_point)
420 return int( p * Decimal(x) )
423 assert self.decimal_point in [5,8]
424 return "BTC" if self.decimal_point == 8 else "mBTC"
426 def update_status(self):
427 if self.wallet.interface and self.wallet.interface.is_connected:
428 if not self.wallet.up_to_date:
429 text = _("Synchronizing...")
430 icon = QIcon(":icons/status_waiting.png")
432 c, u = self.wallet.get_account_balance(self.current_account)
433 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
434 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
435 text += self.create_quote_text(Decimal(c+u)/100000000)
436 icon = QIcon(":icons/status_connected.png")
438 text = _("Not connected")
439 icon = QIcon(":icons/status_disconnected.png")
441 self.status_text = text
442 self.statusBar().showMessage(text)
443 self.status_button.setIcon( icon )
445 def update_wallet(self):
447 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
448 self.update_history_tab()
449 self.update_receive_tab()
450 self.update_contacts_tab()
451 self.update_completions()
454 def create_quote_text(self, btc_balance):
455 quote_currency = self.config.get("currency", "None")
456 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
457 if quote_balance is None:
460 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
463 def create_history_tab(self):
464 self.history_list = l = MyTreeWidget(self)
466 for i,width in enumerate(self.column_widths['history']):
467 l.setColumnWidth(i, width)
468 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
469 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
470 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
472 l.setContextMenuPolicy(Qt.CustomContextMenu)
473 l.customContextMenuRequested.connect(self.create_history_menu)
477 def create_history_menu(self, position):
478 self.history_list.selectedIndexes()
479 item = self.history_list.currentItem()
481 tx_hash = str(item.data(0, Qt.UserRole).toString())
482 if not tx_hash: return
484 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
485 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
486 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
487 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
490 def show_tx_details(self, tx):
491 dialog = QDialog(self)
493 dialog.setWindowTitle(_("Transaction Details"))
495 dialog.setLayout(vbox)
496 dialog.setMinimumSize(600,300)
499 if tx_hash in self.wallet.transactions.keys():
500 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
501 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
503 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
509 vbox.addWidget(QLabel("Transaction ID:"))
510 e = QLineEdit(tx_hash)
514 vbox.addWidget(QLabel("Date: %s"%time_str))
515 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
518 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
519 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
521 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
522 vbox.addWidget(QLabel("Transaction fee: unknown"))
524 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
526 vbox.addWidget( self.generate_transaction_information_widget(tx) )
528 ok_button = QPushButton(_("Close"))
529 ok_button.setDefault(True)
530 ok_button.clicked.connect(dialog.accept)
534 hbox.addWidget(ok_button)
538 def tx_label_clicked(self, item, column):
539 if column==2 and item.isSelected():
541 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
542 self.history_list.editItem( item, column )
543 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
546 def tx_label_changed(self, item, column):
550 tx_hash = str(item.data(0, Qt.UserRole).toString())
551 tx = self.wallet.transactions.get(tx_hash)
552 text = unicode( item.text(2) )
553 self.set_label(tx_hash, text)
555 item.setForeground(2, QBrush(QColor('black')))
557 text = self.wallet.get_default_label(tx_hash)
558 item.setText(2, text)
559 item.setForeground(2, QBrush(QColor('gray')))
563 def edit_label(self, is_recv):
564 l = self.receive_list if is_recv else self.contacts_list
565 item = l.currentItem()
566 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
567 l.editItem( item, 1 )
568 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
572 def address_label_clicked(self, item, column, l, column_addr, column_label):
573 if column == column_label and item.isSelected():
574 is_editable = item.data(0, 32).toBool()
577 addr = unicode( item.text(column_addr) )
578 label = unicode( item.text(column_label) )
579 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
580 l.editItem( item, column )
581 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
584 def address_label_changed(self, item, column, l, column_addr, column_label):
585 if column == column_label:
586 addr = unicode( item.text(column_addr) )
587 text = unicode( item.text(column_label) )
588 is_editable = item.data(0, 32).toBool()
592 changed = self.set_label(addr, text)
594 self.update_history_tab()
595 self.update_completions()
597 self.current_item_changed(item)
599 self.run_hook('item_changed', item, column)
602 def current_item_changed(self, a):
603 self.run_hook('current_item_changed', a)
607 def update_history_tab(self):
609 self.history_list.clear()
610 for item in self.wallet.get_tx_history(self.current_account):
611 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
614 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
619 time_str = 'unverified'
620 icon = QIcon(":icons/unconfirmed.png")
623 icon = QIcon(":icons/unconfirmed.png")
625 icon = QIcon(":icons/clock%d.png"%conf)
627 icon = QIcon(":icons/confirmed.png")
629 if value is not None:
630 v_str = self.format_amount(value, True)
634 balance_str = self.format_amount(balance)
637 label, is_default_label = self.wallet.get_label(tx_hash)
639 label = _('Pruned transaction outputs')
640 is_default_label = False
642 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
643 item.setFont(2, QFont(MONOSPACE_FONT))
644 item.setFont(3, QFont(MONOSPACE_FONT))
645 item.setFont(4, QFont(MONOSPACE_FONT))
647 item.setForeground(3, QBrush(QColor("#BC1E1E")))
649 item.setData(0, Qt.UserRole, tx_hash)
650 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
652 item.setForeground(2, QBrush(QColor('grey')))
654 item.setIcon(0, icon)
655 self.history_list.insertTopLevelItem(0,item)
658 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
661 def create_send_tab(self):
666 grid.setColumnMinimumWidth(3,300)
667 grid.setColumnStretch(5,1)
670 self.payto_e = QLineEdit()
671 grid.addWidget(QLabel(_('Pay to')), 1, 0)
672 grid.addWidget(self.payto_e, 1, 1, 1, 3)
674 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)
676 completer = QCompleter()
677 completer.setCaseSensitivity(False)
678 self.payto_e.setCompleter(completer)
679 completer.setModel(self.completions)
681 self.message_e = QLineEdit()
682 grid.addWidget(QLabel(_('Description')), 2, 0)
683 grid.addWidget(self.message_e, 2, 1, 1, 3)
684 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)
686 self.amount_e = AmountEdit(self.base_unit)
687 grid.addWidget(QLabel(_('Amount')), 3, 0)
688 grid.addWidget(self.amount_e, 3, 1, 1, 2)
689 grid.addWidget(HelpButton(
690 _('Amount to be sent.') + '\n\n' \
691 + _('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.') \
692 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
694 self.fee_e = AmountEdit(self.base_unit)
695 grid.addWidget(QLabel(_('Fee')), 4, 0)
696 grid.addWidget(self.fee_e, 4, 1, 1, 2)
697 grid.addWidget(HelpButton(
698 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
699 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
700 + _('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)
703 b = EnterButton(_("Send"), self.do_send)
705 b = EnterButton(_("Create unsigned transaction"), self.do_send)
706 grid.addWidget(b, 6, 1)
708 b = EnterButton(_("Clear"),self.do_clear)
709 grid.addWidget(b, 6, 2)
711 self.payto_sig = QLabel('')
712 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
714 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
715 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
724 def entry_changed( is_fee ):
725 self.funds_error = False
727 if self.amount_e.is_shortcut:
728 self.amount_e.is_shortcut = False
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( self.format_amount(amount) )
734 self.fee_e.setText( self.format_amount( fee ) )
737 amount = self.read_amount(str(self.amount_e.text()))
738 fee = self.read_amount(str(self.fee_e.text()))
740 if not is_fee: fee = None
743 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
745 self.fee_e.setText( self.format_amount( fee ) )
748 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
749 text = self.status_text
752 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
753 self.funds_error = True
754 text = _( "Not enough funds" )
755 c, u = self.wallet.get_frozen_balance()
756 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
758 self.statusBar().showMessage(text)
759 self.amount_e.setPalette(palette)
760 self.fee_e.setPalette(palette)
762 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
763 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
765 self.run_hook('create_send_tab', grid)
769 def update_completions(self):
771 for addr,label in self.wallet.labels.items():
772 if addr in self.wallet.addressbook:
773 l.append( label + ' <' + addr + '>')
775 self.run_hook('update_completions', l)
776 self.completions.setStringList(l)
780 return lambda s, *args: s.do_protect(func, args)
784 def do_send(self, password):
786 label = unicode( self.message_e.text() )
787 r = unicode( self.payto_e.text() )
790 # label or alias, with address in brackets
791 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
792 to_address = m.group(2) if m else r
794 if not is_valid(to_address):
795 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
799 amount = self.read_amount(unicode( self.amount_e.text()))
801 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
804 fee = self.read_amount(unicode( self.fee_e.text()))
806 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
810 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
811 except BaseException, e:
812 self.show_message(str(e))
815 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
816 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
819 self.run_hook('send_tx', tx)
822 self.set_label(tx.hash(), label)
825 h = self.wallet.send_tx(tx)
826 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
827 status, msg = self.wallet.receive_tx( h )
829 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
831 self.update_contacts_tab()
833 QMessageBox.warning(self, _('Error'), msg, _('OK'))
835 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
837 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
838 with open(fileName,'w') as f:
839 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
840 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
842 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
847 def set_url(self, url):
848 address, amount, label, message, signature, identity, url = util.parse_url(url)
849 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
851 if label and self.wallet.labels.get(address) != label:
852 if self.question('Give label "%s" to address %s ?'%(label,address)):
853 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
854 self.wallet.addressbook.append(address)
855 self.set_label(address, label)
857 self.run_hook('set_url', url, self.show_message, self.question)
859 self.tabs.setCurrentIndex(1)
860 label = self.wallet.labels.get(address)
861 m_addr = label + ' <'+ address +'>' if label else address
862 self.payto_e.setText(m_addr)
864 self.message_e.setText(message)
865 self.amount_e.setText(amount)
867 self.set_frozen(self.payto_e,True)
868 self.set_frozen(self.amount_e,True)
869 self.set_frozen(self.message_e,True)
870 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
872 self.payto_sig.setVisible(False)
875 self.payto_sig.setVisible(False)
876 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
878 self.set_frozen(e,False)
881 def set_frozen(self,entry,frozen):
883 entry.setReadOnly(True)
884 entry.setFrame(False)
886 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
887 entry.setPalette(palette)
889 entry.setReadOnly(False)
892 palette.setColor(entry.backgroundRole(), QColor('white'))
893 entry.setPalette(palette)
896 def toggle_freeze(self,addr):
898 if addr in self.wallet.frozen_addresses:
899 self.wallet.unfreeze(addr)
901 self.wallet.freeze(addr)
902 self.update_receive_tab()
904 def toggle_priority(self,addr):
906 if addr in self.wallet.prioritized_addresses:
907 self.wallet.unprioritize(addr)
909 self.wallet.prioritize(addr)
910 self.update_receive_tab()
913 def create_list_tab(self, headers):
914 "generic tab creation method"
915 l = MyTreeWidget(self)
916 l.setColumnCount( len(headers) )
917 l.setHeaderLabels( headers )
927 vbox.addWidget(buttons)
932 buttons.setLayout(hbox)
937 def create_receive_tab(self):
938 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
939 l.setContextMenuPolicy(Qt.CustomContextMenu)
940 l.customContextMenuRequested.connect(self.create_receive_menu)
941 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
942 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
943 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
944 self.receive_list = l
945 self.receive_buttons_hbox = hbox
950 def receive_tab_set_mode(self, i):
951 self.save_column_widths()
952 self.expert_mode = (i == 1)
953 self.config.set_key('classic_expert_mode', self.expert_mode, True)
955 self.update_receive_tab()
958 def save_column_widths(self):
959 if not self.expert_mode:
960 widths = [ self.receive_list.columnWidth(0) ]
963 for i in range(self.receive_list.columnCount() -1):
964 widths.append(self.receive_list.columnWidth(i))
965 self.column_widths["receive"][self.expert_mode] = widths
967 self.column_widths["history"] = []
968 for i in range(self.history_list.columnCount() - 1):
969 self.column_widths["history"].append(self.history_list.columnWidth(i))
971 self.column_widths["contacts"] = []
972 for i in range(self.contacts_list.columnCount() - 1):
973 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
975 self.config.set_key("column_widths", self.column_widths, True)
978 def create_contacts_tab(self):
979 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
980 l.setContextMenuPolicy(Qt.CustomContextMenu)
981 l.customContextMenuRequested.connect(self.create_contact_menu)
982 for i,width in enumerate(self.column_widths['contacts']):
983 l.setColumnWidth(i, width)
985 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
986 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
987 self.contacts_list = l
988 self.contacts_buttons_hbox = hbox
989 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
994 def delete_imported_key(self, addr):
995 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
996 self.wallet.imported_keys.pop(addr)
997 self.update_receive_tab()
998 self.update_history_tab()
1002 def create_receive_menu(self, position):
1003 # fixme: this function apparently has a side effect.
1004 # if it is not called the menu pops up several times
1005 #self.receive_list.selectedIndexes()
1007 item = self.receive_list.itemAt(position)
1009 addr = unicode(item.text(0))
1010 if not is_valid(addr):
1011 item.setExpanded(not item.isExpanded())
1014 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1015 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1016 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1017 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1018 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1019 if addr in self.wallet.imported_keys:
1020 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1022 if self.expert_mode:
1023 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1024 menu.addAction(t, lambda: self.toggle_freeze(addr))
1025 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1026 menu.addAction(t, lambda: self.toggle_priority(addr))
1028 self.run_hook('receive_menu', menu)
1029 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1032 def payto(self, addr):
1034 label = self.wallet.labels.get(addr)
1035 m_addr = label + ' <' + addr + '>' if label else addr
1036 self.tabs.setCurrentIndex(1)
1037 self.payto_e.setText(m_addr)
1038 self.amount_e.setFocus()
1041 def delete_contact(self, x):
1042 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1043 if x in self.wallet.addressbook:
1044 self.wallet.addressbook.remove(x)
1045 self.set_label(x, None)
1046 self.update_history_tab()
1047 self.update_contacts_tab()
1048 self.update_completions()
1051 def create_contact_menu(self, position):
1052 item = self.contacts_list.itemAt(position)
1054 addr = unicode(item.text(0))
1055 label = unicode(item.text(1))
1056 is_editable = item.data(0,32).toBool()
1057 payto_addr = item.data(0,33).toString()
1059 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1060 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1061 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1063 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1064 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1066 self.run_hook('create_contact_menu', menu, item)
1067 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1070 def update_receive_item(self, item):
1071 item.setFont(0, QFont(MONOSPACE_FONT))
1072 address = str(item.data(0,0).toString())
1073 label = self.wallet.labels.get(address,'')
1074 item.setData(1,0,label)
1075 item.setData(0,32, True) # is editable
1077 self.run_hook('update_receive_item', address, item)
1079 c, u = self.wallet.get_addr_balance(address)
1080 balance = self.format_amount(c + u)
1081 item.setData(2,0,balance)
1083 if self.expert_mode:
1084 if address in self.wallet.frozen_addresses:
1085 item.setBackgroundColor(0, QColor('lightblue'))
1086 elif address in self.wallet.prioritized_addresses:
1087 item.setBackgroundColor(0, QColor('lightgreen'))
1090 def update_receive_tab(self):
1091 l = self.receive_list
1094 l.setColumnHidden(2, not self.expert_mode)
1095 l.setColumnHidden(3, not self.expert_mode)
1096 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1097 l.setColumnWidth(i, width)
1099 if self.current_account is None:
1100 account_items = self.wallet.accounts.items()
1101 elif self.current_account != -1:
1102 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1106 for k, account in account_items:
1107 name = account.get('name',str(k))
1108 c,u = self.wallet.get_account_balance(k)
1109 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1110 l.addTopLevelItem(account_item)
1111 account_item.setExpanded(True)
1113 for is_change in ([0,1] if self.expert_mode else [0]):
1114 if self.expert_mode:
1115 name = "Receiving" if not is_change else "Change"
1116 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1117 account_item.addChild(seq_item)
1118 if not is_change: seq_item.setExpanded(True)
1120 seq_item = account_item
1124 for address in account[is_change]:
1125 h = self.wallet.history.get(address,[])
1129 if gap > self.wallet.gap_limit:
1134 num_tx = '*' if h == ['*'] else "%d"%len(h)
1135 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1136 self.update_receive_item(item)
1138 item.setBackgroundColor(1, QColor('red'))
1139 seq_item.addChild(item)
1142 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1143 c,u = self.wallet.get_imported_balance()
1144 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1145 l.addTopLevelItem(account_item)
1146 account_item.setExpanded(True)
1147 for address in self.wallet.imported_keys.keys():
1148 item = QTreeWidgetItem( [ address, '', '', ''] )
1149 self.update_receive_item(item)
1150 account_item.addChild(item)
1153 # we use column 1 because column 0 may be hidden
1154 l.setCurrentItem(l.topLevelItem(0),1)
1157 def update_contacts_tab(self):
1159 l = self.contacts_list
1162 for address in self.wallet.addressbook:
1163 label = self.wallet.labels.get(address,'')
1164 n = self.wallet.get_num_tx(address)
1165 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1166 item.setFont(0, QFont(MONOSPACE_FONT))
1167 # 32 = label can be edited (bool)
1168 item.setData(0,32, True)
1170 item.setData(0,33, address)
1171 l.addTopLevelItem(item)
1173 self.run_hook('update_contacts_tab', l)
1174 l.setCurrentItem(l.topLevelItem(0))
1178 def create_console_tab(self):
1179 from qt_console import Console
1180 self.console = console = Console()
1181 self.console.history = self.config.get("console-history",[])
1182 self.console.history_index = len(self.console.history)
1184 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1185 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1187 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1189 def mkfunc(f, method):
1190 return lambda *args: apply( f, (method, args, self.password_dialog ))
1192 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1193 methods[m] = mkfunc(c._run, m)
1195 console.updateNamespace(methods)
1198 def change_account(self,s):
1199 if s == _("All accounts"):
1200 self.current_account = None
1202 accounts = self.wallet.get_accounts()
1203 for k, v in accounts.items():
1205 self.current_account = k
1206 self.update_history_tab()
1207 self.update_status()
1208 self.update_receive_tab()
1210 def create_status_bar(self):
1211 self.status_text = ""
1213 sb.setFixedHeight(35)
1214 qtVersion = qVersion()
1216 update_notification = UpdateLabel(self.config)
1217 if(update_notification.new_version):
1218 sb.addPermanentWidget(update_notification)
1220 accounts = self.wallet.get_accounts()
1221 if len(accounts) > 1:
1222 from_combo = QComboBox()
1223 from_combo.addItems([_("All accounts")] + accounts.values())
1224 from_combo.setCurrentIndex(0)
1225 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1226 sb.addPermanentWidget(from_combo)
1228 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1229 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1230 if self.wallet.seed:
1231 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1232 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1233 sb.addPermanentWidget( self.password_button )
1234 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1235 if self.wallet.seed:
1236 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1237 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1238 sb.addPermanentWidget( self.status_button )
1240 self.run_hook('create_status_bar', (sb,))
1242 self.setStatusBar(sb)
1246 self.config.set_key('gui', 'lite', True)
1249 self.lite.mini.show()
1251 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1252 self.lite.main(None)
1254 def new_contact_dialog(self):
1255 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1256 address = unicode(text)
1258 if is_valid(address):
1259 self.wallet.addressbook.append(address)
1261 self.update_contacts_tab()
1262 self.update_history_tab()
1263 self.update_completions()
1265 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1267 def show_master_public_key(self):
1268 dialog = QDialog(self)
1270 dialog.setWindowTitle(_("Master Public Key"))
1272 main_text = QTextEdit()
1273 main_text.setText(self.wallet.get_master_public_key())
1274 main_text.setReadOnly(True)
1275 main_text.setMaximumHeight(170)
1276 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1278 ok_button = QPushButton(_("OK"))
1279 ok_button.setDefault(True)
1280 ok_button.clicked.connect(dialog.accept)
1282 main_layout = QGridLayout()
1283 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1285 main_layout.addWidget(main_text, 1, 0)
1286 main_layout.addWidget(qrw, 1, 1 )
1288 vbox = QVBoxLayout()
1289 vbox.addLayout(main_layout)
1290 hbox = QHBoxLayout()
1292 hbox.addWidget(ok_button)
1293 vbox.addLayout(hbox)
1295 dialog.setLayout(vbox)
1300 def show_seed_dialog(self, password):
1301 if not self.wallet.seed:
1302 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1305 seed = self.wallet.decode_seed(password)
1307 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1309 self.show_seed(seed, self.wallet.imported_keys, self)
1313 def show_seed(self, seed, imported_keys, parent=None):
1314 dialog = QDialog(parent)
1316 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1318 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1320 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1322 seed_text = QTextEdit(brainwallet)
1323 seed_text.setReadOnly(True)
1324 seed_text.setMaximumHeight(130)
1326 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1327 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1328 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1329 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1331 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1332 label2 = QLabel(msg2)
1333 label2.setWordWrap(True)
1336 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1337 logo.setMaximumWidth(60)
1339 qrw = QRCodeWidget(seed)
1341 ok_button = QPushButton(_("OK"))
1342 ok_button.setDefault(True)
1343 ok_button.clicked.connect(dialog.accept)
1345 grid = QGridLayout()
1346 #main_layout.addWidget(logo, 0, 0)
1348 grid.addWidget(logo, 0, 0)
1349 grid.addWidget(label1, 0, 1)
1351 grid.addWidget(seed_text, 1, 0, 1, 2)
1353 grid.addWidget(qrw, 0, 2, 2, 1)
1355 vbox = QVBoxLayout()
1356 vbox.addLayout(grid)
1357 vbox.addWidget(label2)
1359 hbox = QHBoxLayout()
1361 hbox.addWidget(ok_button)
1362 vbox.addLayout(hbox)
1364 dialog.setLayout(vbox)
1367 def show_qrcode(self, data, title = "QR code"):
1371 d.setWindowTitle(title)
1372 d.setMinimumSize(270, 300)
1373 vbox = QVBoxLayout()
1374 qrw = QRCodeWidget(data)
1375 vbox.addWidget(qrw, 1)
1376 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1377 hbox = QHBoxLayout()
1381 filename = "qrcode.bmp"
1382 bmp.save_qrcode(qrw.qr, filename)
1383 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1385 b = QPushButton(_("Save"))
1387 b.clicked.connect(print_qr)
1389 b = QPushButton(_("Close"))
1391 b.clicked.connect(d.accept)
1394 vbox.addLayout(hbox)
1399 def do_protect(self, func, args):
1400 if self.wallet.use_encryption:
1401 password = self.password_dialog()
1407 if args != (False,):
1408 args = (self,) + args + (password,)
1410 args = (self,password)
1415 def show_private_key(self, address, password):
1416 if not address: return
1418 pk = self.wallet.get_private_key(address, password)
1419 except BaseException, e:
1420 self.show_message(str(e))
1422 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1426 def do_sign(self, address, message, signature, password):
1428 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1429 signature.setText(sig)
1430 except BaseException, e:
1431 self.show_message(str(e))
1433 def sign_message(self, address):
1434 if not address: return
1437 d.setWindowTitle(_('Sign Message'))
1438 d.setMinimumSize(410, 290)
1440 tab_widget = QTabWidget()
1442 layout = QGridLayout(tab)
1444 sign_address = QLineEdit()
1446 sign_address.setText(address)
1447 layout.addWidget(QLabel(_('Address')), 1, 0)
1448 layout.addWidget(sign_address, 1, 1)
1450 sign_message = QTextEdit()
1451 layout.addWidget(QLabel(_('Message')), 2, 0)
1452 layout.addWidget(sign_message, 2, 1)
1453 layout.setRowStretch(2,3)
1455 sign_signature = QTextEdit()
1456 layout.addWidget(QLabel(_('Signature')), 3, 0)
1457 layout.addWidget(sign_signature, 3, 1)
1458 layout.setRowStretch(3,1)
1461 hbox = QHBoxLayout()
1462 b = QPushButton(_("Sign"))
1464 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1465 b = QPushButton(_("Close"))
1466 b.clicked.connect(d.accept)
1468 layout.addLayout(hbox, 4, 1)
1469 tab_widget.addTab(tab, _("Sign"))
1473 layout = QGridLayout(tab)
1475 verify_address = QLineEdit()
1476 layout.addWidget(QLabel(_('Address')), 1, 0)
1477 layout.addWidget(verify_address, 1, 1)
1479 verify_message = QTextEdit()
1480 layout.addWidget(QLabel(_('Message')), 2, 0)
1481 layout.addWidget(verify_message, 2, 1)
1482 layout.setRowStretch(2,3)
1484 verify_signature = QTextEdit()
1485 layout.addWidget(QLabel(_('Signature')), 3, 0)
1486 layout.addWidget(verify_signature, 3, 1)
1487 layout.setRowStretch(3,1)
1491 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1492 self.show_message(_("Signature verified"))
1493 except BaseException, e:
1494 self.show_message(str(e))
1497 hbox = QHBoxLayout()
1498 b = QPushButton(_("Verify"))
1499 b.clicked.connect(do_verify)
1501 b = QPushButton(_("Close"))
1502 b.clicked.connect(d.accept)
1504 layout.addLayout(hbox, 4, 1)
1505 tab_widget.addTab(tab, _("Verify"))
1507 vbox = QVBoxLayout()
1508 vbox.addWidget(tab_widget)
1515 def question(self, msg):
1516 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1518 def show_message(self, msg):
1519 QMessageBox.information(self, _('Message'), msg, _('OK'))
1521 def password_dialog(self ):
1528 vbox = QVBoxLayout()
1529 msg = _('Please enter your password')
1530 vbox.addWidget(QLabel(msg))
1532 grid = QGridLayout()
1534 grid.addWidget(QLabel(_('Password')), 1, 0)
1535 grid.addWidget(pw, 1, 1)
1536 vbox.addLayout(grid)
1538 vbox.addLayout(ok_cancel_buttons(d))
1541 self.run_hook('password_dialog', pw, grid, 1)
1542 if not d.exec_(): return
1543 return unicode(pw.text())
1550 def change_password_dialog( wallet, parent=None ):
1553 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1561 new_pw = QLineEdit()
1562 new_pw.setEchoMode(2)
1563 conf_pw = QLineEdit()
1564 conf_pw.setEchoMode(2)
1566 vbox = QVBoxLayout()
1568 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1569 +_('To disable wallet encryption, enter an empty new password.')) \
1570 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1572 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1573 +_("Leave these fields empty if you want to disable encryption.")
1574 vbox.addWidget(QLabel(msg))
1576 grid = QGridLayout()
1579 if wallet.use_encryption:
1580 grid.addWidget(QLabel(_('Password')), 1, 0)
1581 grid.addWidget(pw, 1, 1)
1583 grid.addWidget(QLabel(_('New Password')), 2, 0)
1584 grid.addWidget(new_pw, 2, 1)
1586 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1587 grid.addWidget(conf_pw, 3, 1)
1588 vbox.addLayout(grid)
1590 vbox.addLayout(ok_cancel_buttons(d))
1593 if not d.exec_(): return
1595 password = unicode(pw.text()) if wallet.use_encryption else None
1596 new_password = unicode(new_pw.text())
1597 new_password2 = unicode(conf_pw.text())
1600 seed = wallet.decode_seed(password)
1602 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1605 if new_password != new_password2:
1606 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1607 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1609 wallet.update_password(seed, password, new_password)
1611 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1612 parent.password_button.setIcon( icon )
1616 def generate_transaction_information_widget(self, tx):
1617 tabs = QTabWidget(self)
1620 grid_ui = QGridLayout(tab1)
1621 grid_ui.setColumnStretch(0,1)
1622 tabs.addTab(tab1, _('Outputs') )
1624 tree_widget = MyTreeWidget(self)
1625 tree_widget.setColumnCount(2)
1626 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1627 tree_widget.setColumnWidth(0, 300)
1628 tree_widget.setColumnWidth(1, 50)
1630 for address, value in tx.outputs:
1631 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1632 tree_widget.addTopLevelItem(item)
1634 tree_widget.setMaximumHeight(100)
1636 grid_ui.addWidget(tree_widget)
1639 grid_ui = QGridLayout(tab2)
1640 grid_ui.setColumnStretch(0,1)
1641 tabs.addTab(tab2, _('Inputs') )
1643 tree_widget = MyTreeWidget(self)
1644 tree_widget.setColumnCount(2)
1645 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1647 for input_line in tx.inputs:
1648 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1649 tree_widget.addTopLevelItem(item)
1651 tree_widget.setMaximumHeight(100)
1653 grid_ui.addWidget(tree_widget)
1657 def tx_dict_from_text(self, txt):
1659 tx_dict = json.loads(str(txt))
1660 assert "hex" in tx_dict.keys()
1661 assert "complete" in tx_dict.keys()
1662 if not tx_dict["complete"]:
1663 assert "input_info" in tx_dict.keys()
1665 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1670 def read_tx_from_file(self):
1671 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1675 with open(fileName, "r") as f:
1676 file_content = f.read()
1677 except (ValueError, IOError, os.error), reason:
1678 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1680 return self.tx_dict_from_text(file_content)
1684 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1686 self.wallet.signrawtransaction(tx, input_info, [], password)
1688 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1690 with open(fileName, "w+") as f:
1691 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1692 self.show_message(_("Transaction saved successfully"))
1695 except BaseException, e:
1696 self.show_message(str(e))
1699 def send_raw_transaction(self, raw_tx, dialog = ""):
1700 result, result_message = self.wallet.sendtx( raw_tx )
1702 self.show_message("Transaction successfully sent: %s" % (result_message))
1706 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1708 def do_process_from_text(self):
1709 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1712 tx_dict = self.tx_dict_from_text(text)
1714 self.create_process_transaction_window(tx_dict)
1716 def do_process_from_file(self):
1717 tx_dict = self.read_tx_from_file()
1719 self.create_process_transaction_window(tx_dict)
1721 def create_process_transaction_window(self, tx_dict):
1722 tx = Transaction(tx_dict["hex"])
1724 dialog = QDialog(self)
1725 dialog.setMinimumWidth(500)
1726 dialog.setWindowTitle(_('Process raw transaction'))
1732 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1733 l.addWidget(QLabel(_("Actions")), 4,0)
1735 if tx_dict["complete"] == False:
1736 l.addWidget(QLabel(_("Unsigned")), 3,1)
1737 if self.wallet.seed :
1738 b = QPushButton("Sign transaction")
1739 input_info = json.loads(tx_dict["input_info"])
1740 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1741 l.addWidget(b, 4, 1)
1743 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1745 l.addWidget(QLabel(_("Signed")), 3,1)
1746 b = QPushButton("Broadcast transaction")
1747 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1750 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1751 cancelButton = QPushButton(_("Cancel"))
1752 cancelButton.clicked.connect(lambda: dialog.done(0))
1753 l.addWidget(cancelButton, 4,2)
1759 def do_export_privkeys(self, password):
1760 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.")))
1763 select_export = _('Select file to export your private keys to')
1764 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1766 with open(fileName, "w+") as csvfile:
1767 transaction = csv.writer(csvfile)
1768 transaction.writerow(["address", "private_key"])
1771 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1772 transaction.writerow(["%34s"%addr,pk])
1774 self.show_message(_("Private keys exported."))
1776 except (IOError, os.error), reason:
1777 export_error_label = _("Electrum was unable to produce a private key-export.")
1778 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1780 except BaseException, e:
1781 self.show_message(str(e))
1785 def do_import_labels(self):
1786 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1787 if not labelsFile: return
1789 f = open(labelsFile, 'r')
1792 for key, value in json.loads(data).items():
1793 self.wallet.labels[key] = value
1795 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1796 except (IOError, os.error), reason:
1797 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1800 def do_export_labels(self):
1801 labels = self.wallet.labels
1803 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1805 with open(fileName, 'w+') as f:
1806 json.dump(labels, f)
1807 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1808 except (IOError, os.error), reason:
1809 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1812 def do_export_history(self):
1813 from gui_lite import csv_transaction
1814 csv_transaction(self.wallet)
1818 def do_import_privkey(self, password):
1819 if not self.wallet.imported_keys:
1820 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1821 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1822 + _('Are you sure you understand what you are doing?'), 3, 4)
1825 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1828 text = str(text).split()
1833 addr = self.wallet.import_key(key, password)
1834 except BaseException as e:
1840 addrlist.append(addr)
1842 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1844 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1845 self.update_receive_tab()
1846 self.update_history_tab()
1849 def settings_dialog(self):
1851 d.setWindowTitle(_('Electrum Settings'))
1853 vbox = QVBoxLayout()
1855 tabs = QTabWidget(self)
1856 self.settings_tab = tabs
1857 vbox.addWidget(tabs)
1860 grid_ui = QGridLayout(tab1)
1861 grid_ui.setColumnStretch(0,1)
1862 tabs.addTab(tab1, _('Display') )
1864 nz_label = QLabel(_('Display zeros'))
1865 grid_ui.addWidget(nz_label, 0, 0)
1866 nz_e = AmountEdit(None,True)
1867 nz_e.setText("%d"% self.wallet.num_zeros)
1868 grid_ui.addWidget(nz_e, 0, 1)
1869 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1870 grid_ui.addWidget(HelpButton(msg), 0, 2)
1871 if not self.config.is_modifiable('num_zeros'):
1872 for w in [nz_e, nz_label]: w.setEnabled(False)
1874 lang_label=QLabel(_('Language') + ':')
1875 grid_ui.addWidget(lang_label, 1, 0)
1876 lang_combo = QComboBox()
1877 from i18n import languages
1878 lang_combo.addItems(languages.values())
1880 index = languages.keys().index(self.config.get("language",''))
1883 lang_combo.setCurrentIndex(index)
1884 grid_ui.addWidget(lang_combo, 1, 1)
1885 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1886 if not self.config.is_modifiable('language'):
1887 for w in [lang_combo, lang_label]: w.setEnabled(False)
1889 currencies = self.exchanger.get_currencies()
1890 currencies.insert(0, "None")
1892 cur_label=QLabel(_('Currency') + ':')
1893 grid_ui.addWidget(cur_label , 2, 0)
1894 cur_combo = QComboBox()
1895 cur_combo.addItems(currencies)
1897 index = currencies.index(self.config.get('currency', "None"))
1900 cur_combo.setCurrentIndex(index)
1901 grid_ui.addWidget(cur_combo, 2, 1)
1902 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1904 expert_cb = QCheckBox(_('Expert mode'))
1905 expert_cb.setChecked(self.expert_mode)
1906 grid_ui.addWidget(expert_cb, 3, 0)
1907 hh = _('In expert mode, your client will:') + '\n' \
1908 + _(' - Show change addresses in the Receive tab') + '\n' \
1909 + _(' - Display the balance of each address') + '\n' \
1910 + _(' - Add freeze/prioritize actions to addresses.')
1911 grid_ui.addWidget(HelpButton(hh), 3, 2)
1912 grid_ui.setRowStretch(4,1)
1916 grid_wallet = QGridLayout(tab2)
1917 grid_wallet.setColumnStretch(0,1)
1918 tabs.addTab(tab2, _('Wallet') )
1920 fee_label = QLabel(_('Transaction fee'))
1921 grid_wallet.addWidget(fee_label, 0, 0)
1922 fee_e = AmountEdit(self.base_unit)
1923 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1924 grid_wallet.addWidget(fee_e, 0, 2)
1925 msg = _('Fee per kilobyte of transaction.') + ' ' \
1926 + _('Recommended value') + ': ' + self.format_amount(20000)
1927 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1928 if not self.config.is_modifiable('fee_per_kb'):
1929 for w in [fee_e, fee_label]: w.setEnabled(False)
1931 usechange_cb = QCheckBox(_('Use change addresses'))
1932 usechange_cb.setChecked(self.wallet.use_change)
1933 grid_wallet.addWidget(usechange_cb, 1, 0)
1934 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1935 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1937 gap_label = QLabel(_('Gap limit'))
1938 grid_wallet.addWidget(gap_label, 2, 0)
1939 gap_e = AmountEdit(None,True)
1940 gap_e.setText("%d"% self.wallet.gap_limit)
1941 grid_wallet.addWidget(gap_e, 2, 2)
1942 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1943 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1944 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1945 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1946 + _('Warning') + ': ' \
1947 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1948 + _('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'
1949 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1950 if not self.config.is_modifiable('gap_limit'):
1951 for w in [gap_e, gap_label]: w.setEnabled(False)
1953 units = ['BTC', 'mBTC']
1954 unit_label = QLabel(_('Base unit'))
1955 grid_wallet.addWidget(unit_label, 3, 0)
1956 unit_combo = QComboBox()
1957 unit_combo.addItems(units)
1958 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1959 grid_wallet.addWidget(unit_combo, 3, 2)
1960 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1961 + '\n1BTC=1000mBTC.\n' \
1962 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1963 grid_wallet.setRowStretch(4,1)
1968 grid_io = QGridLayout(tab3)
1969 grid_io.setColumnStretch(0,1)
1970 tabs.addTab(tab3, _('Import/Export') )
1972 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1973 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1974 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1975 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1977 grid_io.addWidget(QLabel(_('History')), 2, 0)
1978 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1979 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1981 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1983 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1984 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1985 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1987 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1988 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1989 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1990 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1991 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1994 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1995 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1996 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1997 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1999 grid_io.setRowStretch(6,1)
2004 tab5 = QScrollArea()
2005 tab5.setEnabled(True)
2006 tab5.setWidgetResizable(True)
2008 grid_plugins = QGridLayout()
2009 grid_plugins.setColumnStretch(0,1)
2012 w.setLayout(grid_plugins)
2014 tab5.setMaximumSize(tab3.size()) # optional
2016 w.setMinimumHeight(len(self.plugins)*35)
2018 tabs.addTab(tab5, _('Plugins') )
2019 def mk_toggle(cb, p):
2020 return lambda: cb.setChecked(p.toggle())
2021 for i, p in enumerate(self.plugins):
2023 name, description = p.get_info()
2024 cb = QCheckBox(name)
2025 cb.setDisabled(not p.is_available())
2026 cb.setChecked(p.is_enabled())
2027 cb.clicked.connect(mk_toggle(cb,p))
2028 grid_plugins.addWidget(cb, i, 0)
2029 if p.requires_settings():
2030 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2031 grid_plugins.addWidget(HelpButton(description), i, 2)
2033 print_msg("Error: cannot display plugin", p)
2034 traceback.print_exc(file=sys.stdout)
2035 grid_plugins.setRowStretch(i+1,1)
2037 self.run_hook('create_settings_tab', tabs)
2039 vbox.addLayout(ok_cancel_buttons(d))
2043 if not d.exec_(): return
2045 fee = unicode(fee_e.text())
2047 fee = self.read_amount(fee)
2049 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2052 if self.wallet.fee != fee:
2053 self.wallet.fee = fee
2056 nz = unicode(nz_e.text())
2061 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2064 if self.wallet.num_zeros != nz:
2065 self.wallet.num_zeros = nz
2066 self.config.set_key('num_zeros', nz, True)
2067 self.update_history_tab()
2068 self.update_receive_tab()
2070 usechange_result = usechange_cb.isChecked()
2071 if self.wallet.use_change != usechange_result:
2072 self.wallet.use_change = usechange_result
2073 self.config.set_key('use_change', self.wallet.use_change, True)
2075 unit_result = units[unit_combo.currentIndex()]
2076 if self.base_unit() != unit_result:
2077 self.decimal_point = 8 if unit_result == 'BTC' else 5
2078 self.config.set_key('decimal_point', self.decimal_point, True)
2079 self.update_history_tab()
2080 self.update_status()
2083 n = int(gap_e.text())
2085 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2088 if self.wallet.gap_limit != n:
2089 r = self.wallet.change_gap_limit(n)
2091 self.update_receive_tab()
2092 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2094 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2096 need_restart = False
2098 lang_request = languages.keys()[lang_combo.currentIndex()]
2099 if lang_request != self.config.get('language'):
2100 self.config.set_key("language", lang_request, True)
2103 cur_request = str(currencies[cur_combo.currentIndex()])
2104 if cur_request != self.config.get('currency', "None"):
2105 self.config.set_key('currency', cur_request, True)
2106 self.update_wallet()
2108 self.run_hook('close_settings_dialog')
2111 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2113 self.receive_tab_set_mode(expert_cb.isChecked())
2117 def network_dialog(wallet, parent=None):
2118 interface = wallet.interface
2120 if interface.is_connected:
2121 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2123 status = _("Not connected")
2124 server = interface.server
2127 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2128 server = interface.server
2130 plist, servers_list = interface.get_servers_list()
2134 d.setWindowTitle(_('Server'))
2135 d.setMinimumSize(375, 20)
2137 vbox = QVBoxLayout()
2140 hbox = QHBoxLayout()
2142 l.setPixmap(QPixmap(":icons/network.png"))
2145 hbox.addWidget(QLabel(status))
2147 vbox.addLayout(hbox)
2151 grid = QGridLayout()
2153 vbox.addLayout(grid)
2156 server_protocol = QComboBox()
2157 server_host = QLineEdit()
2158 server_host.setFixedWidth(200)
2159 server_port = QLineEdit()
2160 server_port.setFixedWidth(60)
2162 protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2163 protocol_letters = 'thsg'
2164 server_protocol.addItems(protocol_names)
2166 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2167 grid.addWidget(server_protocol, 0, 1)
2168 grid.addWidget(server_host, 0, 2)
2169 grid.addWidget(server_port, 0, 3)
2171 def change_protocol(p):
2172 protocol = protocol_letters[p]
2173 host = unicode(server_host.text())
2174 pp = plist.get(host,DEFAULT_PORTS)
2175 if protocol not in pp.keys():
2176 protocol = pp.keys()[0]
2178 server_host.setText( host )
2179 server_port.setText( port )
2181 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2183 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2184 servers_list_widget = QTreeWidget(parent)
2185 servers_list_widget.setHeaderLabels( [ label, _('Limit') ] )
2186 servers_list_widget.setMaximumHeight(150)
2187 servers_list_widget.setColumnWidth(0, 240)
2188 for _host in servers_list.keys():
2189 pruning_level = servers_list[_host].get('pruning','')
2190 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2191 servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2193 def change_server(host, protocol=None):
2194 pp = plist.get(host,DEFAULT_PORTS)
2196 port = pp.get(protocol)
2197 if not port: protocol = None
2200 if 's' in pp.keys():
2202 port = pp.get(protocol)
2204 protocol = pp.keys()[0]
2205 port = pp.get(protocol)
2207 server_host.setText( host )
2208 server_port.setText( port )
2209 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2211 if not plist: return
2212 for p in protocol_letters:
2213 i = protocol_letters.index(p)
2214 j = server_protocol.model().index(i,0)
2215 if p not in pp.keys() and interface.is_connected:
2216 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2218 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2221 host, port, protocol = server.split(':')
2222 change_server(host,protocol)
2224 servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'),
2225 lambda x,y: change_server(unicode(x.text(0))))
2226 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2228 if not wallet.config.is_modifiable('server'):
2229 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2232 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2233 autocycle_cb.setChecked(wallet.config.get('auto_cycle', True))
2234 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2235 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2238 proxy_mode = QComboBox()
2239 proxy_host = QLineEdit()
2240 proxy_host.setFixedWidth(200)
2241 proxy_port = QLineEdit()
2242 proxy_port.setFixedWidth(60)
2243 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2245 def check_for_disable(index = False):
2246 if proxy_mode.currentText() != 'NONE':
2247 proxy_host.setEnabled(True)
2248 proxy_port.setEnabled(True)
2250 proxy_host.setEnabled(False)
2251 proxy_port.setEnabled(False)
2254 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2256 if not wallet.config.is_modifiable('proxy'):
2257 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2259 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2260 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2261 proxy_host.setText(proxy_config.get("host"))
2262 proxy_port.setText(proxy_config.get("port"))
2264 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2265 grid.addWidget(proxy_mode, 2, 1)
2266 grid.addWidget(proxy_host, 2, 2)
2267 grid.addWidget(proxy_port, 2, 3)
2270 vbox.addLayout(ok_cancel_buttons(d))
2273 if not d.exec_(): return
2275 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2276 if proxy_mode.currentText() != 'NONE':
2277 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2281 wallet.config.set_key("proxy", proxy, True)
2282 wallet.config.set_key("server", server, True)
2283 interface.set_server(server, proxy)
2284 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2287 def closeEvent(self, event):
2289 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2290 self.save_column_widths()
2291 self.config.set_key("console-history",self.console.history[-50:])
2297 def __init__(self, wallet, config, app=None):
2298 self.wallet = wallet
2299 self.config = config
2301 self.app = QApplication(sys.argv)
2304 def restore_or_create(self):
2305 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2306 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2307 if r==2: return None
2308 return 'restore' if r==1 else 'create'
2311 def verify_seed(self):
2312 r = self.seed_dialog(False)
2313 if r != self.wallet.seed:
2314 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2321 def seed_dialog(self, is_restore=True):
2325 vbox = QVBoxLayout()
2327 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2329 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2331 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2334 label.setWordWrap(True)
2335 vbox.addWidget(label)
2337 seed_e = QTextEdit()
2338 seed_e.setMaximumHeight(100)
2339 vbox.addWidget(seed_e)
2342 grid = QGridLayout()
2344 gap_e = AmountEdit(None, True)
2346 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2347 grid.addWidget(gap_e, 2, 1)
2348 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2349 vbox.addLayout(grid)
2351 vbox.addLayout(ok_cancel_buttons(d))
2354 if not d.exec_(): return
2357 seed = str(seed_e.toPlainText())
2361 seed = mnemonic.mn_decode( seed.split() )
2363 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2367 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2374 gap = int(unicode(gap_e.text()))
2376 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2381 def network_dialog(self):
2382 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2385 def show_seed(self):
2386 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2388 def password_dialog(self):
2389 if self.wallet.seed:
2390 ElectrumWindow.change_password_dialog(self.wallet)
2393 def restore_wallet(self):
2394 wallet = self.wallet
2395 # wait until we are connected, because the user might have selected another server
2396 if not wallet.interface.is_connected:
2397 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2398 waiting_dialog(waiting)
2400 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2401 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2403 wallet.set_up_to_date(False)
2404 wallet.interface.poke('synchronizer')
2405 waiting_dialog(waiting)
2406 if wallet.is_found():
2407 print_error( "Recovery successful" )
2409 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2416 w = ElectrumWindow(self.wallet, self.config)
2417 if url: w.set_url(url)