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():
362 def set_label(self, name, text = None):
364 old_text = self.wallet.labels.get(name)
367 self.wallet.labels[name] = text
371 self.wallet.labels.pop(name)
373 self.run_hook('set_label', name, text, changed)
377 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
378 def getOpenFileName(self, title, filter = None):
379 directory = self.config.get('io_dir', os.path.expanduser('~'))
380 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
381 if fileName and directory != os.path.dirname(fileName):
382 self.config.set_key('io_dir', os.path.dirname(fileName), True)
385 def getSaveFileName(self, title, filename, filter = None):
386 directory = self.config.get('io_dir', os.path.expanduser('~'))
387 path = os.path.join( directory, filename )
388 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
389 if fileName and directory != os.path.dirname(fileName):
390 self.config.set_key('io_dir', os.path.dirname(fileName), True)
396 QMainWindow.close(self)
397 self.run_hook('close_main_window')
399 def connect_slots(self, sender):
400 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
401 self.previous_payto_e=''
403 def timer_actions(self):
404 self.run_hook('timer_actions')
406 def format_amount(self, x, is_diff=False):
407 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
409 def read_amount(self, x):
410 if x in['.', '']: return None
411 p = pow(10, self.decimal_point)
412 return int( p * Decimal(x) )
415 assert self.decimal_point in [5,8]
416 return "BTC" if self.decimal_point == 8 else "mBTC"
418 def update_status(self):
419 if self.wallet.interface and self.wallet.interface.is_connected:
420 if not self.wallet.up_to_date:
421 text = _("Synchronizing...")
422 icon = QIcon(":icons/status_waiting.png")
424 c, u = self.wallet.get_account_balance(self.current_account)
425 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
426 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
427 text += self.create_quote_text(Decimal(c+u)/100000000)
428 icon = QIcon(":icons/status_connected.png")
430 text = _("Not connected")
431 icon = QIcon(":icons/status_disconnected.png")
433 self.status_text = text
434 self.statusBar().showMessage(text)
435 self.status_button.setIcon( icon )
437 def update_wallet(self):
439 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
440 self.update_history_tab()
441 self.update_receive_tab()
442 self.update_contacts_tab()
443 self.update_completions()
446 def create_quote_text(self, btc_balance):
447 quote_currency = self.config.get("currency", "None")
448 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
449 if quote_balance is None:
452 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
455 def create_history_tab(self):
456 self.history_list = l = MyTreeWidget(self)
458 for i,width in enumerate(self.column_widths['history']):
459 l.setColumnWidth(i, width)
460 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
461 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
462 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
464 l.setContextMenuPolicy(Qt.CustomContextMenu)
465 l.customContextMenuRequested.connect(self.create_history_menu)
469 def create_history_menu(self, position):
470 self.history_list.selectedIndexes()
471 item = self.history_list.currentItem()
473 tx_hash = str(item.data(0, Qt.UserRole).toString())
474 if not tx_hash: return
476 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
477 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
478 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
479 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
482 def show_tx_details(self, tx):
483 dialog = QDialog(self)
485 dialog.setWindowTitle(_("Transaction Details"))
487 dialog.setLayout(vbox)
488 dialog.setMinimumSize(600,300)
491 if tx_hash in self.wallet.transactions.keys():
492 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
493 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
495 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
501 vbox.addWidget(QLabel("Transaction ID:"))
502 e = QLineEdit(tx_hash)
506 vbox.addWidget(QLabel("Date: %s"%time_str))
507 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
510 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
511 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
513 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
514 vbox.addWidget(QLabel("Transaction fee: unknown"))
516 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
518 vbox.addWidget( self.generate_transaction_information_widget(tx) )
520 ok_button = QPushButton(_("Close"))
521 ok_button.setDefault(True)
522 ok_button.clicked.connect(dialog.accept)
526 hbox.addWidget(ok_button)
530 def tx_label_clicked(self, item, column):
531 if column==2 and item.isSelected():
533 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
534 self.history_list.editItem( item, column )
535 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
538 def tx_label_changed(self, item, column):
542 tx_hash = str(item.data(0, Qt.UserRole).toString())
543 tx = self.wallet.transactions.get(tx_hash)
544 text = unicode( item.text(2) )
545 self.set_label(tx_hash, text)
547 item.setForeground(2, QBrush(QColor('black')))
549 text = self.wallet.get_default_label(tx_hash)
550 item.setText(2, text)
551 item.setForeground(2, QBrush(QColor('gray')))
555 def edit_label(self, is_recv):
556 l = self.receive_list if is_recv else self.contacts_list
557 item = l.currentItem()
558 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
559 l.editItem( item, 1 )
560 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
564 def address_label_clicked(self, item, column, l, column_addr, column_label):
565 if column == column_label and item.isSelected():
566 is_editable = item.data(0, 32).toBool()
569 addr = unicode( item.text(column_addr) )
570 label = unicode( item.text(column_label) )
571 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
572 l.editItem( item, column )
573 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
576 def address_label_changed(self, item, column, l, column_addr, column_label):
577 if column == column_label:
578 addr = unicode( item.text(column_addr) )
579 text = unicode( item.text(column_label) )
580 is_editable = item.data(0, 32).toBool()
584 changed = self.set_label(addr, text)
586 self.update_history_tab()
587 self.update_completions()
589 self.current_item_changed(item)
591 self.run_hook('item_changed', item, column)
594 def current_item_changed(self, a):
595 self.run_hook('current_item_changed', a)
599 def update_history_tab(self):
601 self.history_list.clear()
602 for item in self.wallet.get_tx_history(self.current_account):
603 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
606 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
612 icon = QIcon(":icons/unconfirmed.png")
614 icon = QIcon(":icons/clock%d.png"%conf)
616 icon = QIcon(":icons/confirmed.png")
619 icon = QIcon(":icons/unconfirmed.png")
621 if value is not None:
622 v_str = self.format_amount(value, True)
626 balance_str = self.format_amount(balance)
629 label, is_default_label = self.wallet.get_label(tx_hash)
631 label = _('Pruned transaction outputs')
632 is_default_label = False
634 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
635 item.setFont(2, QFont(MONOSPACE_FONT))
636 item.setFont(3, QFont(MONOSPACE_FONT))
637 item.setFont(4, QFont(MONOSPACE_FONT))
639 item.setForeground(3, QBrush(QColor("#BC1E1E")))
641 item.setData(0, Qt.UserRole, tx_hash)
642 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
644 item.setForeground(2, QBrush(QColor('grey')))
646 item.setIcon(0, icon)
647 self.history_list.insertTopLevelItem(0,item)
650 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
653 def create_send_tab(self):
658 grid.setColumnMinimumWidth(3,300)
659 grid.setColumnStretch(5,1)
662 self.payto_e = QLineEdit()
663 grid.addWidget(QLabel(_('Pay to')), 1, 0)
664 grid.addWidget(self.payto_e, 1, 1, 1, 3)
666 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)
668 completer = QCompleter()
669 completer.setCaseSensitivity(False)
670 self.payto_e.setCompleter(completer)
671 completer.setModel(self.completions)
673 self.message_e = QLineEdit()
674 grid.addWidget(QLabel(_('Description')), 2, 0)
675 grid.addWidget(self.message_e, 2, 1, 1, 3)
676 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)
678 self.amount_e = AmountEdit(self.base_unit)
679 grid.addWidget(QLabel(_('Amount')), 3, 0)
680 grid.addWidget(self.amount_e, 3, 1, 1, 2)
681 grid.addWidget(HelpButton(
682 _('Amount to be sent.') + '\n\n' \
683 + _('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.') \
684 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
686 self.fee_e = AmountEdit(self.base_unit)
687 grid.addWidget(QLabel(_('Fee')), 4, 0)
688 grid.addWidget(self.fee_e, 4, 1, 1, 2)
689 grid.addWidget(HelpButton(
690 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
691 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
692 + _('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)
695 b = EnterButton(_("Send"), self.do_send)
697 b = EnterButton(_("Create unsigned transaction"), self.do_send)
698 grid.addWidget(b, 6, 1)
700 b = EnterButton(_("Clear"),self.do_clear)
701 grid.addWidget(b, 6, 2)
703 self.payto_sig = QLabel('')
704 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
706 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
707 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
716 def entry_changed( is_fee ):
717 self.funds_error = False
719 if self.amount_e.is_shortcut:
720 self.amount_e.is_shortcut = False
721 c, u = self.wallet.get_account_balance(self.current_account)
722 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
723 fee = self.wallet.estimated_fee(inputs)
725 self.amount_e.setText( self.format_amount(amount) )
726 self.fee_e.setText( self.format_amount( fee ) )
729 amount = self.read_amount(str(self.amount_e.text()))
730 fee = self.read_amount(str(self.fee_e.text()))
732 if not is_fee: fee = None
735 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
737 self.fee_e.setText( self.format_amount( fee ) )
740 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
741 text = self.status_text
744 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
745 self.funds_error = True
746 text = _( "Not enough funds" )
747 c, u = self.wallet.get_frozen_balance()
748 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
750 self.statusBar().showMessage(text)
751 self.amount_e.setPalette(palette)
752 self.fee_e.setPalette(palette)
754 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
755 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
757 self.run_hook('create_send_tab', grid)
761 def update_completions(self):
763 for addr,label in self.wallet.labels.items():
764 if addr in self.wallet.addressbook:
765 l.append( label + ' <' + addr + '>')
767 self.run_hook('update_completions', l)
768 self.completions.setStringList(l)
772 return lambda s, *args: s.do_protect(func, args)
776 def do_send(self, password):
778 label = unicode( self.message_e.text() )
779 r = unicode( self.payto_e.text() )
782 # label or alias, with address in brackets
783 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
784 to_address = m.group(2) if m else r
786 if not is_valid(to_address):
787 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
791 amount = self.read_amount(unicode( self.amount_e.text()))
793 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
796 fee = self.read_amount(unicode( self.fee_e.text()))
798 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
802 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
803 except BaseException, e:
804 self.show_message(str(e))
807 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
808 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
811 self.run_hook('send_tx', tx)
814 self.set_label(tx.hash(), label)
817 h = self.wallet.send_tx(tx)
818 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
819 status, msg = self.wallet.receive_tx( h )
821 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
823 self.update_contacts_tab()
825 QMessageBox.warning(self, _('Error'), msg, _('OK'))
827 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
829 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
830 with open(fileName,'w') as f:
831 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
832 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
834 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
839 def set_url(self, url):
840 address, amount, label, message, signature, identity, url = util.parse_url(url)
841 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
843 if label and self.wallet.labels.get(address) != label:
844 if self.question('Give label "%s" to address %s ?'%(label,address)):
845 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
846 self.wallet.addressbook.append(address)
847 self.set_label(address, label)
849 self.run_hook('set_url', url, self.show_message, self.question)
851 self.tabs.setCurrentIndex(1)
852 label = self.wallet.labels.get(address)
853 m_addr = label + ' <'+ address +'>' if label else address
854 self.payto_e.setText(m_addr)
856 self.message_e.setText(message)
857 self.amount_e.setText(amount)
859 self.set_frozen(self.payto_e,True)
860 self.set_frozen(self.amount_e,True)
861 self.set_frozen(self.message_e,True)
862 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
864 self.payto_sig.setVisible(False)
867 self.payto_sig.setVisible(False)
868 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
870 self.set_frozen(e,False)
873 def set_frozen(self,entry,frozen):
875 entry.setReadOnly(True)
876 entry.setFrame(False)
878 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
879 entry.setPalette(palette)
881 entry.setReadOnly(False)
884 palette.setColor(entry.backgroundRole(), QColor('white'))
885 entry.setPalette(palette)
888 def toggle_freeze(self,addr):
890 if addr in self.wallet.frozen_addresses:
891 self.wallet.unfreeze(addr)
893 self.wallet.freeze(addr)
894 self.update_receive_tab()
896 def toggle_priority(self,addr):
898 if addr in self.wallet.prioritized_addresses:
899 self.wallet.unprioritize(addr)
901 self.wallet.prioritize(addr)
902 self.update_receive_tab()
905 def create_list_tab(self, headers):
906 "generic tab creation method"
907 l = MyTreeWidget(self)
908 l.setColumnCount( len(headers) )
909 l.setHeaderLabels( headers )
919 vbox.addWidget(buttons)
924 buttons.setLayout(hbox)
929 def create_receive_tab(self):
930 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
931 l.setContextMenuPolicy(Qt.CustomContextMenu)
932 l.customContextMenuRequested.connect(self.create_receive_menu)
933 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
934 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
935 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
936 self.receive_list = l
937 self.receive_buttons_hbox = hbox
942 def receive_tab_set_mode(self, i):
943 self.save_column_widths()
944 self.expert_mode = (i == 1)
945 self.config.set_key('classic_expert_mode', self.expert_mode, True)
947 self.update_receive_tab()
950 def save_column_widths(self):
951 if not self.expert_mode:
952 widths = [ self.receive_list.columnWidth(0) ]
955 for i in range(self.receive_list.columnCount() -1):
956 widths.append(self.receive_list.columnWidth(i))
957 self.column_widths["receive"][self.expert_mode] = widths
959 self.column_widths["history"] = []
960 for i in range(self.history_list.columnCount() - 1):
961 self.column_widths["history"].append(self.history_list.columnWidth(i))
963 self.column_widths["contacts"] = []
964 for i in range(self.contacts_list.columnCount() - 1):
965 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
967 self.config.set_key("column_widths", self.column_widths, True)
970 def create_contacts_tab(self):
971 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
972 l.setContextMenuPolicy(Qt.CustomContextMenu)
973 l.customContextMenuRequested.connect(self.create_contact_menu)
974 for i,width in enumerate(self.column_widths['contacts']):
975 l.setColumnWidth(i, width)
977 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
978 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
979 self.contacts_list = l
980 self.contacts_buttons_hbox = hbox
981 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
986 def delete_imported_key(self, addr):
987 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
988 self.wallet.imported_keys.pop(addr)
989 self.update_receive_tab()
990 self.update_history_tab()
994 def create_receive_menu(self, position):
995 # fixme: this function apparently has a side effect.
996 # if it is not called the menu pops up several times
997 #self.receive_list.selectedIndexes()
999 item = self.receive_list.itemAt(position)
1001 addr = unicode(item.text(0))
1002 if not is_valid(addr):
1003 item.setExpanded(not item.isExpanded())
1006 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1007 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1008 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1009 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1010 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1011 if addr in self.wallet.imported_keys:
1012 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1014 if self.expert_mode:
1015 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1016 menu.addAction(t, lambda: self.toggle_freeze(addr))
1017 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1018 menu.addAction(t, lambda: self.toggle_priority(addr))
1020 self.run_hook('receive_menu', menu)
1021 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1024 def payto(self, addr):
1026 label = self.wallet.labels.get(addr)
1027 m_addr = label + ' <' + addr + '>' if label else addr
1028 self.tabs.setCurrentIndex(1)
1029 self.payto_e.setText(m_addr)
1030 self.amount_e.setFocus()
1033 def delete_contact(self, x):
1034 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1035 if x in self.wallet.addressbook:
1036 self.wallet.addressbook.remove(x)
1037 self.set_label(x, None)
1038 self.update_history_tab()
1039 self.update_contacts_tab()
1040 self.update_completions()
1043 def create_contact_menu(self, position):
1044 item = self.contacts_list.itemAt(position)
1046 addr = unicode(item.text(0))
1047 label = unicode(item.text(1))
1048 is_editable = item.data(0,32).toBool()
1049 payto_addr = item.data(0,33).toString()
1051 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1052 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1053 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1055 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1056 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1058 self.run_hook('create_contact_menu', menu, item)
1059 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1062 def update_receive_item(self, item):
1063 item.setFont(0, QFont(MONOSPACE_FONT))
1064 address = str(item.data(0,0).toString())
1065 label = self.wallet.labels.get(address,'')
1066 item.setData(1,0,label)
1067 item.setData(0,32, True) # is editable
1069 self.run_hook('update_receive_item', address, item)
1071 c, u = self.wallet.get_addr_balance(address)
1072 balance = self.format_amount(c + u)
1073 item.setData(2,0,balance)
1075 if self.expert_mode:
1076 if address in self.wallet.frozen_addresses:
1077 item.setBackgroundColor(0, QColor('lightblue'))
1078 elif address in self.wallet.prioritized_addresses:
1079 item.setBackgroundColor(0, QColor('lightgreen'))
1082 def update_receive_tab(self):
1083 l = self.receive_list
1086 l.setColumnHidden(2, not self.expert_mode)
1087 l.setColumnHidden(3, not self.expert_mode)
1088 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1089 l.setColumnWidth(i, width)
1091 if self.current_account is None:
1092 account_items = self.wallet.accounts.items()
1093 elif self.current_account != -1:
1094 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1098 for k, account in account_items:
1099 name = account.get('name',str(k))
1100 c,u = self.wallet.get_account_balance(k)
1101 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1102 l.addTopLevelItem(account_item)
1103 account_item.setExpanded(True)
1105 for is_change in ([0,1] if self.expert_mode else [0]):
1106 if self.expert_mode:
1107 name = "Receiving" if not is_change else "Change"
1108 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1109 account_item.addChild(seq_item)
1110 if not is_change: seq_item.setExpanded(True)
1112 seq_item = account_item
1116 for address in account[is_change]:
1117 h = self.wallet.history.get(address,[])
1121 if gap > self.wallet.gap_limit:
1126 num_tx = '*' if h == ['*'] else "%d"%len(h)
1127 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1128 self.update_receive_item(item)
1130 item.setBackgroundColor(1, QColor('red'))
1131 seq_item.addChild(item)
1134 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1135 c,u = self.wallet.get_imported_balance()
1136 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1137 l.addTopLevelItem(account_item)
1138 account_item.setExpanded(True)
1139 for address in self.wallet.imported_keys.keys():
1140 item = QTreeWidgetItem( [ address, '', '', ''] )
1141 self.update_receive_item(item)
1142 account_item.addChild(item)
1145 # we use column 1 because column 0 may be hidden
1146 l.setCurrentItem(l.topLevelItem(0),1)
1149 def update_contacts_tab(self):
1151 l = self.contacts_list
1154 for address in self.wallet.addressbook:
1155 label = self.wallet.labels.get(address,'')
1156 n = self.wallet.get_num_tx(address)
1157 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1158 item.setFont(0, QFont(MONOSPACE_FONT))
1159 # 32 = label can be edited (bool)
1160 item.setData(0,32, True)
1162 item.setData(0,33, address)
1163 l.addTopLevelItem(item)
1165 self.run_hook('update_contacts_tab', l)
1166 l.setCurrentItem(l.topLevelItem(0))
1170 def create_console_tab(self):
1171 from qt_console import Console
1172 self.console = console = Console()
1173 self.console.history = self.config.get("console-history",[])
1174 self.console.history_index = len(self.console.history)
1176 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1177 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1179 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1181 def mkfunc(f, method):
1182 return lambda *args: apply( f, (method, args, self.password_dialog ))
1184 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1185 methods[m] = mkfunc(c._run, m)
1187 console.updateNamespace(methods)
1190 def change_account(self,s):
1191 if s == _("All accounts"):
1192 self.current_account = None
1194 accounts = self.wallet.get_accounts()
1195 for k, v in accounts.items():
1197 self.current_account = k
1198 self.update_history_tab()
1199 self.update_status()
1200 self.update_receive_tab()
1202 def create_status_bar(self):
1203 self.status_text = ""
1205 sb.setFixedHeight(35)
1206 qtVersion = qVersion()
1208 update_notification = UpdateLabel(self.config)
1209 if(update_notification.new_version):
1210 sb.addPermanentWidget(update_notification)
1212 accounts = self.wallet.get_accounts()
1213 if len(accounts) > 1:
1214 from_combo = QComboBox()
1215 from_combo.addItems([_("All accounts")] + accounts.values())
1216 from_combo.setCurrentIndex(0)
1217 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1218 sb.addPermanentWidget(from_combo)
1220 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1221 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1222 if self.wallet.seed:
1223 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1224 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1225 sb.addPermanentWidget( self.password_button )
1226 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1227 if self.wallet.seed:
1228 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1229 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1230 sb.addPermanentWidget( self.status_button )
1232 self.run_hook('create_status_bar', (sb,))
1234 self.setStatusBar(sb)
1238 self.config.set_key('gui', 'lite', True)
1241 self.lite.mini.show()
1243 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1244 self.lite.main(None)
1246 def new_contact_dialog(self):
1247 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1248 address = unicode(text)
1250 if is_valid(address):
1251 self.wallet.addressbook.append(address)
1253 self.update_contacts_tab()
1254 self.update_history_tab()
1255 self.update_completions()
1257 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1259 def show_master_public_key(self):
1260 dialog = QDialog(self)
1262 dialog.setWindowTitle(_("Master Public Key"))
1264 main_text = QTextEdit()
1265 main_text.setText(self.wallet.get_master_public_key())
1266 main_text.setReadOnly(True)
1267 main_text.setMaximumHeight(170)
1268 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1270 ok_button = QPushButton(_("OK"))
1271 ok_button.setDefault(True)
1272 ok_button.clicked.connect(dialog.accept)
1274 main_layout = QGridLayout()
1275 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1277 main_layout.addWidget(main_text, 1, 0)
1278 main_layout.addWidget(qrw, 1, 1 )
1280 vbox = QVBoxLayout()
1281 vbox.addLayout(main_layout)
1282 hbox = QHBoxLayout()
1284 hbox.addWidget(ok_button)
1285 vbox.addLayout(hbox)
1287 dialog.setLayout(vbox)
1292 def show_seed_dialog(self, password):
1293 if not self.wallet.seed:
1294 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1297 seed = self.wallet.decode_seed(password)
1299 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1301 self.show_seed(seed, self.wallet.imported_keys, self)
1305 def show_seed(self, seed, imported_keys, parent=None):
1306 dialog = QDialog(parent)
1308 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1310 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1312 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1314 seed_text = QTextEdit(brainwallet)
1315 seed_text.setReadOnly(True)
1316 seed_text.setMaximumHeight(130)
1318 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1319 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1320 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1321 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1323 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1324 label2 = QLabel(msg2)
1325 label2.setWordWrap(True)
1328 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1329 logo.setMaximumWidth(60)
1331 qrw = QRCodeWidget(seed)
1333 ok_button = QPushButton(_("OK"))
1334 ok_button.setDefault(True)
1335 ok_button.clicked.connect(dialog.accept)
1337 grid = QGridLayout()
1338 #main_layout.addWidget(logo, 0, 0)
1340 grid.addWidget(logo, 0, 0)
1341 grid.addWidget(label1, 0, 1)
1343 grid.addWidget(seed_text, 1, 0, 1, 2)
1345 grid.addWidget(qrw, 0, 2, 2, 1)
1347 vbox = QVBoxLayout()
1348 vbox.addLayout(grid)
1349 vbox.addWidget(label2)
1351 hbox = QHBoxLayout()
1353 hbox.addWidget(ok_button)
1354 vbox.addLayout(hbox)
1356 dialog.setLayout(vbox)
1359 def show_qrcode(self, data, title = "QR code"):
1363 d.setWindowTitle(title)
1364 d.setMinimumSize(270, 300)
1365 vbox = QVBoxLayout()
1366 qrw = QRCodeWidget(data)
1367 vbox.addWidget(qrw, 1)
1368 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1369 hbox = QHBoxLayout()
1373 filename = "qrcode.bmp"
1374 bmp.save_qrcode(qrw.qr, filename)
1375 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1377 b = QPushButton(_("Save"))
1379 b.clicked.connect(print_qr)
1381 b = QPushButton(_("Close"))
1383 b.clicked.connect(d.accept)
1386 vbox.addLayout(hbox)
1391 def do_protect(self, func, args):
1392 if self.wallet.use_encryption:
1393 password = self.password_dialog()
1399 if args != (False,):
1400 args = (self,) + args + (password,)
1402 args = (self,password)
1407 def show_private_key(self, address, password):
1408 if not address: return
1410 pk = self.wallet.get_private_key(address, password)
1411 except BaseException, e:
1412 self.show_message(str(e))
1414 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1418 def do_sign(self, address, message, signature, password):
1420 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1421 signature.setText(sig)
1422 except BaseException, e:
1423 self.show_message(str(e))
1425 def sign_message(self, address):
1426 if not address: return
1429 d.setWindowTitle(_('Sign Message'))
1430 d.setMinimumSize(410, 290)
1432 tab_widget = QTabWidget()
1434 layout = QGridLayout(tab)
1436 sign_address = QLineEdit()
1438 sign_address.setText(address)
1439 layout.addWidget(QLabel(_('Address')), 1, 0)
1440 layout.addWidget(sign_address, 1, 1)
1442 sign_message = QTextEdit()
1443 layout.addWidget(QLabel(_('Message')), 2, 0)
1444 layout.addWidget(sign_message, 2, 1)
1445 layout.setRowStretch(2,3)
1447 sign_signature = QTextEdit()
1448 layout.addWidget(QLabel(_('Signature')), 3, 0)
1449 layout.addWidget(sign_signature, 3, 1)
1450 layout.setRowStretch(3,1)
1453 hbox = QHBoxLayout()
1454 b = QPushButton(_("Sign"))
1456 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1457 b = QPushButton(_("Close"))
1458 b.clicked.connect(d.accept)
1460 layout.addLayout(hbox, 4, 1)
1461 tab_widget.addTab(tab, _("Sign"))
1465 layout = QGridLayout(tab)
1467 verify_address = QLineEdit()
1468 layout.addWidget(QLabel(_('Address')), 1, 0)
1469 layout.addWidget(verify_address, 1, 1)
1471 verify_message = QTextEdit()
1472 layout.addWidget(QLabel(_('Message')), 2, 0)
1473 layout.addWidget(verify_message, 2, 1)
1474 layout.setRowStretch(2,3)
1476 verify_signature = QTextEdit()
1477 layout.addWidget(QLabel(_('Signature')), 3, 0)
1478 layout.addWidget(verify_signature, 3, 1)
1479 layout.setRowStretch(3,1)
1483 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1484 self.show_message(_("Signature verified"))
1485 except BaseException, e:
1486 self.show_message(str(e))
1489 hbox = QHBoxLayout()
1490 b = QPushButton(_("Verify"))
1491 b.clicked.connect(do_verify)
1493 b = QPushButton(_("Close"))
1494 b.clicked.connect(d.accept)
1496 layout.addLayout(hbox, 4, 1)
1497 tab_widget.addTab(tab, _("Verify"))
1499 vbox = QVBoxLayout()
1500 vbox.addWidget(tab_widget)
1507 def question(self, msg):
1508 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1510 def show_message(self, msg):
1511 QMessageBox.information(self, _('Message'), msg, _('OK'))
1513 def password_dialog(self ):
1520 vbox = QVBoxLayout()
1521 msg = _('Please enter your password')
1522 vbox.addWidget(QLabel(msg))
1524 grid = QGridLayout()
1526 grid.addWidget(QLabel(_('Password')), 1, 0)
1527 grid.addWidget(pw, 1, 1)
1528 vbox.addLayout(grid)
1530 vbox.addLayout(ok_cancel_buttons(d))
1533 self.run_hook('password_dialog', pw, grid, 1)
1534 if not d.exec_(): return
1535 return unicode(pw.text())
1542 def change_password_dialog( wallet, parent=None ):
1545 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1553 new_pw = QLineEdit()
1554 new_pw.setEchoMode(2)
1555 conf_pw = QLineEdit()
1556 conf_pw.setEchoMode(2)
1558 vbox = QVBoxLayout()
1560 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1561 +_('To disable wallet encryption, enter an empty new password.')) \
1562 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1564 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1565 +_("Leave these fields empty if you want to disable encryption.")
1566 vbox.addWidget(QLabel(msg))
1568 grid = QGridLayout()
1571 if wallet.use_encryption:
1572 grid.addWidget(QLabel(_('Password')), 1, 0)
1573 grid.addWidget(pw, 1, 1)
1575 grid.addWidget(QLabel(_('New Password')), 2, 0)
1576 grid.addWidget(new_pw, 2, 1)
1578 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1579 grid.addWidget(conf_pw, 3, 1)
1580 vbox.addLayout(grid)
1582 vbox.addLayout(ok_cancel_buttons(d))
1585 if not d.exec_(): return
1587 password = unicode(pw.text()) if wallet.use_encryption else None
1588 new_password = unicode(new_pw.text())
1589 new_password2 = unicode(conf_pw.text())
1592 seed = wallet.decode_seed(password)
1594 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1597 if new_password != new_password2:
1598 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1599 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1601 wallet.update_password(seed, password, new_password)
1603 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1604 parent.password_button.setIcon( icon )
1608 def generate_transaction_information_widget(self, tx):
1609 tabs = QTabWidget(self)
1612 grid_ui = QGridLayout(tab1)
1613 grid_ui.setColumnStretch(0,1)
1614 tabs.addTab(tab1, _('Outputs') )
1616 tree_widget = MyTreeWidget(self)
1617 tree_widget.setColumnCount(2)
1618 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1619 tree_widget.setColumnWidth(0, 300)
1620 tree_widget.setColumnWidth(1, 50)
1622 for address, value in tx.outputs:
1623 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1624 tree_widget.addTopLevelItem(item)
1626 tree_widget.setMaximumHeight(100)
1628 grid_ui.addWidget(tree_widget)
1631 grid_ui = QGridLayout(tab2)
1632 grid_ui.setColumnStretch(0,1)
1633 tabs.addTab(tab2, _('Inputs') )
1635 tree_widget = MyTreeWidget(self)
1636 tree_widget.setColumnCount(2)
1637 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1639 for input_line in tx.inputs:
1640 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1641 tree_widget.addTopLevelItem(item)
1643 tree_widget.setMaximumHeight(100)
1645 grid_ui.addWidget(tree_widget)
1649 def tx_dict_from_text(self, txt):
1651 tx_dict = json.loads(str(txt))
1652 assert "hex" in tx_dict.keys()
1653 assert "complete" in tx_dict.keys()
1654 if not tx_dict["complete"]:
1655 assert "input_info" in tx_dict.keys()
1657 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1662 def read_tx_from_file(self):
1663 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1667 with open(fileName, "r") as f:
1668 file_content = f.read()
1669 except (ValueError, IOError, os.error), reason:
1670 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1672 return self.tx_dict_from_text(file_content)
1676 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1678 self.wallet.signrawtransaction(tx, input_info, [], password)
1680 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1682 with open(fileName, "w+") as f:
1683 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1684 self.show_message(_("Transaction saved successfully"))
1687 except BaseException, e:
1688 self.show_message(str(e))
1691 def send_raw_transaction(self, raw_tx, dialog = ""):
1692 result, result_message = self.wallet.sendtx( raw_tx )
1694 self.show_message("Transaction successfully sent: %s" % (result_message))
1698 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1700 def do_process_from_text(self):
1701 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1704 tx_dict = self.tx_dict_from_text(text)
1706 self.create_process_transaction_window(tx_dict)
1708 def do_process_from_file(self):
1709 tx_dict = self.read_tx_from_file()
1711 self.create_process_transaction_window(tx_dict)
1713 def create_process_transaction_window(self, tx_dict):
1714 tx = Transaction(tx_dict["hex"])
1716 dialog = QDialog(self)
1717 dialog.setMinimumWidth(500)
1718 dialog.setWindowTitle(_('Process raw transaction'))
1724 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1725 l.addWidget(QLabel(_("Actions")), 4,0)
1727 if tx_dict["complete"] == False:
1728 l.addWidget(QLabel(_("Unsigned")), 3,1)
1729 if self.wallet.seed :
1730 b = QPushButton("Sign transaction")
1731 input_info = json.loads(tx_dict["input_info"])
1732 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1733 l.addWidget(b, 4, 1)
1735 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1737 l.addWidget(QLabel(_("Signed")), 3,1)
1738 b = QPushButton("Broadcast transaction")
1739 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1742 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1743 cancelButton = QPushButton(_("Cancel"))
1744 cancelButton.clicked.connect(lambda: dialog.done(0))
1745 l.addWidget(cancelButton, 4,2)
1751 def do_export_privkeys(self, password):
1752 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.")))
1755 select_export = _('Select file to export your private keys to')
1756 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1758 with open(fileName, "w+") as csvfile:
1759 transaction = csv.writer(csvfile)
1760 transaction.writerow(["address", "private_key"])
1763 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1764 transaction.writerow(["%34s"%addr,pk])
1766 self.show_message(_("Private keys exported."))
1768 except (IOError, os.error), reason:
1769 export_error_label = _("Electrum was unable to produce a private key-export.")
1770 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1772 except BaseException, e:
1773 self.show_message(str(e))
1777 def do_import_labels(self):
1778 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1779 if not labelsFile: return
1781 f = open(labelsFile, 'r')
1784 for key, value in json.loads(data).items():
1785 self.wallet.labels[key] = value
1787 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1788 except (IOError, os.error), reason:
1789 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1792 def do_export_labels(self):
1793 labels = self.wallet.labels
1795 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1797 with open(fileName, 'w+') as f:
1798 json.dump(labels, f)
1799 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1800 except (IOError, os.error), reason:
1801 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1804 def do_export_history(self):
1805 from gui_lite import csv_transaction
1806 csv_transaction(self.wallet)
1810 def do_import_privkey(self, password):
1811 if not self.wallet.imported_keys:
1812 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1813 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1814 + _('Are you sure you understand what you are doing?'), 3, 4)
1817 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1820 text = str(text).split()
1825 addr = self.wallet.import_key(key, password)
1826 except BaseException as e:
1832 addrlist.append(addr)
1834 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1836 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1837 self.update_receive_tab()
1838 self.update_history_tab()
1841 def settings_dialog(self):
1843 d.setWindowTitle(_('Electrum Settings'))
1845 vbox = QVBoxLayout()
1847 tabs = QTabWidget(self)
1848 self.settings_tab = tabs
1849 vbox.addWidget(tabs)
1852 grid_ui = QGridLayout(tab1)
1853 grid_ui.setColumnStretch(0,1)
1854 tabs.addTab(tab1, _('Display') )
1856 nz_label = QLabel(_('Display zeros'))
1857 grid_ui.addWidget(nz_label, 0, 0)
1858 nz_e = AmountEdit(None,True)
1859 nz_e.setText("%d"% self.wallet.num_zeros)
1860 grid_ui.addWidget(nz_e, 0, 1)
1861 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1862 grid_ui.addWidget(HelpButton(msg), 0, 2)
1863 if not self.config.is_modifiable('num_zeros'):
1864 for w in [nz_e, nz_label]: w.setEnabled(False)
1866 lang_label=QLabel(_('Language') + ':')
1867 grid_ui.addWidget(lang_label, 1, 0)
1868 lang_combo = QComboBox()
1869 from i18n import languages
1870 lang_combo.addItems(languages.values())
1872 index = languages.keys().index(self.config.get("language",''))
1875 lang_combo.setCurrentIndex(index)
1876 grid_ui.addWidget(lang_combo, 1, 1)
1877 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1878 if not self.config.is_modifiable('language'):
1879 for w in [lang_combo, lang_label]: w.setEnabled(False)
1881 currencies = self.exchanger.get_currencies()
1882 currencies.insert(0, "None")
1884 cur_label=QLabel(_('Currency') + ':')
1885 grid_ui.addWidget(cur_label , 2, 0)
1886 cur_combo = QComboBox()
1887 cur_combo.addItems(currencies)
1889 index = currencies.index(self.config.get('currency', "None"))
1892 cur_combo.setCurrentIndex(index)
1893 grid_ui.addWidget(cur_combo, 2, 1)
1894 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1896 expert_cb = QCheckBox(_('Expert mode'))
1897 expert_cb.setChecked(self.expert_mode)
1898 grid_ui.addWidget(expert_cb, 3, 0)
1899 hh = _('In expert mode, your client will:') + '\n' \
1900 + _(' - Show change addresses in the Receive tab') + '\n' \
1901 + _(' - Display the balance of each address') + '\n' \
1902 + _(' - Add freeze/prioritize actions to addresses.')
1903 grid_ui.addWidget(HelpButton(hh), 3, 2)
1904 grid_ui.setRowStretch(4,1)
1908 grid_wallet = QGridLayout(tab2)
1909 grid_wallet.setColumnStretch(0,1)
1910 tabs.addTab(tab2, _('Wallet') )
1912 fee_label = QLabel(_('Transaction fee'))
1913 grid_wallet.addWidget(fee_label, 0, 0)
1914 fee_e = AmountEdit(self.base_unit)
1915 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1916 grid_wallet.addWidget(fee_e, 0, 2)
1917 msg = _('Fee per kilobyte of transaction.') + ' ' \
1918 + _('Recommended value') + ': ' + self.format_amount(50000)
1919 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1920 if not self.config.is_modifiable('fee_per_kb'):
1921 for w in [fee_e, fee_label]: w.setEnabled(False)
1923 usechange_cb = QCheckBox(_('Use change addresses'))
1924 usechange_cb.setChecked(self.wallet.use_change)
1925 grid_wallet.addWidget(usechange_cb, 1, 0)
1926 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1927 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1929 gap_label = QLabel(_('Gap limit'))
1930 grid_wallet.addWidget(gap_label, 2, 0)
1931 gap_e = AmountEdit(None,True)
1932 gap_e.setText("%d"% self.wallet.gap_limit)
1933 grid_wallet.addWidget(gap_e, 2, 2)
1934 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1935 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1936 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1937 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1938 + _('Warning') + ': ' \
1939 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1940 + _('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'
1941 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1942 if not self.config.is_modifiable('gap_limit'):
1943 for w in [gap_e, gap_label]: w.setEnabled(False)
1945 units = ['BTC', 'mBTC']
1946 unit_label = QLabel(_('Base unit'))
1947 grid_wallet.addWidget(unit_label, 3, 0)
1948 unit_combo = QComboBox()
1949 unit_combo.addItems(units)
1950 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1951 grid_wallet.addWidget(unit_combo, 3, 2)
1952 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1953 + '\n1BTC=1000mBTC.\n' \
1954 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1955 grid_wallet.setRowStretch(4,1)
1960 grid_io = QGridLayout(tab3)
1961 grid_io.setColumnStretch(0,1)
1962 tabs.addTab(tab3, _('Import/Export') )
1964 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1965 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1966 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1967 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1969 grid_io.addWidget(QLabel(_('History')), 2, 0)
1970 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1971 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1973 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1975 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1976 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1977 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1979 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1980 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1981 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1982 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1983 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1986 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1987 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1988 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1989 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1991 grid_io.setRowStretch(6,1)
1996 tab5 = QScrollArea()
1997 tab5.setEnabled(True)
1998 tab5.setWidgetResizable(True)
2000 grid_plugins = QGridLayout()
2001 grid_plugins.setColumnStretch(0,1)
2004 w.setLayout(grid_plugins)
2006 tab5.setMaximumSize(tab3.size()) # optional
2008 w.setMinimumHeight(len(self.plugins)*35)
2010 tabs.addTab(tab5, _('Plugins') )
2011 def mk_toggle(cb, p):
2012 return lambda: cb.setChecked(p.toggle())
2013 for i, p in enumerate(self.plugins):
2015 name, description = p.get_info()
2016 cb = QCheckBox(name)
2017 cb.setDisabled(not p.is_available())
2018 cb.setChecked(p.is_enabled())
2019 cb.clicked.connect(mk_toggle(cb,p))
2020 grid_plugins.addWidget(cb, i, 0)
2021 if p.requires_settings():
2022 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2023 grid_plugins.addWidget(HelpButton(description), i, 2)
2025 print_msg("Error: cannot display plugin", p)
2026 traceback.print_exc(file=sys.stdout)
2027 grid_plugins.setRowStretch(i+1,1)
2029 self.run_hook('create_settings_tab', tabs)
2031 vbox.addLayout(ok_cancel_buttons(d))
2035 if not d.exec_(): return
2037 fee = unicode(fee_e.text())
2039 fee = self.read_amount(fee)
2041 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2044 if self.wallet.fee != fee:
2045 self.wallet.fee = fee
2048 nz = unicode(nz_e.text())
2053 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2056 if self.wallet.num_zeros != nz:
2057 self.wallet.num_zeros = nz
2058 self.config.set_key('num_zeros', nz, True)
2059 self.update_history_tab()
2060 self.update_receive_tab()
2062 usechange_result = usechange_cb.isChecked()
2063 if self.wallet.use_change != usechange_result:
2064 self.wallet.use_change = usechange_result
2065 self.config.set_key('use_change', self.wallet.use_change, True)
2067 unit_result = units[unit_combo.currentIndex()]
2068 if self.base_unit() != unit_result:
2069 self.decimal_point = 8 if unit_result == 'BTC' else 5
2070 self.config.set_key('decimal_point', self.decimal_point, True)
2071 self.update_history_tab()
2072 self.update_status()
2075 n = int(gap_e.text())
2077 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2080 if self.wallet.gap_limit != n:
2081 r = self.wallet.change_gap_limit(n)
2083 self.update_receive_tab()
2084 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2086 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2088 need_restart = False
2090 lang_request = languages.keys()[lang_combo.currentIndex()]
2091 if lang_request != self.config.get('language'):
2092 self.config.set_key("language", lang_request, True)
2095 cur_request = str(currencies[cur_combo.currentIndex()])
2096 if cur_request != self.config.get('currency', "None"):
2097 self.config.set_key('currency', cur_request, True)
2098 self.update_wallet()
2100 self.run_hook('close_settings_dialog')
2103 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2105 self.receive_tab_set_mode(expert_cb.isChecked())
2109 def network_dialog(wallet, parent=None):
2110 interface = wallet.interface
2112 if interface.is_connected:
2113 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2115 status = _("Not connected")
2116 server = interface.server
2119 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2120 server = interface.server
2122 plist, servers_list = interface.get_servers_list()
2126 d.setWindowTitle(_('Server'))
2127 d.setMinimumSize(375, 20)
2129 vbox = QVBoxLayout()
2132 hbox = QHBoxLayout()
2134 l.setPixmap(QPixmap(":icons/network.png"))
2137 hbox.addWidget(QLabel(status))
2139 vbox.addLayout(hbox)
2143 grid = QGridLayout()
2145 vbox.addLayout(grid)
2148 server_protocol = QComboBox()
2149 server_host = QLineEdit()
2150 server_host.setFixedWidth(200)
2151 server_port = QLineEdit()
2152 server_port.setFixedWidth(60)
2154 protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2155 protocol_letters = 'thsg'
2156 server_protocol.addItems(protocol_names)
2158 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2159 grid.addWidget(server_protocol, 0, 1)
2160 grid.addWidget(server_host, 0, 2)
2161 grid.addWidget(server_port, 0, 3)
2163 def change_protocol(p):
2164 protocol = protocol_letters[p]
2165 host = unicode(server_host.text())
2166 pp = plist.get(host,DEFAULT_PORTS)
2167 if protocol not in pp.keys():
2168 protocol = pp.keys()[0]
2170 server_host.setText( host )
2171 server_port.setText( port )
2173 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2175 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2176 servers_list_widget = QTreeWidget(parent)
2177 servers_list_widget.setHeaderLabels( [ label, _('Limit') ] )
2178 servers_list_widget.setMaximumHeight(150)
2179 servers_list_widget.setColumnWidth(0, 240)
2180 for _host in servers_list.keys():
2181 pruning_level = servers_list[_host].get('pruning','')
2182 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2183 servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2185 def change_server(host, protocol=None):
2186 pp = plist.get(host,DEFAULT_PORTS)
2188 port = pp.get(protocol)
2189 if not port: protocol = None
2192 if 's' in pp.keys():
2194 port = pp.get(protocol)
2196 protocol = pp.keys()[0]
2197 port = pp.get(protocol)
2199 server_host.setText( host )
2200 server_port.setText( port )
2201 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2203 if not plist: return
2204 for p in protocol_letters:
2205 i = protocol_letters.index(p)
2206 j = server_protocol.model().index(i,0)
2207 if p not in pp.keys() and interface.is_connected:
2208 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2210 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2213 host, port, protocol = server.split(':')
2214 change_server(host,protocol)
2216 servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'),
2217 lambda x,y: change_server(unicode(x.text(0))))
2218 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2220 if not wallet.config.is_modifiable('server'):
2221 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2224 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2225 autocycle_cb.setChecked(wallet.config.get('auto_cycle', True))
2226 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2227 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2230 proxy_mode = QComboBox()
2231 proxy_host = QLineEdit()
2232 proxy_host.setFixedWidth(200)
2233 proxy_port = QLineEdit()
2234 proxy_port.setFixedWidth(60)
2235 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2237 def check_for_disable(index = False):
2238 if proxy_mode.currentText() != 'NONE':
2239 proxy_host.setEnabled(True)
2240 proxy_port.setEnabled(True)
2242 proxy_host.setEnabled(False)
2243 proxy_port.setEnabled(False)
2246 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2248 if not wallet.config.is_modifiable('proxy'):
2249 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2251 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2252 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2253 proxy_host.setText(proxy_config.get("host"))
2254 proxy_port.setText(proxy_config.get("port"))
2256 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2257 grid.addWidget(proxy_mode, 2, 1)
2258 grid.addWidget(proxy_host, 2, 2)
2259 grid.addWidget(proxy_port, 2, 3)
2262 vbox.addLayout(ok_cancel_buttons(d))
2265 if not d.exec_(): return
2267 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2268 if proxy_mode.currentText() != 'NONE':
2269 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2273 wallet.config.set_key("proxy", proxy, True)
2274 wallet.config.set_key("server", server, True)
2275 interface.set_server(server, proxy)
2276 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2279 def closeEvent(self, event):
2281 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2282 self.save_column_widths()
2283 self.config.set_key("console-history",self.console.history[-50:])
2289 def __init__(self, wallet, config, app=None):
2290 self.wallet = wallet
2291 self.config = config
2293 self.app = QApplication(sys.argv)
2296 def restore_or_create(self):
2297 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2298 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2299 if r==2: return None
2300 return 'restore' if r==1 else 'create'
2303 def verify_seed(self):
2304 r = self.seed_dialog(False)
2305 if r != self.wallet.seed:
2306 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2313 def seed_dialog(self, is_restore=True):
2317 vbox = QVBoxLayout()
2319 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2321 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2323 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2326 label.setWordWrap(True)
2327 vbox.addWidget(label)
2329 seed_e = QTextEdit()
2330 seed_e.setMaximumHeight(100)
2331 vbox.addWidget(seed_e)
2334 grid = QGridLayout()
2336 gap_e = AmountEdit(None, True)
2338 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2339 grid.addWidget(gap_e, 2, 1)
2340 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2341 vbox.addLayout(grid)
2343 vbox.addLayout(ok_cancel_buttons(d))
2346 if not d.exec_(): return
2349 seed = str(seed_e.toPlainText())
2353 seed = mnemonic.mn_decode( seed.split(' ') )
2355 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2359 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2366 gap = int(unicode(gap_e.text()))
2368 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2373 def network_dialog(self):
2374 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2377 def show_seed(self):
2378 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2380 def password_dialog(self):
2381 if self.wallet.seed:
2382 ElectrumWindow.change_password_dialog(self.wallet)
2385 def restore_wallet(self):
2386 wallet = self.wallet
2387 # wait until we are connected, because the user might have selected another server
2388 if not wallet.interface.is_connected:
2389 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2390 waiting_dialog(waiting)
2392 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2393 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2395 wallet.set_up_to_date(False)
2396 wallet.interface.poke('synchronizer')
2397 waiting_dialog(waiting)
2398 if wallet.is_found():
2399 print_error( "Recovery successful" )
2401 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2408 w = ElectrumWindow(self.wallet, self.config)
2409 if url: w.set_url(url)