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 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.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
272 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
273 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
274 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
276 self.expert_mode = config.get('classic_expert_mode', False)
277 self.decimal_point = config.get('decimal_point', 8)
279 set_language(config.get('language'))
281 self.funds_error = False
282 self.completions = QStringListModel()
284 self.tabs = tabs = QTabWidget(self)
285 self.column_widths = self.config.get("column_widths", default_column_widths )
286 tabs.addTab(self.create_history_tab(), _('History') )
287 tabs.addTab(self.create_send_tab(), _('Send') )
288 tabs.addTab(self.create_receive_tab(), _('Receive') )
289 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
290 tabs.addTab(self.create_console_tab(), _('Console') )
291 tabs.setMinimumSize(600, 400)
292 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
293 self.setCentralWidget(tabs)
295 g = self.config.get("winpos-qt",[100, 100, 840, 400])
296 self.setGeometry(g[0], g[1], g[2], g[3])
297 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
298 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
299 self.setWindowTitle( title )
301 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
302 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
303 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
304 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
306 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
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 self.run_hook('timer_actions')
411 def format_amount(self, x, is_diff=False):
412 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
414 def read_amount(self, x):
415 if x in['.', '']: return None
416 p = pow(10, self.decimal_point)
417 return int( p * Decimal(x) )
420 assert self.decimal_point in [5,8]
421 return "BTC" if self.decimal_point == 8 else "mBTC"
423 def update_status(self):
424 if self.wallet.interface and self.wallet.interface.is_connected:
425 if not self.wallet.up_to_date:
426 text = _("Synchronizing...")
427 icon = QIcon(":icons/status_waiting.png")
429 c, u = self.wallet.get_account_balance(self.current_account)
430 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
431 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
432 text += self.create_quote_text(Decimal(c+u)/100000000)
433 icon = QIcon(":icons/status_connected.png")
435 text = _("Not connected")
436 icon = QIcon(":icons/status_disconnected.png")
438 self.status_text = text
439 self.statusBar().showMessage(text)
440 self.status_button.setIcon( icon )
442 def update_wallet(self):
444 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
445 self.update_history_tab()
446 self.update_receive_tab()
447 self.update_contacts_tab()
448 self.update_completions()
451 def create_quote_text(self, btc_balance):
452 quote_currency = self.config.get("currency", "None")
453 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
454 if quote_balance is None:
457 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
460 def create_history_tab(self):
461 self.history_list = l = MyTreeWidget(self)
463 for i,width in enumerate(self.column_widths['history']):
464 l.setColumnWidth(i, width)
465 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
466 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
467 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
469 l.setContextMenuPolicy(Qt.CustomContextMenu)
470 l.customContextMenuRequested.connect(self.create_history_menu)
474 def create_history_menu(self, position):
475 self.history_list.selectedIndexes()
476 item = self.history_list.currentItem()
478 tx_hash = str(item.data(0, Qt.UserRole).toString())
479 if not tx_hash: return
481 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
482 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
483 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
484 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
487 def show_tx_details(self, tx):
488 dialog = QDialog(self)
490 dialog.setWindowTitle(_("Transaction Details"))
492 dialog.setLayout(vbox)
493 dialog.setMinimumSize(600,300)
496 if tx_hash in self.wallet.transactions.keys():
497 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
498 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
500 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
506 vbox.addWidget(QLabel("Transaction ID:"))
507 e = QLineEdit(tx_hash)
511 vbox.addWidget(QLabel("Date: %s"%time_str))
512 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
515 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
516 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
518 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
519 vbox.addWidget(QLabel("Transaction fee: unknown"))
521 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
523 vbox.addWidget( self.generate_transaction_information_widget(tx) )
525 ok_button = QPushButton(_("Close"))
526 ok_button.setDefault(True)
527 ok_button.clicked.connect(dialog.accept)
531 hbox.addWidget(ok_button)
535 def tx_label_clicked(self, item, column):
536 if column==2 and item.isSelected():
538 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
539 self.history_list.editItem( item, column )
540 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
543 def tx_label_changed(self, item, column):
547 tx_hash = str(item.data(0, Qt.UserRole).toString())
548 tx = self.wallet.transactions.get(tx_hash)
549 text = unicode( item.text(2) )
550 self.set_label(tx_hash, text)
552 item.setForeground(2, QBrush(QColor('black')))
554 text = self.wallet.get_default_label(tx_hash)
555 item.setText(2, text)
556 item.setForeground(2, QBrush(QColor('gray')))
560 def edit_label(self, is_recv):
561 l = self.receive_list if is_recv else self.contacts_list
562 item = l.currentItem()
563 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
564 l.editItem( item, 1 )
565 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
569 def address_label_clicked(self, item, column, l, column_addr, column_label):
570 if column == column_label and item.isSelected():
571 is_editable = item.data(0, 32).toBool()
574 addr = unicode( item.text(column_addr) )
575 label = unicode( item.text(column_label) )
576 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
577 l.editItem( item, column )
578 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
581 def address_label_changed(self, item, column, l, column_addr, column_label):
582 if column == column_label:
583 addr = unicode( item.text(column_addr) )
584 text = unicode( item.text(column_label) )
585 is_editable = item.data(0, 32).toBool()
589 changed = self.set_label(addr, text)
591 self.update_history_tab()
592 self.update_completions()
594 self.current_item_changed(item)
596 self.run_hook('item_changed', item, column)
599 def current_item_changed(self, a):
600 self.run_hook('current_item_changed', a)
604 def update_history_tab(self):
606 self.history_list.clear()
607 for item in self.wallet.get_tx_history(self.current_account):
608 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
611 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
616 time_str = 'unverified'
617 icon = QIcon(":icons/unconfirmed.png")
620 icon = QIcon(":icons/unconfirmed.png")
622 icon = QIcon(":icons/clock%d.png"%conf)
624 icon = QIcon(":icons/confirmed.png")
626 if value is not None:
627 v_str = self.format_amount(value, True)
631 balance_str = self.format_amount(balance)
634 label, is_default_label = self.wallet.get_label(tx_hash)
636 label = _('Pruned transaction outputs')
637 is_default_label = False
639 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
640 item.setFont(2, QFont(MONOSPACE_FONT))
641 item.setFont(3, QFont(MONOSPACE_FONT))
642 item.setFont(4, QFont(MONOSPACE_FONT))
644 item.setForeground(3, QBrush(QColor("#BC1E1E")))
646 item.setData(0, Qt.UserRole, tx_hash)
647 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
649 item.setForeground(2, QBrush(QColor('grey')))
651 item.setIcon(0, icon)
652 self.history_list.insertTopLevelItem(0,item)
655 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
658 def create_send_tab(self):
663 grid.setColumnMinimumWidth(3,300)
664 grid.setColumnStretch(5,1)
667 self.payto_e = QLineEdit()
668 grid.addWidget(QLabel(_('Pay to')), 1, 0)
669 grid.addWidget(self.payto_e, 1, 1, 1, 3)
671 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)
673 completer = QCompleter()
674 completer.setCaseSensitivity(False)
675 self.payto_e.setCompleter(completer)
676 completer.setModel(self.completions)
678 self.message_e = QLineEdit()
679 grid.addWidget(QLabel(_('Description')), 2, 0)
680 grid.addWidget(self.message_e, 2, 1, 1, 3)
681 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)
683 self.amount_e = AmountEdit(self.base_unit)
684 grid.addWidget(QLabel(_('Amount')), 3, 0)
685 grid.addWidget(self.amount_e, 3, 1, 1, 2)
686 grid.addWidget(HelpButton(
687 _('Amount to be sent.') + '\n\n' \
688 + _('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.') \
689 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
691 self.fee_e = AmountEdit(self.base_unit)
692 grid.addWidget(QLabel(_('Fee')), 4, 0)
693 grid.addWidget(self.fee_e, 4, 1, 1, 2)
694 grid.addWidget(HelpButton(
695 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
696 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
697 + _('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)
700 b = EnterButton(_("Send"), self.do_send)
702 b = EnterButton(_("Create unsigned transaction"), self.do_send)
703 grid.addWidget(b, 6, 1)
705 b = EnterButton(_("Clear"),self.do_clear)
706 grid.addWidget(b, 6, 2)
708 self.payto_sig = QLabel('')
709 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
711 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
712 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
721 def entry_changed( is_fee ):
722 self.funds_error = False
724 if self.amount_e.is_shortcut:
725 self.amount_e.is_shortcut = False
726 c, u = self.wallet.get_account_balance(self.current_account)
727 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
728 fee = self.wallet.estimated_fee(inputs)
730 self.amount_e.setText( self.format_amount(amount) )
731 self.fee_e.setText( self.format_amount( fee ) )
734 amount = self.read_amount(str(self.amount_e.text()))
735 fee = self.read_amount(str(self.fee_e.text()))
737 if not is_fee: fee = None
740 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
742 self.fee_e.setText( self.format_amount( fee ) )
745 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
746 text = self.status_text
749 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
750 self.funds_error = True
751 text = _( "Not enough funds" )
752 c, u = self.wallet.get_frozen_balance()
753 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
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 = self.read_amount(unicode( self.amount_e.text()))
798 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
801 fee = self.read_amount(unicode( self.fee_e.text()))
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)
846 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
848 if label and self.wallet.labels.get(address) != label:
849 if self.question('Give label "%s" to address %s ?'%(label,address)):
850 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
851 self.wallet.addressbook.append(address)
852 self.set_label(address, label)
854 self.run_hook('set_url', url, self.show_message, self.question)
856 self.tabs.setCurrentIndex(1)
857 label = self.wallet.labels.get(address)
858 m_addr = label + ' <'+ address +'>' if label else address
859 self.payto_e.setText(m_addr)
861 self.message_e.setText(message)
862 self.amount_e.setText(amount)
864 self.set_frozen(self.payto_e,True)
865 self.set_frozen(self.amount_e,True)
866 self.set_frozen(self.message_e,True)
867 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
869 self.payto_sig.setVisible(False)
872 self.payto_sig.setVisible(False)
873 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
875 self.set_frozen(e,False)
878 def set_frozen(self,entry,frozen):
880 entry.setReadOnly(True)
881 entry.setFrame(False)
883 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
884 entry.setPalette(palette)
886 entry.setReadOnly(False)
889 palette.setColor(entry.backgroundRole(), QColor('white'))
890 entry.setPalette(palette)
893 def toggle_freeze(self,addr):
895 if addr in self.wallet.frozen_addresses:
896 self.wallet.unfreeze(addr)
898 self.wallet.freeze(addr)
899 self.update_receive_tab()
901 def toggle_priority(self,addr):
903 if addr in self.wallet.prioritized_addresses:
904 self.wallet.unprioritize(addr)
906 self.wallet.prioritize(addr)
907 self.update_receive_tab()
910 def create_list_tab(self, headers):
911 "generic tab creation method"
912 l = MyTreeWidget(self)
913 l.setColumnCount( len(headers) )
914 l.setHeaderLabels( headers )
924 vbox.addWidget(buttons)
929 buttons.setLayout(hbox)
934 def create_receive_tab(self):
935 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
936 l.setContextMenuPolicy(Qt.CustomContextMenu)
937 l.customContextMenuRequested.connect(self.create_receive_menu)
938 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
939 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
940 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
941 self.receive_list = l
942 self.receive_buttons_hbox = hbox
947 def receive_tab_set_mode(self, i):
948 self.save_column_widths()
949 self.expert_mode = (i == 1)
950 self.config.set_key('classic_expert_mode', self.expert_mode, True)
952 self.update_receive_tab()
955 def save_column_widths(self):
956 if not self.expert_mode:
957 widths = [ self.receive_list.columnWidth(0) ]
960 for i in range(self.receive_list.columnCount() -1):
961 widths.append(self.receive_list.columnWidth(i))
962 self.column_widths["receive"][self.expert_mode] = widths
964 self.column_widths["history"] = []
965 for i in range(self.history_list.columnCount() - 1):
966 self.column_widths["history"].append(self.history_list.columnWidth(i))
968 self.column_widths["contacts"] = []
969 for i in range(self.contacts_list.columnCount() - 1):
970 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
972 self.config.set_key("column_widths", self.column_widths, True)
975 def create_contacts_tab(self):
976 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
977 l.setContextMenuPolicy(Qt.CustomContextMenu)
978 l.customContextMenuRequested.connect(self.create_contact_menu)
979 for i,width in enumerate(self.column_widths['contacts']):
980 l.setColumnWidth(i, width)
982 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
983 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
984 self.contacts_list = l
985 self.contacts_buttons_hbox = hbox
986 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
991 def delete_imported_key(self, addr):
992 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
993 self.wallet.imported_keys.pop(addr)
994 self.update_receive_tab()
995 self.update_history_tab()
999 def create_receive_menu(self, position):
1000 # fixme: this function apparently has a side effect.
1001 # if it is not called the menu pops up several times
1002 #self.receive_list.selectedIndexes()
1004 item = self.receive_list.itemAt(position)
1006 addr = unicode(item.text(0))
1007 if not is_valid(addr):
1008 item.setExpanded(not item.isExpanded())
1011 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1012 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1013 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1014 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1015 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1016 if addr in self.wallet.imported_keys:
1017 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1019 if self.expert_mode:
1020 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1021 menu.addAction(t, lambda: self.toggle_freeze(addr))
1022 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1023 menu.addAction(t, lambda: self.toggle_priority(addr))
1025 self.run_hook('receive_menu', menu)
1026 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1029 def payto(self, addr):
1031 label = self.wallet.labels.get(addr)
1032 m_addr = label + ' <' + addr + '>' if label else addr
1033 self.tabs.setCurrentIndex(1)
1034 self.payto_e.setText(m_addr)
1035 self.amount_e.setFocus()
1038 def delete_contact(self, x):
1039 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1040 if x in self.wallet.addressbook:
1041 self.wallet.addressbook.remove(x)
1042 self.set_label(x, None)
1043 self.update_history_tab()
1044 self.update_contacts_tab()
1045 self.update_completions()
1048 def create_contact_menu(self, position):
1049 item = self.contacts_list.itemAt(position)
1051 addr = unicode(item.text(0))
1052 label = unicode(item.text(1))
1053 is_editable = item.data(0,32).toBool()
1054 payto_addr = item.data(0,33).toString()
1056 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1057 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1058 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1060 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1061 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1063 self.run_hook('create_contact_menu', menu, item)
1064 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1067 def update_receive_item(self, item):
1068 item.setFont(0, QFont(MONOSPACE_FONT))
1069 address = str(item.data(0,0).toString())
1070 label = self.wallet.labels.get(address,'')
1071 item.setData(1,0,label)
1072 item.setData(0,32, True) # is editable
1074 self.run_hook('update_receive_item', address, item)
1076 c, u = self.wallet.get_addr_balance(address)
1077 balance = self.format_amount(c + u)
1078 item.setData(2,0,balance)
1080 if self.expert_mode:
1081 if address in self.wallet.frozen_addresses:
1082 item.setBackgroundColor(0, QColor('lightblue'))
1083 elif address in self.wallet.prioritized_addresses:
1084 item.setBackgroundColor(0, QColor('lightgreen'))
1087 def update_receive_tab(self):
1088 l = self.receive_list
1091 l.setColumnHidden(2, not self.expert_mode)
1092 l.setColumnHidden(3, not self.expert_mode)
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, '', self.format_amount(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'), '', self.format_amount(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 generate_transaction_information_widget(self, tx):
1614 tabs = QTabWidget(self)
1617 grid_ui = QGridLayout(tab1)
1618 grid_ui.setColumnStretch(0,1)
1619 tabs.addTab(tab1, _('Outputs') )
1621 tree_widget = MyTreeWidget(self)
1622 tree_widget.setColumnCount(2)
1623 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1624 tree_widget.setColumnWidth(0, 300)
1625 tree_widget.setColumnWidth(1, 50)
1627 for address, value in tx.outputs:
1628 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1629 tree_widget.addTopLevelItem(item)
1631 tree_widget.setMaximumHeight(100)
1633 grid_ui.addWidget(tree_widget)
1636 grid_ui = QGridLayout(tab2)
1637 grid_ui.setColumnStretch(0,1)
1638 tabs.addTab(tab2, _('Inputs') )
1640 tree_widget = MyTreeWidget(self)
1641 tree_widget.setColumnCount(2)
1642 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1644 for input_line in tx.inputs:
1645 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1646 tree_widget.addTopLevelItem(item)
1648 tree_widget.setMaximumHeight(100)
1650 grid_ui.addWidget(tree_widget)
1654 def tx_dict_from_text(self, txt):
1656 tx_dict = json.loads(str(txt))
1657 assert "hex" in tx_dict.keys()
1658 assert "complete" in tx_dict.keys()
1659 if not tx_dict["complete"]:
1660 assert "input_info" in tx_dict.keys()
1662 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1667 def read_tx_from_file(self):
1668 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1672 with open(fileName, "r") as f:
1673 file_content = f.read()
1674 except (ValueError, IOError, os.error), reason:
1675 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1677 return self.tx_dict_from_text(file_content)
1681 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1683 self.wallet.signrawtransaction(tx, input_info, [], password)
1685 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1687 with open(fileName, "w+") as f:
1688 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1689 self.show_message(_("Transaction saved successfully"))
1692 except BaseException, e:
1693 self.show_message(str(e))
1696 def send_raw_transaction(self, raw_tx, dialog = ""):
1697 result, result_message = self.wallet.sendtx( raw_tx )
1699 self.show_message("Transaction successfully sent: %s" % (result_message))
1703 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1705 def do_process_from_text(self):
1706 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1709 tx_dict = self.tx_dict_from_text(text)
1711 self.create_process_transaction_window(tx_dict)
1713 def do_process_from_file(self):
1714 tx_dict = self.read_tx_from_file()
1716 self.create_process_transaction_window(tx_dict)
1718 def create_process_transaction_window(self, tx_dict):
1719 tx = Transaction(tx_dict["hex"])
1721 dialog = QDialog(self)
1722 dialog.setMinimumWidth(500)
1723 dialog.setWindowTitle(_('Process raw transaction'))
1729 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1730 l.addWidget(QLabel(_("Actions")), 4,0)
1732 if tx_dict["complete"] == False:
1733 l.addWidget(QLabel(_("Unsigned")), 3,1)
1734 if self.wallet.seed :
1735 b = QPushButton("Sign transaction")
1736 input_info = json.loads(tx_dict["input_info"])
1737 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1738 l.addWidget(b, 4, 1)
1740 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1742 l.addWidget(QLabel(_("Signed")), 3,1)
1743 b = QPushButton("Broadcast transaction")
1744 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1747 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1748 cancelButton = QPushButton(_("Cancel"))
1749 cancelButton.clicked.connect(lambda: dialog.done(0))
1750 l.addWidget(cancelButton, 4,2)
1756 def do_export_privkeys(self, password):
1757 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.")))
1760 select_export = _('Select file to export your private keys to')
1761 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1763 with open(fileName, "w+") as csvfile:
1764 transaction = csv.writer(csvfile)
1765 transaction.writerow(["address", "private_key"])
1768 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1769 transaction.writerow(["%34s"%addr,pk])
1771 self.show_message(_("Private keys exported."))
1773 except (IOError, os.error), reason:
1774 export_error_label = _("Electrum was unable to produce a private key-export.")
1775 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1777 except BaseException, e:
1778 self.show_message(str(e))
1782 def do_import_labels(self):
1783 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1784 if not labelsFile: return
1786 f = open(labelsFile, 'r')
1789 for key, value in json.loads(data).items():
1790 self.wallet.labels[key] = value
1792 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1793 except (IOError, os.error), reason:
1794 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1797 def do_export_labels(self):
1798 labels = self.wallet.labels
1800 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1802 with open(fileName, 'w+') as f:
1803 json.dump(labels, f)
1804 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1805 except (IOError, os.error), reason:
1806 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1809 def do_export_history(self):
1810 from gui_lite import csv_transaction
1811 csv_transaction(self.wallet)
1815 def do_import_privkey(self, password):
1816 if not self.wallet.imported_keys:
1817 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1818 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1819 + _('Are you sure you understand what you are doing?'), 3, 4)
1822 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1825 text = str(text).split()
1830 addr = self.wallet.import_key(key, password)
1831 except BaseException as e:
1837 addrlist.append(addr)
1839 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1841 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1842 self.update_receive_tab()
1843 self.update_history_tab()
1846 def settings_dialog(self):
1848 d.setWindowTitle(_('Electrum Settings'))
1850 vbox = QVBoxLayout()
1852 tabs = QTabWidget(self)
1853 self.settings_tab = tabs
1854 vbox.addWidget(tabs)
1857 grid_ui = QGridLayout(tab1)
1858 grid_ui.setColumnStretch(0,1)
1859 tabs.addTab(tab1, _('Display') )
1861 nz_label = QLabel(_('Display zeros'))
1862 grid_ui.addWidget(nz_label, 0, 0)
1863 nz_e = AmountEdit(None,True)
1864 nz_e.setText("%d"% self.wallet.num_zeros)
1865 grid_ui.addWidget(nz_e, 0, 1)
1866 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1867 grid_ui.addWidget(HelpButton(msg), 0, 2)
1868 if not self.config.is_modifiable('num_zeros'):
1869 for w in [nz_e, nz_label]: w.setEnabled(False)
1871 lang_label=QLabel(_('Language') + ':')
1872 grid_ui.addWidget(lang_label, 1, 0)
1873 lang_combo = QComboBox()
1874 from i18n import languages
1875 lang_combo.addItems(languages.values())
1877 index = languages.keys().index(self.config.get("language",''))
1880 lang_combo.setCurrentIndex(index)
1881 grid_ui.addWidget(lang_combo, 1, 1)
1882 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1883 if not self.config.is_modifiable('language'):
1884 for w in [lang_combo, lang_label]: w.setEnabled(False)
1886 currencies = self.exchanger.get_currencies()
1887 currencies.insert(0, "None")
1889 cur_label=QLabel(_('Currency') + ':')
1890 grid_ui.addWidget(cur_label , 2, 0)
1891 cur_combo = QComboBox()
1892 cur_combo.addItems(currencies)
1894 index = currencies.index(self.config.get('currency', "None"))
1897 cur_combo.setCurrentIndex(index)
1898 grid_ui.addWidget(cur_combo, 2, 1)
1899 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1901 expert_cb = QCheckBox(_('Expert mode'))
1902 expert_cb.setChecked(self.expert_mode)
1903 grid_ui.addWidget(expert_cb, 3, 0)
1904 hh = _('In expert mode, your client will:') + '\n' \
1905 + _(' - Show change addresses in the Receive tab') + '\n' \
1906 + _(' - Display the balance of each address') + '\n' \
1907 + _(' - Add freeze/prioritize actions to addresses.')
1908 grid_ui.addWidget(HelpButton(hh), 3, 2)
1909 grid_ui.setRowStretch(4,1)
1913 grid_wallet = QGridLayout(tab2)
1914 grid_wallet.setColumnStretch(0,1)
1915 tabs.addTab(tab2, _('Wallet') )
1917 fee_label = QLabel(_('Transaction fee'))
1918 grid_wallet.addWidget(fee_label, 0, 0)
1919 fee_e = AmountEdit(self.base_unit)
1920 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1921 grid_wallet.addWidget(fee_e, 0, 2)
1922 msg = _('Fee per kilobyte of transaction.') + ' ' \
1923 + _('Recommended value') + ': ' + self.format_amount(20000)
1924 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1925 if not self.config.is_modifiable('fee_per_kb'):
1926 for w in [fee_e, fee_label]: w.setEnabled(False)
1928 usechange_cb = QCheckBox(_('Use change addresses'))
1929 usechange_cb.setChecked(self.wallet.use_change)
1930 grid_wallet.addWidget(usechange_cb, 1, 0)
1931 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1932 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1934 gap_label = QLabel(_('Gap limit'))
1935 grid_wallet.addWidget(gap_label, 2, 0)
1936 gap_e = AmountEdit(None,True)
1937 gap_e.setText("%d"% self.wallet.gap_limit)
1938 grid_wallet.addWidget(gap_e, 2, 2)
1939 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1940 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1941 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1942 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1943 + _('Warning') + ': ' \
1944 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1945 + _('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'
1946 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1947 if not self.config.is_modifiable('gap_limit'):
1948 for w in [gap_e, gap_label]: w.setEnabled(False)
1950 units = ['BTC', 'mBTC']
1951 unit_label = QLabel(_('Base unit'))
1952 grid_wallet.addWidget(unit_label, 3, 0)
1953 unit_combo = QComboBox()
1954 unit_combo.addItems(units)
1955 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1956 grid_wallet.addWidget(unit_combo, 3, 2)
1957 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1958 + '\n1BTC=1000mBTC.\n' \
1959 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1960 grid_wallet.setRowStretch(4,1)
1965 grid_io = QGridLayout(tab3)
1966 grid_io.setColumnStretch(0,1)
1967 tabs.addTab(tab3, _('Import/Export') )
1969 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1970 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1971 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1972 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1974 grid_io.addWidget(QLabel(_('History')), 2, 0)
1975 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1976 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1978 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1980 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1981 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1982 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1984 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1985 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1986 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1987 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1988 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1991 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1992 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1993 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1994 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1996 grid_io.setRowStretch(6,1)
2001 tab5 = QScrollArea()
2002 tab5.setEnabled(True)
2003 tab5.setWidgetResizable(True)
2005 grid_plugins = QGridLayout()
2006 grid_plugins.setColumnStretch(0,1)
2009 w.setLayout(grid_plugins)
2011 tab5.setMaximumSize(tab3.size()) # optional
2013 w.setMinimumHeight(len(self.plugins)*35)
2015 tabs.addTab(tab5, _('Plugins') )
2016 def mk_toggle(cb, p):
2017 return lambda: cb.setChecked(p.toggle())
2018 for i, p in enumerate(self.plugins):
2020 name, description = p.get_info()
2021 cb = QCheckBox(name)
2022 cb.setDisabled(not p.is_available())
2023 cb.setChecked(p.is_enabled())
2024 cb.clicked.connect(mk_toggle(cb,p))
2025 grid_plugins.addWidget(cb, i, 0)
2026 if p.requires_settings():
2027 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2028 grid_plugins.addWidget(HelpButton(description), i, 2)
2030 print_msg("Error: cannot display plugin", p)
2031 traceback.print_exc(file=sys.stdout)
2032 grid_plugins.setRowStretch(i+1,1)
2034 self.run_hook('create_settings_tab', tabs)
2036 vbox.addLayout(ok_cancel_buttons(d))
2040 if not d.exec_(): return
2042 fee = unicode(fee_e.text())
2044 fee = self.read_amount(fee)
2046 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2049 if self.wallet.fee != fee:
2050 self.wallet.fee = fee
2053 nz = unicode(nz_e.text())
2058 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2061 if self.wallet.num_zeros != nz:
2062 self.wallet.num_zeros = nz
2063 self.config.set_key('num_zeros', nz, True)
2064 self.update_history_tab()
2065 self.update_receive_tab()
2067 usechange_result = usechange_cb.isChecked()
2068 if self.wallet.use_change != usechange_result:
2069 self.wallet.use_change = usechange_result
2070 self.config.set_key('use_change', self.wallet.use_change, True)
2072 unit_result = units[unit_combo.currentIndex()]
2073 if self.base_unit() != unit_result:
2074 self.decimal_point = 8 if unit_result == 'BTC' else 5
2075 self.config.set_key('decimal_point', self.decimal_point, True)
2076 self.update_history_tab()
2077 self.update_status()
2080 n = int(gap_e.text())
2082 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2085 if self.wallet.gap_limit != n:
2086 r = self.wallet.change_gap_limit(n)
2088 self.update_receive_tab()
2089 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2091 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2093 need_restart = False
2095 lang_request = languages.keys()[lang_combo.currentIndex()]
2096 if lang_request != self.config.get('language'):
2097 self.config.set_key("language", lang_request, True)
2100 cur_request = str(currencies[cur_combo.currentIndex()])
2101 if cur_request != self.config.get('currency', "None"):
2102 self.config.set_key('currency', cur_request, True)
2103 self.update_wallet()
2105 self.run_hook('close_settings_dialog')
2108 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2110 self.receive_tab_set_mode(expert_cb.isChecked())
2114 def network_dialog(wallet, parent=None):
2115 interface = wallet.interface
2117 if interface.is_connected:
2118 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2120 status = _("Not connected")
2121 server = interface.server
2124 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2125 server = interface.server
2127 plist, servers_list = interface.get_servers_list()
2131 d.setWindowTitle(_('Server'))
2132 d.setMinimumSize(375, 20)
2134 vbox = QVBoxLayout()
2137 hbox = QHBoxLayout()
2139 l.setPixmap(QPixmap(":icons/network.png"))
2142 hbox.addWidget(QLabel(status))
2144 vbox.addLayout(hbox)
2148 grid = QGridLayout()
2150 vbox.addLayout(grid)
2153 server_protocol = QComboBox()
2154 server_host = QLineEdit()
2155 server_host.setFixedWidth(200)
2156 server_port = QLineEdit()
2157 server_port.setFixedWidth(60)
2159 protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2160 protocol_letters = 'thsg'
2161 server_protocol.addItems(protocol_names)
2163 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2164 grid.addWidget(server_protocol, 0, 1)
2165 grid.addWidget(server_host, 0, 2)
2166 grid.addWidget(server_port, 0, 3)
2168 def change_protocol(p):
2169 protocol = protocol_letters[p]
2170 host = unicode(server_host.text())
2171 pp = plist.get(host,DEFAULT_PORTS)
2172 if protocol not in pp.keys():
2173 protocol = pp.keys()[0]
2175 server_host.setText( host )
2176 server_port.setText( port )
2178 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2180 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2181 servers_list_widget = QTreeWidget(parent)
2182 servers_list_widget.setHeaderLabels( [ label, _('Limit') ] )
2183 servers_list_widget.setMaximumHeight(150)
2184 servers_list_widget.setColumnWidth(0, 240)
2185 for _host in servers_list.keys():
2186 pruning_level = servers_list[_host].get('pruning','')
2187 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2188 servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2190 def change_server(host, protocol=None):
2191 pp = plist.get(host,DEFAULT_PORTS)
2193 port = pp.get(protocol)
2194 if not port: protocol = None
2197 if 's' in pp.keys():
2199 port = pp.get(protocol)
2201 protocol = pp.keys()[0]
2202 port = pp.get(protocol)
2204 server_host.setText( host )
2205 server_port.setText( port )
2206 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2208 if not plist: return
2209 for p in protocol_letters:
2210 i = protocol_letters.index(p)
2211 j = server_protocol.model().index(i,0)
2212 if p not in pp.keys() and interface.is_connected:
2213 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2215 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2218 host, port, protocol = server.split(':')
2219 change_server(host,protocol)
2221 servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'),
2222 lambda x,y: change_server(unicode(x.text(0))))
2223 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2225 if not wallet.config.is_modifiable('server'):
2226 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2229 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2230 autocycle_cb.setChecked(wallet.config.get('auto_cycle', True))
2231 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2232 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2235 proxy_mode = QComboBox()
2236 proxy_host = QLineEdit()
2237 proxy_host.setFixedWidth(200)
2238 proxy_port = QLineEdit()
2239 proxy_port.setFixedWidth(60)
2240 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2242 def check_for_disable(index = False):
2243 if proxy_mode.currentText() != 'NONE':
2244 proxy_host.setEnabled(True)
2245 proxy_port.setEnabled(True)
2247 proxy_host.setEnabled(False)
2248 proxy_port.setEnabled(False)
2251 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2253 if not wallet.config.is_modifiable('proxy'):
2254 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2256 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2257 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2258 proxy_host.setText(proxy_config.get("host"))
2259 proxy_port.setText(proxy_config.get("port"))
2261 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2262 grid.addWidget(proxy_mode, 2, 1)
2263 grid.addWidget(proxy_host, 2, 2)
2264 grid.addWidget(proxy_port, 2, 3)
2267 vbox.addLayout(ok_cancel_buttons(d))
2270 if not d.exec_(): return
2272 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2273 if proxy_mode.currentText() != 'NONE':
2274 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2278 wallet.config.set_key("proxy", proxy, True)
2279 wallet.config.set_key("server", server, True)
2280 interface.set_server(server, proxy)
2281 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2284 def closeEvent(self, event):
2286 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2287 self.save_column_widths()
2288 self.config.set_key("console-history",self.console.history[-50:])
2294 def __init__(self, wallet, config, app=None):
2295 self.wallet = wallet
2296 self.config = config
2298 self.app = QApplication(sys.argv)
2301 def restore_or_create(self):
2302 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2303 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2304 if r==2: return None
2305 return 'restore' if r==1 else 'create'
2308 def verify_seed(self):
2309 r = self.seed_dialog(False)
2310 if r != self.wallet.seed:
2311 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2318 def seed_dialog(self, is_restore=True):
2322 vbox = QVBoxLayout()
2324 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2326 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2328 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2331 label.setWordWrap(True)
2332 vbox.addWidget(label)
2334 seed_e = QTextEdit()
2335 seed_e.setMaximumHeight(100)
2336 vbox.addWidget(seed_e)
2339 grid = QGridLayout()
2341 gap_e = AmountEdit(None, True)
2343 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2344 grid.addWidget(gap_e, 2, 1)
2345 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2346 vbox.addLayout(grid)
2348 vbox.addLayout(ok_cancel_buttons(d))
2351 if not d.exec_(): return
2354 seed = str(seed_e.toPlainText())
2358 seed = mnemonic.mn_decode( seed.split() )
2360 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2364 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2371 gap = int(unicode(gap_e.text()))
2373 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2378 def network_dialog(self):
2379 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2382 def show_seed(self):
2383 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2385 def password_dialog(self):
2386 if self.wallet.seed:
2387 ElectrumWindow.change_password_dialog(self.wallet)
2390 def restore_wallet(self):
2391 wallet = self.wallet
2392 # wait until we are connected, because the user might have selected another server
2393 if not wallet.interface.is_connected:
2394 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2395 waiting_dialog(waiting)
2397 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2398 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2400 wallet.set_up_to_date(False)
2401 wallet.interface.poke('synchronizer')
2402 waiting_dialog(waiting)
2403 if wallet.is_found():
2404 print_error( "Recovery successful" )
2406 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2413 w = ElectrumWindow(self.wallet, self.config)
2414 if url: w.set_url(url)