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],
258 "receive":[[370],[370,200,130]] }
260 class ElectrumWindow(QMainWindow):
262 def __init__(self, wallet, config):
263 QMainWindow.__init__(self)
267 self.current_account = self.config.get("current_account", None)
270 self.create_status_bar()
272 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
273 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
274 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
275 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
277 self.expert_mode = config.get('classic_expert_mode', False)
278 self.decimal_point = config.get('decimal_point', 8)
280 set_language(config.get('language'))
282 self.funds_error = False
283 self.completions = QStringListModel()
285 self.tabs = tabs = QTabWidget(self)
286 self.column_widths = self.config.get("column_widths", default_column_widths )
287 tabs.addTab(self.create_history_tab(), _('History') )
288 tabs.addTab(self.create_send_tab(), _('Send') )
289 tabs.addTab(self.create_receive_tab(), _('Receive') )
290 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
291 tabs.addTab(self.create_console_tab(), _('Console') )
292 tabs.setMinimumSize(600, 400)
293 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
294 self.setCentralWidget(tabs)
296 g = self.config.get("winpos-qt",[100, 100, 840, 400])
297 self.setGeometry(g[0], g[1], g[2], g[3])
298 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
299 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
300 self.setWindowTitle( title )
302 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
303 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
304 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
305 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
307 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
308 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
309 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
310 self.history_list.setFocus(True)
312 self.exchanger = exchange_rate.Exchanger(self)
313 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
315 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
316 if platform.system() == 'Windows':
317 n = 3 if self.wallet.seed else 2
318 tabs.setCurrentIndex (n)
319 tabs.setCurrentIndex (0)
321 # set initial message
322 self.console.showMessage(self.wallet.interface.banner)
324 # plugins that need to change the GUI do it here
325 self.run_hook('init_gui')
329 def init_plugins(self):
330 import imp, pkgutil, __builtin__
331 if __builtin__.use_local_modules:
332 fp, pathname, description = imp.find_module('plugins')
333 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
334 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
335 imp.load_module('electrum_plugins', fp, pathname, description)
336 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
338 import electrum_plugins
339 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
340 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
345 self.plugins.append( p.Plugin(self) )
347 print_msg("Error:cannot initialize plugin",p)
348 traceback.print_exc(file=sys.stdout)
351 def run_hook(self, name, *args):
352 for p in self.plugins:
353 if not p.is_enabled():
363 def set_label(self, name, text = None):
365 old_text = self.wallet.labels.get(name)
368 self.wallet.labels[name] = text
372 self.wallet.labels.pop(name)
374 self.run_hook('set_label', name, text, changed)
378 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
379 def getOpenFileName(self, title, filter = None):
380 directory = self.config.get('io_dir', os.path.expanduser('~'))
381 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
382 if fileName and directory != os.path.dirname(fileName):
383 self.config.set_key('io_dir', os.path.dirname(fileName), True)
386 def getSaveFileName(self, title, filename, filter = None):
387 directory = self.config.get('io_dir', os.path.expanduser('~'))
388 path = os.path.join( directory, filename )
389 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
390 if fileName and directory != os.path.dirname(fileName):
391 self.config.set_key('io_dir', os.path.dirname(fileName), True)
397 QMainWindow.close(self)
398 self.run_hook('close_main_window')
400 def connect_slots(self, sender):
401 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
402 self.previous_payto_e=''
404 def timer_actions(self):
405 self.run_hook('timer_actions')
407 def format_amount(self, x, is_diff=False):
408 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
410 def read_amount(self, x):
411 if x in['.', '']: return None
412 p = pow(10, self.decimal_point)
413 return int( p * Decimal(x) )
416 assert self.decimal_point in [5,8]
417 return "BTC" if self.decimal_point == 8 else "mBTC"
419 def update_status(self):
420 if self.wallet.interface and self.wallet.interface.is_connected:
421 if not self.wallet.up_to_date:
422 text = _("Synchronizing...")
423 icon = QIcon(":icons/status_waiting.png")
425 c, u = self.wallet.get_account_balance(self.current_account)
426 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
427 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
428 text += self.create_quote_text(Decimal(c+u)/100000000)
429 icon = QIcon(":icons/status_connected.png")
431 text = _("Not connected")
432 icon = QIcon(":icons/status_disconnected.png")
434 self.status_text = text
435 self.statusBar().showMessage(text)
436 self.status_button.setIcon( icon )
438 def update_wallet(self):
440 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
441 self.update_history_tab()
442 self.update_receive_tab()
443 self.update_contacts_tab()
444 self.update_completions()
447 def create_quote_text(self, btc_balance):
448 quote_currency = self.config.get("currency", "None")
449 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
450 if quote_balance is None:
453 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
456 def create_history_tab(self):
457 self.history_list = l = MyTreeWidget(self)
459 for i,width in enumerate(self.column_widths['history']):
460 l.setColumnWidth(i, width)
461 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
462 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
463 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
465 l.setContextMenuPolicy(Qt.CustomContextMenu)
466 l.customContextMenuRequested.connect(self.create_history_menu)
470 def create_history_menu(self, position):
471 self.history_list.selectedIndexes()
472 item = self.history_list.currentItem()
474 tx_hash = str(item.data(0, Qt.UserRole).toString())
475 if not tx_hash: return
477 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
478 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
479 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
480 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
483 def show_tx_details(self, tx):
484 dialog = QDialog(self)
486 dialog.setWindowTitle(_("Transaction Details"))
488 dialog.setLayout(vbox)
489 dialog.setMinimumSize(600,300)
492 if tx_hash in self.wallet.transactions.keys():
493 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
494 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
496 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
502 vbox.addWidget(QLabel("Transaction ID:"))
503 e = QLineEdit(tx_hash)
507 vbox.addWidget(QLabel("Date: %s"%time_str))
508 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
511 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
512 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
514 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
515 vbox.addWidget(QLabel("Transaction fee: unknown"))
517 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
519 vbox.addWidget( self.generate_transaction_information_widget(tx) )
521 ok_button = QPushButton(_("Close"))
522 ok_button.setDefault(True)
523 ok_button.clicked.connect(dialog.accept)
527 hbox.addWidget(ok_button)
531 def tx_label_clicked(self, item, column):
532 if column==2 and item.isSelected():
534 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
535 self.history_list.editItem( item, column )
536 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
539 def tx_label_changed(self, item, column):
543 tx_hash = str(item.data(0, Qt.UserRole).toString())
544 tx = self.wallet.transactions.get(tx_hash)
545 text = unicode( item.text(2) )
546 self.set_label(tx_hash, text)
548 item.setForeground(2, QBrush(QColor('black')))
550 text = self.wallet.get_default_label(tx_hash)
551 item.setText(2, text)
552 item.setForeground(2, QBrush(QColor('gray')))
556 def edit_label(self, is_recv):
557 l = self.receive_list if is_recv else self.contacts_list
558 item = l.currentItem()
559 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
560 l.editItem( item, 1 )
561 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
565 def address_label_clicked(self, item, column, l, column_addr, column_label):
566 if column == column_label and item.isSelected():
567 is_editable = item.data(0, 32).toBool()
570 addr = unicode( item.text(column_addr) )
571 label = unicode( item.text(column_label) )
572 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
573 l.editItem( item, column )
574 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
577 def address_label_changed(self, item, column, l, column_addr, column_label):
578 if column == column_label:
579 addr = unicode( item.text(column_addr) )
580 text = unicode( item.text(column_label) )
581 is_editable = item.data(0, 32).toBool()
585 changed = self.set_label(addr, text)
587 self.update_history_tab()
588 self.update_completions()
590 self.current_item_changed(item)
592 self.run_hook('item_changed', item, column)
595 def current_item_changed(self, a):
596 self.run_hook('current_item_changed', a)
600 def update_history_tab(self):
602 self.history_list.clear()
603 for item in self.wallet.get_tx_history(self.current_account):
604 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
607 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
613 icon = QIcon(":icons/unconfirmed.png")
615 icon = QIcon(":icons/clock%d.png"%conf)
617 icon = QIcon(":icons/confirmed.png")
620 icon = QIcon(":icons/unconfirmed.png")
622 if value is not None:
623 v_str = self.format_amount(value, True)
627 balance_str = self.format_amount(balance)
630 label, is_default_label = self.wallet.get_label(tx_hash)
632 label = _('Pruned transaction outputs')
633 is_default_label = False
635 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
636 item.setFont(2, QFont(MONOSPACE_FONT))
637 item.setFont(3, QFont(MONOSPACE_FONT))
638 item.setFont(4, QFont(MONOSPACE_FONT))
640 item.setForeground(3, QBrush(QColor("#BC1E1E")))
642 item.setData(0, Qt.UserRole, tx_hash)
643 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
645 item.setForeground(2, QBrush(QColor('grey')))
647 item.setIcon(0, icon)
648 self.history_list.insertTopLevelItem(0,item)
651 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
654 def create_send_tab(self):
659 grid.setColumnMinimumWidth(3,300)
660 grid.setColumnStretch(5,1)
663 self.payto_e = QLineEdit()
664 grid.addWidget(QLabel(_('Pay to')), 1, 0)
665 grid.addWidget(self.payto_e, 1, 1, 1, 3)
667 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)
669 completer = QCompleter()
670 completer.setCaseSensitivity(False)
671 self.payto_e.setCompleter(completer)
672 completer.setModel(self.completions)
674 self.message_e = QLineEdit()
675 grid.addWidget(QLabel(_('Description')), 2, 0)
676 grid.addWidget(self.message_e, 2, 1, 1, 3)
677 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)
679 self.amount_e = AmountEdit(self.base_unit)
680 grid.addWidget(QLabel(_('Amount')), 3, 0)
681 grid.addWidget(self.amount_e, 3, 1, 1, 2)
682 grid.addWidget(HelpButton(
683 _('Amount to be sent.') + '\n\n' \
684 + _('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.') \
685 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
687 self.fee_e = AmountEdit(self.base_unit)
688 grid.addWidget(QLabel(_('Fee')), 4, 0)
689 grid.addWidget(self.fee_e, 4, 1, 1, 2)
690 grid.addWidget(HelpButton(
691 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
692 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
693 + _('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)
696 b = EnterButton(_("Send"), self.do_send)
698 b = EnterButton(_("Create unsigned transaction"), self.do_send)
699 grid.addWidget(b, 6, 1)
701 b = EnterButton(_("Clear"),self.do_clear)
702 grid.addWidget(b, 6, 2)
704 self.payto_sig = QLabel('')
705 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
707 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
708 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
717 def entry_changed( is_fee ):
718 self.funds_error = False
720 if self.amount_e.is_shortcut:
721 self.amount_e.is_shortcut = False
722 c, u = self.wallet.get_account_balance(self.current_account)
723 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
724 fee = self.wallet.estimated_fee(inputs)
726 self.amount_e.setText( self.format_amount(amount) )
727 self.fee_e.setText( self.format_amount( fee ) )
730 amount = self.read_amount(str(self.amount_e.text()))
731 fee = self.read_amount(str(self.fee_e.text()))
733 if not is_fee: fee = None
736 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
738 self.fee_e.setText( self.format_amount( fee ) )
741 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
742 text = self.status_text
745 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
746 self.funds_error = True
747 text = _( "Not enough funds" )
749 self.statusBar().showMessage(text)
750 self.amount_e.setPalette(palette)
751 self.fee_e.setPalette(palette)
753 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
754 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
756 self.run_hook('create_send_tab', grid)
760 def update_completions(self):
762 for addr,label in self.wallet.labels.items():
763 if addr in self.wallet.addressbook:
764 l.append( label + ' <' + addr + '>')
766 self.run_hook('update_completions', l)
767 self.completions.setStringList(l)
771 return lambda s, *args: s.do_protect(func, args)
775 def do_send(self, password):
777 label = unicode( self.message_e.text() )
778 r = unicode( self.payto_e.text() )
781 # label or alias, with address in brackets
782 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
783 to_address = m.group(2) if m else r
785 if not is_valid(to_address):
786 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
790 amount = self.read_amount(unicode( self.amount_e.text()))
792 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
795 fee = self.read_amount(unicode( self.fee_e.text()))
797 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
801 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
802 except BaseException, e:
803 self.show_message(str(e))
806 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
807 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
810 self.run_hook('send_tx', tx)
813 self.set_label(tx.hash(), label)
816 h = self.wallet.send_tx(tx)
817 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
818 status, msg = self.wallet.receive_tx( h )
820 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
822 self.update_contacts_tab()
824 QMessageBox.warning(self, _('Error'), msg, _('OK'))
826 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
828 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
829 with open(fileName,'w') as f:
830 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
831 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
833 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
838 def set_url(self, url):
839 address, amount, label, message, signature, identity, url = util.parse_url(url)
840 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
842 if label and self.wallet.labels.get(address) != label:
843 if self.question('Give label "%s" to address %s ?'%(label,address)):
844 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
845 self.wallet.addressbook.append(address)
846 self.set_label(address, label)
848 self.run_hook('set_url', url, self.show_message, self.question)
850 self.tabs.setCurrentIndex(1)
851 label = self.wallet.labels.get(address)
852 m_addr = label + ' <'+ address +'>' if label else address
853 self.payto_e.setText(m_addr)
855 self.message_e.setText(message)
856 self.amount_e.setText(amount)
858 self.set_frozen(self.payto_e,True)
859 self.set_frozen(self.amount_e,True)
860 self.set_frozen(self.message_e,True)
861 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
863 self.payto_sig.setVisible(False)
866 self.payto_sig.setVisible(False)
867 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
869 self.set_frozen(e,False)
872 def set_frozen(self,entry,frozen):
874 entry.setReadOnly(True)
875 entry.setFrame(False)
877 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
878 entry.setPalette(palette)
880 entry.setReadOnly(False)
883 palette.setColor(entry.backgroundRole(), QColor('white'))
884 entry.setPalette(palette)
887 def toggle_freeze(self,addr):
889 if addr in self.wallet.frozen_addresses:
890 self.wallet.unfreeze(addr)
892 self.wallet.freeze(addr)
893 self.update_receive_tab()
895 def toggle_priority(self,addr):
897 if addr in self.wallet.prioritized_addresses:
898 self.wallet.unprioritize(addr)
900 self.wallet.prioritize(addr)
901 self.update_receive_tab()
904 def create_list_tab(self, headers):
905 "generic tab creation method"
906 l = MyTreeWidget(self)
907 l.setColumnCount( len(headers) )
908 l.setHeaderLabels( headers )
918 vbox.addWidget(buttons)
923 buttons.setLayout(hbox)
928 def create_receive_tab(self):
929 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
930 l.setContextMenuPolicy(Qt.CustomContextMenu)
931 l.customContextMenuRequested.connect(self.create_receive_menu)
932 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
933 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
934 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
935 self.receive_list = l
936 self.receive_buttons_hbox = hbox
941 def receive_tab_set_mode(self, i):
942 self.save_column_widths()
943 self.expert_mode = (i == 1)
944 self.config.set_key('classic_expert_mode', self.expert_mode, True)
946 self.update_receive_tab()
949 def save_column_widths(self):
950 if not self.expert_mode:
951 widths = [ self.receive_list.columnWidth(0) ]
954 for i in range(self.receive_list.columnCount() -1):
955 widths.append(self.receive_list.columnWidth(i))
956 self.column_widths["receive"][self.expert_mode] = widths
958 self.column_widths["history"] = []
959 for i in range(self.history_list.columnCount() - 1):
960 self.column_widths["history"].append(self.history_list.columnWidth(i))
962 self.column_widths["contacts"] = []
963 for i in range(self.contacts_list.columnCount() - 1):
964 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
967 def create_contacts_tab(self):
968 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
969 l.setContextMenuPolicy(Qt.CustomContextMenu)
970 l.customContextMenuRequested.connect(self.create_contact_menu)
971 for i,width in enumerate(self.column_widths['contacts']):
972 l.setColumnWidth(i, width)
974 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
975 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
976 self.contacts_list = l
977 self.contacts_buttons_hbox = hbox
978 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
983 def delete_imported_key(self, addr):
984 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
985 self.wallet.imported_keys.pop(addr)
986 self.update_receive_tab()
987 self.update_history_tab()
991 def create_receive_menu(self, position):
992 # fixme: this function apparently has a side effect.
993 # if it is not called the menu pops up several times
994 #self.receive_list.selectedIndexes()
996 item = self.receive_list.itemAt(position)
998 addr = unicode(item.text(0))
999 if not is_valid(addr):
1000 item.setExpanded(not item.isExpanded())
1003 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1004 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1005 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1006 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1007 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1008 if addr in self.wallet.imported_keys:
1009 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1011 if self.expert_mode:
1012 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1013 menu.addAction(t, lambda: self.toggle_freeze(addr))
1014 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1015 menu.addAction(t, lambda: self.toggle_priority(addr))
1017 self.run_hook('receive_menu', menu)
1018 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1021 def payto(self, addr):
1023 label = self.wallet.labels.get(addr)
1024 m_addr = label + ' <' + addr + '>' if label else addr
1025 self.tabs.setCurrentIndex(1)
1026 self.payto_e.setText(m_addr)
1027 self.amount_e.setFocus()
1030 def delete_contact(self, x):
1031 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1032 if x in self.wallet.addressbook:
1033 self.wallet.addressbook.remove(x)
1034 self.set_label(x, None)
1035 self.update_history_tab()
1036 self.update_contacts_tab()
1037 self.update_completions()
1040 def create_contact_menu(self, position):
1041 item = self.contacts_list.itemAt(position)
1043 addr = unicode(item.text(0))
1044 label = unicode(item.text(1))
1045 is_editable = item.data(0,32).toBool()
1046 payto_addr = item.data(0,33).toString()
1048 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1049 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1050 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1052 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1053 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1055 self.run_hook('create_contact_menu', menu, item)
1056 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1059 def update_receive_item(self, item):
1060 item.setFont(0, QFont(MONOSPACE_FONT))
1061 address = str(item.data(0,0).toString())
1062 label = self.wallet.labels.get(address,'')
1063 item.setData(1,0,label)
1064 item.setData(0,32, True) # is editable
1066 self.run_hook('update_receive_item', address, item)
1068 c, u = self.wallet.get_addr_balance(address)
1069 balance = self.format_amount(c + u)
1070 item.setData(2,0,balance)
1072 if self.expert_mode:
1073 if address in self.wallet.frozen_addresses:
1074 item.setBackgroundColor(0, QColor('lightblue'))
1075 elif address in self.wallet.prioritized_addresses:
1076 item.setBackgroundColor(0, QColor('lightgreen'))
1079 def update_receive_tab(self):
1080 l = self.receive_list
1083 l.setColumnHidden(2, not self.expert_mode)
1084 l.setColumnHidden(3, not self.expert_mode)
1085 if not self.expert_mode:
1086 width = self.column_widths['receive'][0][0]
1087 l.setColumnWidth(0, width)
1089 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1090 l.setColumnWidth(i, width)
1092 if self.current_account is None:
1093 account_items = self.wallet.accounts.items()
1094 elif self.current_account != -1:
1095 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1099 for k, account in account_items:
1100 name = account.get('name',str(k))
1101 c,u = self.wallet.get_account_balance(k)
1102 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1103 l.addTopLevelItem(account_item)
1104 account_item.setExpanded(True)
1106 for is_change in ([0,1] if self.expert_mode else [0]):
1107 if self.expert_mode:
1108 name = "Receiving" if not is_change else "Change"
1109 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1110 account_item.addChild(seq_item)
1111 if not is_change: seq_item.setExpanded(True)
1113 seq_item = account_item
1117 for address in account[is_change]:
1118 h = self.wallet.history.get(address,[])
1122 if gap > self.wallet.gap_limit:
1127 num_tx = '*' if h == ['*'] else "%d"%len(h)
1128 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1129 self.update_receive_item(item)
1131 item.setBackgroundColor(1, QColor('red'))
1132 seq_item.addChild(item)
1135 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1136 c,u = self.wallet.get_imported_balance()
1137 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1138 l.addTopLevelItem(account_item)
1139 account_item.setExpanded(True)
1140 for address in self.wallet.imported_keys.keys():
1141 item = QTreeWidgetItem( [ address, '', '', ''] )
1142 self.update_receive_item(item)
1143 account_item.addChild(item)
1146 # we use column 1 because column 0 may be hidden
1147 l.setCurrentItem(l.topLevelItem(0),1)
1150 def update_contacts_tab(self):
1152 l = self.contacts_list
1155 for address in self.wallet.addressbook:
1156 label = self.wallet.labels.get(address,'')
1157 n = self.wallet.get_num_tx(address)
1158 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1159 item.setFont(0, QFont(MONOSPACE_FONT))
1160 # 32 = label can be edited (bool)
1161 item.setData(0,32, True)
1163 item.setData(0,33, address)
1164 l.addTopLevelItem(item)
1166 self.run_hook('update_contacts_tab', l)
1167 l.setCurrentItem(l.topLevelItem(0))
1171 def create_console_tab(self):
1172 from qt_console import Console
1173 self.console = console = Console()
1174 self.console.history = self.config.get("console-history",[])
1175 self.console.history_index = len(self.console.history)
1177 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1178 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1180 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1182 def mkfunc(f, method):
1183 return lambda *args: apply( f, (method, args, self.password_dialog ))
1185 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1186 methods[m] = mkfunc(c._run, m)
1188 console.updateNamespace(methods)
1191 def change_account(self,s):
1192 if s == _("All accounts"):
1193 self.current_account = None
1195 accounts = self.wallet.get_accounts()
1196 for k, v in accounts.items():
1198 self.current_account = k
1199 self.update_history_tab()
1200 self.update_status()
1201 self.update_receive_tab()
1203 def create_status_bar(self):
1204 self.status_text = ""
1206 sb.setFixedHeight(35)
1207 qtVersion = qVersion()
1209 update_notification = UpdateLabel(self.config)
1210 if(update_notification.new_version):
1211 sb.addPermanentWidget(update_notification)
1213 accounts = self.wallet.get_accounts()
1214 if len(accounts) > 1:
1215 from_combo = QComboBox()
1216 from_combo.addItems([_("All accounts")] + accounts.values())
1217 from_combo.setCurrentIndex(0)
1218 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1219 sb.addPermanentWidget(from_combo)
1221 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1222 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1223 if self.wallet.seed:
1224 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1225 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1226 sb.addPermanentWidget( self.password_button )
1227 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1228 if self.wallet.seed:
1229 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1230 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1231 sb.addPermanentWidget( self.status_button )
1233 self.run_hook('create_status_bar', (sb,))
1235 self.setStatusBar(sb)
1239 self.config.set_key('gui', 'lite', True)
1242 self.lite.mini.show()
1244 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1245 self.lite.main(None)
1247 def new_contact_dialog(self):
1248 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1249 address = unicode(text)
1251 if is_valid(address):
1252 self.wallet.addressbook.append(address)
1254 self.update_contacts_tab()
1255 self.update_history_tab()
1256 self.update_completions()
1258 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1260 def show_master_public_key(self):
1261 dialog = QDialog(self)
1263 dialog.setWindowTitle(_("Master Public Key"))
1265 main_text = QTextEdit()
1266 main_text.setText(self.wallet.get_master_public_key())
1267 main_text.setReadOnly(True)
1268 main_text.setMaximumHeight(170)
1269 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1271 ok_button = QPushButton(_("OK"))
1272 ok_button.setDefault(True)
1273 ok_button.clicked.connect(dialog.accept)
1275 main_layout = QGridLayout()
1276 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1278 main_layout.addWidget(main_text, 1, 0)
1279 main_layout.addWidget(qrw, 1, 1 )
1281 vbox = QVBoxLayout()
1282 vbox.addLayout(main_layout)
1283 hbox = QHBoxLayout()
1285 hbox.addWidget(ok_button)
1286 vbox.addLayout(hbox)
1288 dialog.setLayout(vbox)
1293 def show_seed_dialog(self, password):
1294 if not self.wallet.seed:
1295 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1298 seed = self.wallet.decode_seed(password)
1300 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1302 self.show_seed(seed, self.wallet.imported_keys, self)
1306 def show_seed(self, seed, imported_keys, parent=None):
1307 dialog = QDialog(parent)
1309 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1311 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1313 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1315 seed_text = QTextEdit(brainwallet)
1316 seed_text.setReadOnly(True)
1317 seed_text.setMaximumHeight(130)
1319 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1320 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1321 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1322 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1324 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1325 label2 = QLabel(msg2)
1326 label2.setWordWrap(True)
1329 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1330 logo.setMaximumWidth(60)
1332 qrw = QRCodeWidget(seed)
1334 ok_button = QPushButton(_("OK"))
1335 ok_button.setDefault(True)
1336 ok_button.clicked.connect(dialog.accept)
1338 grid = QGridLayout()
1339 #main_layout.addWidget(logo, 0, 0)
1341 grid.addWidget(logo, 0, 0)
1342 grid.addWidget(label1, 0, 1)
1344 grid.addWidget(seed_text, 1, 0, 1, 2)
1346 grid.addWidget(qrw, 0, 2, 2, 1)
1348 vbox = QVBoxLayout()
1349 vbox.addLayout(grid)
1350 vbox.addWidget(label2)
1352 hbox = QHBoxLayout()
1354 hbox.addWidget(ok_button)
1355 vbox.addLayout(hbox)
1357 dialog.setLayout(vbox)
1360 def show_qrcode(self, data, title = "QR code"):
1364 d.setWindowTitle(title)
1365 d.setMinimumSize(270, 300)
1366 vbox = QVBoxLayout()
1367 qrw = QRCodeWidget(data)
1368 vbox.addWidget(qrw, 1)
1369 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1370 hbox = QHBoxLayout()
1374 filename = "qrcode.bmp"
1375 bmp.save_qrcode(qrw.qr, filename)
1376 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1378 b = QPushButton(_("Save"))
1380 b.clicked.connect(print_qr)
1382 b = QPushButton(_("Close"))
1384 b.clicked.connect(d.accept)
1387 vbox.addLayout(hbox)
1392 def do_protect(self, func, args):
1393 if self.wallet.use_encryption:
1394 password = self.password_dialog()
1400 if args != (False,):
1401 args = (self,) + args + (password,)
1403 args = (self,password)
1408 def show_private_key(self, address, password):
1409 if not address: return
1411 pk = self.wallet.get_private_key(address, password)
1412 except BaseException, e:
1413 self.show_message(str(e))
1415 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1419 def do_sign(self, address, message, signature, password):
1421 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1422 signature.setText(sig)
1423 except BaseException, e:
1424 self.show_message(str(e))
1426 def sign_message(self, address):
1427 if not address: return
1430 d.setWindowTitle(_('Sign Message'))
1431 d.setMinimumSize(410, 290)
1433 tab_widget = QTabWidget()
1435 layout = QGridLayout(tab)
1437 sign_address = QLineEdit()
1439 sign_address.setText(address)
1440 layout.addWidget(QLabel(_('Address')), 1, 0)
1441 layout.addWidget(sign_address, 1, 1)
1443 sign_message = QTextEdit()
1444 layout.addWidget(QLabel(_('Message')), 2, 0)
1445 layout.addWidget(sign_message, 2, 1)
1446 layout.setRowStretch(2,3)
1448 sign_signature = QTextEdit()
1449 layout.addWidget(QLabel(_('Signature')), 3, 0)
1450 layout.addWidget(sign_signature, 3, 1)
1451 layout.setRowStretch(3,1)
1454 hbox = QHBoxLayout()
1455 b = QPushButton(_("Sign"))
1457 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1458 b = QPushButton(_("Close"))
1459 b.clicked.connect(d.accept)
1461 layout.addLayout(hbox, 4, 1)
1462 tab_widget.addTab(tab, _("Sign"))
1466 layout = QGridLayout(tab)
1468 verify_address = QLineEdit()
1469 layout.addWidget(QLabel(_('Address')), 1, 0)
1470 layout.addWidget(verify_address, 1, 1)
1472 verify_message = QTextEdit()
1473 layout.addWidget(QLabel(_('Message')), 2, 0)
1474 layout.addWidget(verify_message, 2, 1)
1475 layout.setRowStretch(2,3)
1477 verify_signature = QTextEdit()
1478 layout.addWidget(QLabel(_('Signature')), 3, 0)
1479 layout.addWidget(verify_signature, 3, 1)
1480 layout.setRowStretch(3,1)
1484 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1485 self.show_message(_("Signature verified"))
1486 except BaseException, e:
1487 self.show_message(str(e))
1490 hbox = QHBoxLayout()
1491 b = QPushButton(_("Verify"))
1492 b.clicked.connect(do_verify)
1494 b = QPushButton(_("Close"))
1495 b.clicked.connect(d.accept)
1497 layout.addLayout(hbox, 4, 1)
1498 tab_widget.addTab(tab, _("Verify"))
1500 vbox = QVBoxLayout()
1501 vbox.addWidget(tab_widget)
1508 def question(self, msg):
1509 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1511 def show_message(self, msg):
1512 QMessageBox.information(self, _('Message'), msg, _('OK'))
1514 def password_dialog(self ):
1521 vbox = QVBoxLayout()
1522 msg = _('Please enter your password')
1523 vbox.addWidget(QLabel(msg))
1525 grid = QGridLayout()
1527 grid.addWidget(QLabel(_('Password')), 1, 0)
1528 grid.addWidget(pw, 1, 1)
1529 vbox.addLayout(grid)
1531 vbox.addLayout(ok_cancel_buttons(d))
1534 self.run_hook('password_dialog', pw, grid, 1)
1535 if not d.exec_(): return
1536 return unicode(pw.text())
1543 def change_password_dialog( wallet, parent=None ):
1546 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1554 new_pw = QLineEdit()
1555 new_pw.setEchoMode(2)
1556 conf_pw = QLineEdit()
1557 conf_pw.setEchoMode(2)
1559 vbox = QVBoxLayout()
1561 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1562 +_('To disable wallet encryption, enter an empty new password.')) \
1563 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1565 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1566 +_("Leave these fields empty if you want to disable encryption.")
1567 vbox.addWidget(QLabel(msg))
1569 grid = QGridLayout()
1572 if wallet.use_encryption:
1573 grid.addWidget(QLabel(_('Password')), 1, 0)
1574 grid.addWidget(pw, 1, 1)
1576 grid.addWidget(QLabel(_('New Password')), 2, 0)
1577 grid.addWidget(new_pw, 2, 1)
1579 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1580 grid.addWidget(conf_pw, 3, 1)
1581 vbox.addLayout(grid)
1583 vbox.addLayout(ok_cancel_buttons(d))
1586 if not d.exec_(): return
1588 password = unicode(pw.text()) if wallet.use_encryption else None
1589 new_password = unicode(new_pw.text())
1590 new_password2 = unicode(conf_pw.text())
1593 seed = wallet.decode_seed(password)
1595 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1598 if new_password != new_password2:
1599 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1600 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1602 wallet.update_password(seed, password, new_password)
1604 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1605 parent.password_button.setIcon( icon )
1609 def generate_transaction_information_widget(self, tx):
1610 tabs = QTabWidget(self)
1613 grid_ui = QGridLayout(tab1)
1614 grid_ui.setColumnStretch(0,1)
1615 tabs.addTab(tab1, _('Outputs') )
1617 tree_widget = MyTreeWidget(self)
1618 tree_widget.setColumnCount(2)
1619 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1620 tree_widget.setColumnWidth(0, 300)
1621 tree_widget.setColumnWidth(1, 50)
1623 for address, value in tx.outputs:
1624 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1625 tree_widget.addTopLevelItem(item)
1627 tree_widget.setMaximumHeight(100)
1629 grid_ui.addWidget(tree_widget)
1632 grid_ui = QGridLayout(tab2)
1633 grid_ui.setColumnStretch(0,1)
1634 tabs.addTab(tab2, _('Inputs') )
1636 tree_widget = MyTreeWidget(self)
1637 tree_widget.setColumnCount(2)
1638 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1640 for input_line in tx.inputs:
1641 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1642 tree_widget.addTopLevelItem(item)
1644 tree_widget.setMaximumHeight(100)
1646 grid_ui.addWidget(tree_widget)
1650 def tx_dict_from_text(self, txt):
1652 tx_dict = json.loads(str(txt))
1653 assert "hex" in tx_dict.keys()
1654 assert "complete" in tx_dict.keys()
1655 if not tx_dict["complete"]:
1656 assert "input_info" in tx_dict.keys()
1658 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1663 def read_tx_from_file(self):
1664 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1668 with open(fileName, "r") as f:
1669 file_content = f.read()
1670 except (ValueError, IOError, os.error), reason:
1671 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1673 return self.tx_dict_from_text(file_content)
1677 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1679 self.wallet.signrawtransaction(tx, input_info, [], password)
1681 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1683 with open(fileName, "w+") as f:
1684 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1685 self.show_message(_("Transaction saved successfully"))
1688 except BaseException, e:
1689 self.show_message(str(e))
1692 def send_raw_transaction(self, raw_tx, dialog = ""):
1693 result, result_message = self.wallet.sendtx( raw_tx )
1695 self.show_message("Transaction successfully sent: %s" % (result_message))
1699 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1701 def do_process_from_text(self):
1702 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1705 tx_dict = self.tx_dict_from_text(text)
1707 self.create_process_transaction_window(tx_dict)
1709 def do_process_from_file(self):
1710 tx_dict = self.read_tx_from_file()
1712 self.create_process_transaction_window(tx_dict)
1714 def create_process_transaction_window(self, tx_dict):
1715 tx = Transaction(tx_dict["hex"])
1717 dialog = QDialog(self)
1718 dialog.setMinimumWidth(500)
1719 dialog.setWindowTitle(_('Process raw transaction'))
1725 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1726 l.addWidget(QLabel(_("Actions")), 4,0)
1728 if tx_dict["complete"] == False:
1729 l.addWidget(QLabel(_("Unsigned")), 3,1)
1730 if self.wallet.seed :
1731 b = QPushButton("Sign transaction")
1732 input_info = json.loads(tx_dict["input_info"])
1733 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1734 l.addWidget(b, 4, 1)
1736 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1738 l.addWidget(QLabel(_("Signed")), 3,1)
1739 b = QPushButton("Broadcast transaction")
1740 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1743 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1744 cancelButton = QPushButton(_("Cancel"))
1745 cancelButton.clicked.connect(lambda: dialog.done(0))
1746 l.addWidget(cancelButton, 4,2)
1752 def do_export_privkeys(self, password):
1753 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.")))
1756 select_export = _('Select file to export your private keys to')
1757 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1759 with open(fileName, "w+") as csvfile:
1760 transaction = csv.writer(csvfile)
1761 transaction.writerow(["address", "private_key"])
1764 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1765 transaction.writerow(["%34s"%addr,pk])
1767 self.show_message(_("Private keys exported."))
1769 except (IOError, os.error), reason:
1770 export_error_label = _("Electrum was unable to produce a private key-export.")
1771 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1773 except BaseException, e:
1774 self.show_message(str(e))
1778 def do_import_labels(self):
1779 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1780 if not labelsFile: return
1782 f = open(labelsFile, 'r')
1785 for key, value in json.loads(data).items():
1786 self.wallet.labels[key] = value
1788 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1789 except (IOError, os.error), reason:
1790 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1793 def do_export_labels(self):
1794 labels = self.wallet.labels
1796 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1798 with open(fileName, 'w+') as f:
1799 json.dump(labels, f)
1800 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1801 except (IOError, os.error), reason:
1802 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1805 def do_export_history(self):
1806 from gui_lite import csv_transaction
1807 csv_transaction(self.wallet)
1811 def do_import_privkey(self, password):
1812 if not self.wallet.imported_keys:
1813 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1814 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1815 + _('Are you sure you understand what you are doing?'), 3, 4)
1818 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1821 text = str(text).split()
1826 addr = self.wallet.import_key(key, password)
1827 except BaseException as e:
1833 addrlist.append(addr)
1835 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1837 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1838 self.update_receive_tab()
1839 self.update_history_tab()
1842 def settings_dialog(self):
1844 d.setWindowTitle(_('Electrum Settings'))
1846 vbox = QVBoxLayout()
1848 tabs = QTabWidget(self)
1849 self.settings_tab = tabs
1850 vbox.addWidget(tabs)
1853 grid_ui = QGridLayout(tab1)
1854 grid_ui.setColumnStretch(0,1)
1855 tabs.addTab(tab1, _('Display') )
1857 nz_label = QLabel(_('Display zeros'))
1858 grid_ui.addWidget(nz_label, 0, 0)
1859 nz_e = AmountEdit(None,True)
1860 nz_e.setText("%d"% self.wallet.num_zeros)
1861 grid_ui.addWidget(nz_e, 0, 1)
1862 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1863 grid_ui.addWidget(HelpButton(msg), 0, 2)
1864 if not self.config.is_modifiable('num_zeros'):
1865 for w in [nz_e, nz_label]: w.setEnabled(False)
1867 lang_label=QLabel(_('Language') + ':')
1868 grid_ui.addWidget(lang_label, 1, 0)
1869 lang_combo = QComboBox()
1870 from i18n import languages
1871 lang_combo.addItems(languages.values())
1873 index = languages.keys().index(self.config.get("language",''))
1876 lang_combo.setCurrentIndex(index)
1877 grid_ui.addWidget(lang_combo, 1, 1)
1878 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1879 if not self.config.is_modifiable('language'):
1880 for w in [lang_combo, lang_label]: w.setEnabled(False)
1882 currencies = self.exchanger.get_currencies()
1883 currencies.insert(0, "None")
1885 cur_label=QLabel(_('Currency') + ':')
1886 grid_ui.addWidget(cur_label , 2, 0)
1887 cur_combo = QComboBox()
1888 cur_combo.addItems(currencies)
1890 index = currencies.index(self.config.get('currency', "None"))
1893 cur_combo.setCurrentIndex(index)
1894 grid_ui.addWidget(cur_combo, 2, 1)
1895 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1897 expert_cb = QCheckBox(_('Expert mode'))
1898 expert_cb.setChecked(self.expert_mode)
1899 grid_ui.addWidget(expert_cb, 3, 0)
1900 hh = _('In expert mode, your client will:') + '\n' \
1901 + _(' - Show change addresses in the Receive tab') + '\n' \
1902 + _(' - Display the balance of each address') + '\n' \
1903 + _(' - Add freeze/prioritize actions to addresses.')
1904 grid_ui.addWidget(HelpButton(hh), 3, 2)
1905 grid_ui.setRowStretch(4,1)
1909 grid_wallet = QGridLayout(tab2)
1910 grid_wallet.setColumnStretch(0,1)
1911 tabs.addTab(tab2, _('Wallet') )
1913 fee_label = QLabel(_('Transaction fee'))
1914 grid_wallet.addWidget(fee_label, 0, 0)
1915 fee_e = AmountEdit(self.base_unit)
1916 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1917 grid_wallet.addWidget(fee_e, 0, 2)
1918 msg = _('Fee per kilobyte of transaction.') + ' ' \
1919 + _('Recommended value') + ': ' + self.format_amount(20000)
1920 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1921 if not self.config.is_modifiable('fee_per_kb'):
1922 for w in [fee_e, fee_label]: w.setEnabled(False)
1924 usechange_cb = QCheckBox(_('Use change addresses'))
1925 usechange_cb.setChecked(self.wallet.use_change)
1926 grid_wallet.addWidget(usechange_cb, 1, 0)
1927 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1928 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1930 gap_label = QLabel(_('Gap limit'))
1931 grid_wallet.addWidget(gap_label, 2, 0)
1932 gap_e = AmountEdit(None,True)
1933 gap_e.setText("%d"% self.wallet.gap_limit)
1934 grid_wallet.addWidget(gap_e, 2, 2)
1935 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1936 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1937 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1938 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1939 + _('Warning') + ': ' \
1940 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1941 + _('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'
1942 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1943 if not self.config.is_modifiable('gap_limit'):
1944 for w in [gap_e, gap_label]: w.setEnabled(False)
1946 units = ['BTC', 'mBTC']
1947 unit_label = QLabel(_('Base unit'))
1948 grid_wallet.addWidget(unit_label, 3, 0)
1949 unit_combo = QComboBox()
1950 unit_combo.addItems(units)
1951 unit_combo.setCurrentIndex(units.index(self.base_unit()))
1952 grid_wallet.addWidget(unit_combo, 3, 2)
1953 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1954 + '\n1BTC=1000mBTC.\n' \
1955 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1956 grid_wallet.setRowStretch(4,1)
1961 grid_io = QGridLayout(tab3)
1962 grid_io.setColumnStretch(0,1)
1963 tabs.addTab(tab3, _('Import/Export') )
1965 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1966 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1967 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1968 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1970 grid_io.addWidget(QLabel(_('History')), 2, 0)
1971 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1972 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1974 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1976 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1977 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1978 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1980 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1981 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1982 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1983 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1984 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1987 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1988 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1989 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1990 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1992 grid_io.setRowStretch(6,1)
1997 tab5 = QScrollArea()
1998 tab5.setEnabled(True)
1999 tab5.setWidgetResizable(True)
2001 grid_plugins = QGridLayout()
2002 grid_plugins.setColumnStretch(0,1)
2005 w.setLayout(grid_plugins)
2007 tab5.setMaximumSize(tab3.size()) # optional
2009 w.setMinimumHeight(len(self.plugins)*35)
2011 tabs.addTab(tab5, _('Plugins') )
2012 def mk_toggle(cb, p):
2013 return lambda: cb.setChecked(p.toggle())
2014 for i, p in enumerate(self.plugins):
2016 name, description = p.get_info()
2017 cb = QCheckBox(name)
2018 cb.setDisabled(not p.is_available())
2019 cb.setChecked(p.is_enabled())
2020 cb.clicked.connect(mk_toggle(cb,p))
2021 grid_plugins.addWidget(cb, i, 0)
2022 if p.requires_settings():
2023 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2024 grid_plugins.addWidget(HelpButton(description), i, 2)
2026 print_msg("Error: cannot display plugin", p)
2027 traceback.print_exc(file=sys.stdout)
2028 grid_plugins.setRowStretch(i+1,1)
2030 self.run_hook('create_settings_tab', tabs)
2032 vbox.addLayout(ok_cancel_buttons(d))
2036 if not d.exec_(): return
2038 fee = unicode(fee_e.text())
2040 fee = self.read_amount(fee)
2042 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2045 if self.wallet.fee != fee:
2046 self.wallet.fee = fee
2049 nz = unicode(nz_e.text())
2054 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2057 if self.wallet.num_zeros != nz:
2058 self.wallet.num_zeros = nz
2059 self.config.set_key('num_zeros', nz, True)
2060 self.update_history_tab()
2061 self.update_receive_tab()
2063 usechange_result = usechange_cb.isChecked()
2064 if self.wallet.use_change != usechange_result:
2065 self.wallet.use_change = usechange_result
2066 self.config.set_key('use_change', self.wallet.use_change, True)
2068 unit_result = units[unit_combo.currentIndex()]
2069 if self.base_unit() != unit_result:
2070 self.decimal_point = 8 if unit_result == 'BTC' else 5
2071 self.config.set_key('decimal_point', self.decimal_point, True)
2072 self.update_history_tab()
2073 self.update_status()
2076 n = int(gap_e.text())
2078 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2081 if self.wallet.gap_limit != n:
2082 r = self.wallet.change_gap_limit(n)
2084 self.update_receive_tab()
2085 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2087 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2089 need_restart = False
2091 lang_request = languages.keys()[lang_combo.currentIndex()]
2092 if lang_request != self.config.get('language'):
2093 self.config.set_key("language", lang_request, True)
2096 cur_request = str(currencies[cur_combo.currentIndex()])
2097 if cur_request != self.config.get('currency', "None"):
2098 self.config.set_key('currency', cur_request, True)
2099 self.update_wallet()
2101 self.run_hook('close_settings_dialog')
2104 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2106 self.receive_tab_set_mode(expert_cb.isChecked())
2110 def network_dialog(wallet, parent=None):
2111 interface = wallet.interface
2113 if interface.is_connected:
2114 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2116 status = _("Not connected")
2117 server = interface.server
2120 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2121 server = interface.server
2123 plist, servers_list = interface.get_servers_list()
2127 d.setWindowTitle(_('Server'))
2128 d.setMinimumSize(375, 20)
2130 vbox = QVBoxLayout()
2133 hbox = QHBoxLayout()
2135 l.setPixmap(QPixmap(":icons/network.png"))
2138 hbox.addWidget(QLabel(status))
2140 vbox.addLayout(hbox)
2144 grid = QGridLayout()
2146 vbox.addLayout(grid)
2149 server_protocol = QComboBox()
2150 server_host = QLineEdit()
2151 server_host.setFixedWidth(200)
2152 server_port = QLineEdit()
2153 server_port.setFixedWidth(60)
2155 protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2156 protocol_letters = 'thsg'
2157 server_protocol.addItems(protocol_names)
2159 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2160 grid.addWidget(server_protocol, 0, 1)
2161 grid.addWidget(server_host, 0, 2)
2162 grid.addWidget(server_port, 0, 3)
2164 def change_protocol(p):
2165 protocol = protocol_letters[p]
2166 host = unicode(server_host.text())
2167 pp = plist.get(host,DEFAULT_PORTS)
2168 if protocol not in pp.keys():
2169 protocol = pp.keys()[0]
2171 server_host.setText( host )
2172 server_port.setText( port )
2174 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2176 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2177 servers_list_widget = QTreeWidget(parent)
2178 servers_list_widget.setHeaderLabels( [ label, _('Limit') ] )
2179 servers_list_widget.setMaximumHeight(150)
2180 servers_list_widget.setColumnWidth(0, 240)
2181 for _host in servers_list.keys():
2182 pruning_level = servers_list[_host].get('pruning','')
2183 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2184 servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2186 def change_server(host, protocol=None):
2187 pp = plist.get(host,DEFAULT_PORTS)
2189 port = pp.get(protocol)
2190 if not port: protocol = None
2193 if 's' in pp.keys():
2195 port = pp.get(protocol)
2197 protocol = pp.keys()[0]
2198 port = pp.get(protocol)
2200 server_host.setText( host )
2201 server_port.setText( port )
2202 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2204 if not plist: return
2205 for p in protocol_letters:
2206 i = protocol_letters.index(p)
2207 j = server_protocol.model().index(i,0)
2208 if p not in pp.keys() and interface.is_connected:
2209 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2211 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2214 host, port, protocol = server.split(':')
2215 change_server(host,protocol)
2217 servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'),
2218 lambda x,y: change_server(unicode(x.text(0))))
2219 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2221 if not wallet.config.is_modifiable('server'):
2222 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2225 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2226 autocycle_cb.setChecked(wallet.config.get('auto_cycle', True))
2227 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2228 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2231 proxy_mode = QComboBox()
2232 proxy_host = QLineEdit()
2233 proxy_host.setFixedWidth(200)
2234 proxy_port = QLineEdit()
2235 proxy_port.setFixedWidth(60)
2236 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2238 def check_for_disable(index = False):
2239 if proxy_mode.currentText() != 'NONE':
2240 proxy_host.setEnabled(True)
2241 proxy_port.setEnabled(True)
2243 proxy_host.setEnabled(False)
2244 proxy_port.setEnabled(False)
2247 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2249 if not wallet.config.is_modifiable('proxy'):
2250 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2252 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2253 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2254 proxy_host.setText(proxy_config.get("host"))
2255 proxy_port.setText(proxy_config.get("port"))
2257 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2258 grid.addWidget(proxy_mode, 2, 1)
2259 grid.addWidget(proxy_host, 2, 2)
2260 grid.addWidget(proxy_port, 2, 3)
2263 vbox.addLayout(ok_cancel_buttons(d))
2266 if not d.exec_(): return
2268 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2269 if proxy_mode.currentText() != 'NONE':
2270 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2274 wallet.config.set_key("proxy", proxy, True)
2275 wallet.config.set_key("server", server, True)
2276 interface.set_server(server, proxy)
2277 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2280 def closeEvent(self, event):
2282 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2283 self.save_column_widths()
2284 self.config.set_key("column_widths", self.column_widths, True)
2285 self.config.set_key("console-history",self.console.history[-50:])
2291 def __init__(self, wallet, config, app=None):
2292 self.wallet = wallet
2293 self.config = config
2295 self.app = QApplication(sys.argv)
2298 def restore_or_create(self):
2299 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2300 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2301 if r==2: return None
2302 return 'restore' if r==1 else 'create'
2305 def verify_seed(self):
2306 r = self.seed_dialog(False)
2307 if r != self.wallet.seed:
2308 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2315 def seed_dialog(self, is_restore=True):
2319 vbox = QVBoxLayout()
2321 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2323 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2325 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2328 label.setWordWrap(True)
2329 vbox.addWidget(label)
2331 seed_e = QTextEdit()
2332 seed_e.setMaximumHeight(100)
2333 vbox.addWidget(seed_e)
2336 grid = QGridLayout()
2338 gap_e = AmountEdit(None, True)
2340 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2341 grid.addWidget(gap_e, 2, 1)
2342 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2343 vbox.addLayout(grid)
2345 vbox.addLayout(ok_cancel_buttons(d))
2348 if not d.exec_(): return
2351 seed = str(seed_e.toPlainText())
2355 seed = mnemonic.mn_decode( seed.split(' ') )
2357 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2361 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2368 gap = int(unicode(gap_e.text()))
2370 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2375 def network_dialog(self):
2376 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2379 def show_seed(self):
2380 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2382 def password_dialog(self):
2383 if self.wallet.seed:
2384 ElectrumWindow.change_password_dialog(self.wallet)
2387 def restore_wallet(self):
2388 wallet = self.wallet
2389 # wait until we are connected, because the user might have selected another server
2390 if not wallet.interface.is_connected:
2391 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2392 waiting_dialog(waiting)
2394 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2395 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2397 wallet.set_up_to_date(False)
2398 wallet.interface.poke('synchronizer')
2399 waiting_dialog(waiting)
2400 if wallet.is_found():
2401 print_error( "Recovery successful" )
2403 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2410 w = ElectrumWindow(self.wallet, self.config)
2411 if url: w.set_url(url)