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)
871 def set_frozen(self,entry,frozen):
873 entry.setReadOnly(True)
874 entry.setFrame(False)
876 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
877 entry.setPalette(palette)
879 entry.setReadOnly(False)
882 palette.setColor(entry.backgroundRole(), QColor('white'))
883 entry.setPalette(palette)
886 def toggle_freeze(self,addr):
888 if addr in self.wallet.frozen_addresses:
889 self.wallet.unfreeze(addr)
891 self.wallet.freeze(addr)
892 self.update_receive_tab()
894 def toggle_priority(self,addr):
896 if addr in self.wallet.prioritized_addresses:
897 self.wallet.unprioritize(addr)
899 self.wallet.prioritize(addr)
900 self.update_receive_tab()
903 def create_list_tab(self, headers):
904 "generic tab creation method"
905 l = MyTreeWidget(self)
906 l.setColumnCount( len(headers) )
907 l.setHeaderLabels( headers )
917 vbox.addWidget(buttons)
922 buttons.setLayout(hbox)
927 def create_receive_tab(self):
928 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
929 l.setContextMenuPolicy(Qt.CustomContextMenu)
930 l.customContextMenuRequested.connect(self.create_receive_menu)
931 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
932 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
933 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
934 self.receive_list = l
935 self.receive_buttons_hbox = hbox
940 def receive_tab_set_mode(self, i):
941 self.save_column_widths()
942 self.expert_mode = (i == 1)
943 self.config.set_key('classic_expert_mode', self.expert_mode, True)
945 self.update_receive_tab()
948 def save_column_widths(self):
949 if not self.expert_mode:
950 widths = [ self.receive_list.columnWidth(0) ]
953 for i in range(self.receive_list.columnCount() -1):
954 widths.append(self.receive_list.columnWidth(i))
955 self.column_widths["receive"][self.expert_mode] = widths
957 self.column_widths["history"] = []
958 for i in range(self.history_list.columnCount() - 1):
959 self.column_widths["history"].append(self.history_list.columnWidth(i))
961 self.column_widths["contacts"] = []
962 for i in range(self.contacts_list.columnCount() - 1):
963 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
966 def create_contacts_tab(self):
967 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
968 l.setContextMenuPolicy(Qt.CustomContextMenu)
969 l.customContextMenuRequested.connect(self.create_contact_menu)
970 for i,width in enumerate(self.column_widths['contacts']):
971 l.setColumnWidth(i, width)
973 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
974 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
975 self.contacts_list = l
976 self.contacts_buttons_hbox = hbox
977 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
982 def delete_imported_key(self, addr):
983 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
984 self.wallet.imported_keys.pop(addr)
985 self.update_receive_tab()
986 self.update_history_tab()
990 def create_receive_menu(self, position):
991 # fixme: this function apparently has a side effect.
992 # if it is not called the menu pops up several times
993 #self.receive_list.selectedIndexes()
995 item = self.receive_list.itemAt(position)
997 addr = unicode(item.text(0))
998 if not is_valid(addr):
999 item.setExpanded(not item.isExpanded())
1002 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1003 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1004 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1005 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1006 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1007 if addr in self.wallet.imported_keys:
1008 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1010 if self.expert_mode:
1011 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1012 menu.addAction(t, lambda: self.toggle_freeze(addr))
1013 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1014 menu.addAction(t, lambda: self.toggle_priority(addr))
1016 self.run_hook('receive_menu', menu)
1017 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1020 def payto(self, addr):
1022 label = self.wallet.labels.get(addr)
1023 m_addr = label + ' <' + addr + '>' if label else addr
1024 self.tabs.setCurrentIndex(1)
1025 self.payto_e.setText(m_addr)
1026 self.amount_e.setFocus()
1029 def delete_contact(self, x):
1030 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1031 if x in self.wallet.addressbook:
1032 self.wallet.addressbook.remove(x)
1033 self.set_label(x, None)
1034 self.update_history_tab()
1035 self.update_contacts_tab()
1036 self.update_completions()
1039 def create_contact_menu(self, position):
1040 item = self.contacts_list.itemAt(position)
1042 addr = unicode(item.text(0))
1043 label = unicode(item.text(1))
1044 is_editable = item.data(0,32).toBool()
1045 payto_addr = item.data(0,33).toString()
1047 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1048 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1049 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1051 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1052 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1054 self.run_hook('create_contact_menu', menu, item)
1055 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1058 def update_receive_item(self, item):
1059 item.setFont(0, QFont(MONOSPACE_FONT))
1060 address = str(item.data(0,0).toString())
1061 label = self.wallet.labels.get(address,'')
1062 item.setData(1,0,label)
1063 item.setData(0,32, True) # is editable
1065 self.run_hook('update_receive_item', address, item)
1067 c, u = self.wallet.get_addr_balance(address)
1068 balance = self.format_amount(c + u)
1069 item.setData(2,0,balance)
1071 if self.expert_mode:
1072 if address in self.wallet.frozen_addresses:
1073 item.setBackgroundColor(0, QColor('lightblue'))
1074 elif address in self.wallet.prioritized_addresses:
1075 item.setBackgroundColor(0, QColor('lightgreen'))
1078 def update_receive_tab(self):
1079 l = self.receive_list
1082 l.setColumnHidden(2, not self.expert_mode)
1083 l.setColumnHidden(3, not self.expert_mode)
1084 if not self.expert_mode:
1085 width = self.column_widths['receive'][0][0]
1086 l.setColumnWidth(0, width)
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 seed_dialog(wallet, parent=None):
1612 vbox = QVBoxLayout()
1613 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1614 vbox.addWidget(QLabel(msg))
1616 grid = QGridLayout()
1619 seed_e = QLineEdit()
1620 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1621 grid.addWidget(seed_e, 1, 1)
1622 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1624 gap_e = AmountEdit(None, True)
1626 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1627 grid.addWidget(gap_e, 2, 1)
1628 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1629 vbox.addLayout(grid)
1631 vbox.addLayout(ok_cancel_buttons(d))
1634 if not d.exec_(): return
1637 gap = int(unicode(gap_e.text()))
1639 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1643 seed = str(seed_e.text())
1646 print_error("Warning: Not hex, trying decode")
1648 seed = mnemonic.mn_decode( seed.split(' ') )
1650 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1654 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1659 def generate_transaction_information_widget(self, tx):
1660 tabs = QTabWidget(self)
1663 grid_ui = QGridLayout(tab1)
1664 grid_ui.setColumnStretch(0,1)
1665 tabs.addTab(tab1, _('Outputs') )
1667 tree_widget = MyTreeWidget(self)
1668 tree_widget.setColumnCount(2)
1669 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1670 tree_widget.setColumnWidth(0, 300)
1671 tree_widget.setColumnWidth(1, 50)
1673 for address, value in tx.outputs:
1674 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1675 tree_widget.addTopLevelItem(item)
1677 tree_widget.setMaximumHeight(100)
1679 grid_ui.addWidget(tree_widget)
1682 grid_ui = QGridLayout(tab2)
1683 grid_ui.setColumnStretch(0,1)
1684 tabs.addTab(tab2, _('Inputs') )
1686 tree_widget = MyTreeWidget(self)
1687 tree_widget.setColumnCount(2)
1688 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1690 for input_line in tx.inputs:
1691 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1692 tree_widget.addTopLevelItem(item)
1694 tree_widget.setMaximumHeight(100)
1696 grid_ui.addWidget(tree_widget)
1700 def tx_dict_from_text(self, txt):
1702 tx_dict = json.loads(str(txt))
1703 assert "hex" in tx_dict.keys()
1704 assert "complete" in tx_dict.keys()
1705 if not tx_dict["complete"]:
1706 assert "input_info" in tx_dict.keys()
1708 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1713 def read_tx_from_file(self):
1714 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1718 with open(fileName, "r") as f:
1719 file_content = f.read()
1720 except (ValueError, IOError, os.error), reason:
1721 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1723 return self.tx_dict_from_text(file_content)
1727 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1729 self.wallet.signrawtransaction(tx, input_info, [], password)
1731 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1733 with open(fileName, "w+") as f:
1734 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1735 self.show_message(_("Transaction saved successfully"))
1738 except BaseException, e:
1739 self.show_message(str(e))
1742 def send_raw_transaction(self, raw_tx, dialog = ""):
1743 result, result_message = self.wallet.sendtx( raw_tx )
1745 self.show_message("Transaction successfully sent: %s" % (result_message))
1749 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1751 def do_process_from_text(self):
1752 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1755 tx_dict = self.tx_dict_from_text(text)
1757 self.create_process_transaction_window(tx_dict)
1759 def do_process_from_file(self):
1760 tx_dict = self.read_tx_from_file()
1762 self.create_process_transaction_window(tx_dict)
1764 def create_process_transaction_window(self, tx_dict):
1765 tx = Transaction(tx_dict["hex"])
1767 dialog = QDialog(self)
1768 dialog.setMinimumWidth(500)
1769 dialog.setWindowTitle(_('Process raw transaction'))
1775 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1776 l.addWidget(QLabel(_("Actions")), 4,0)
1778 if tx_dict["complete"] == False:
1779 l.addWidget(QLabel(_("Unsigned")), 3,1)
1780 if self.wallet.seed :
1781 b = QPushButton("Sign transaction")
1782 input_info = json.loads(tx_dict["input_info"])
1783 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1784 l.addWidget(b, 4, 1)
1786 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1788 l.addWidget(QLabel(_("Signed")), 3,1)
1789 b = QPushButton("Broadcast transaction")
1790 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1793 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1794 cancelButton = QPushButton(_("Cancel"))
1795 cancelButton.clicked.connect(lambda: dialog.done(0))
1796 l.addWidget(cancelButton, 4,2)
1802 def do_export_privkeys(self, password):
1803 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.")))
1806 select_export = _('Select file to export your private keys to')
1807 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1809 with open(fileName, "w+") as csvfile:
1810 transaction = csv.writer(csvfile)
1811 transaction.writerow(["address", "private_key"])
1814 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1815 transaction.writerow(["%34s"%addr,pk])
1817 self.show_message(_("Private keys exported."))
1819 except (IOError, os.error), reason:
1820 export_error_label = _("Electrum was unable to produce a private key-export.")
1821 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1823 except BaseException, e:
1824 self.show_message(str(e))
1828 def do_import_labels(self):
1829 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1830 if not labelsFile: return
1832 f = open(labelsFile, 'r')
1835 for key, value in json.loads(data).items():
1836 self.wallet.labels[key] = value
1838 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1839 except (IOError, os.error), reason:
1840 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1843 def do_export_labels(self):
1844 labels = self.wallet.labels
1846 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1848 with open(fileName, 'w+') as f:
1849 json.dump(labels, f)
1850 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1851 except (IOError, os.error), reason:
1852 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1855 def do_export_history(self):
1856 from gui_lite import csv_transaction
1857 csv_transaction(self.wallet)
1861 def do_import_privkey(self, password):
1862 if not self.wallet.imported_keys:
1863 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1864 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1865 + _('Are you sure you understand what you are doing?'), 3, 4)
1868 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1871 text = str(text).split()
1876 addr = self.wallet.import_key(key, password)
1877 except BaseException as e:
1883 addrlist.append(addr)
1885 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1887 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1888 self.update_receive_tab()
1889 self.update_history_tab()
1892 def settings_dialog(self):
1894 d.setWindowTitle(_('Electrum Settings'))
1896 vbox = QVBoxLayout()
1898 tabs = QTabWidget(self)
1899 self.settings_tab = tabs
1900 vbox.addWidget(tabs)
1903 grid_ui = QGridLayout(tab1)
1904 grid_ui.setColumnStretch(0,1)
1905 tabs.addTab(tab1, _('Display') )
1907 nz_label = QLabel(_('Display zeros'))
1908 grid_ui.addWidget(nz_label, 0, 0)
1909 nz_e = AmountEdit(None,True)
1910 nz_e.setText("%d"% self.wallet.num_zeros)
1911 grid_ui.addWidget(nz_e, 0, 1)
1912 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1913 grid_ui.addWidget(HelpButton(msg), 0, 2)
1914 if not self.config.is_modifiable('num_zeros'):
1915 for w in [nz_e, nz_label]: w.setEnabled(False)
1917 lang_label=QLabel(_('Language') + ':')
1918 grid_ui.addWidget(lang_label, 1, 0)
1919 lang_combo = QComboBox()
1920 from i18n import languages
1921 lang_combo.addItems(languages.values())
1923 index = languages.keys().index(self.config.get("language",''))
1926 lang_combo.setCurrentIndex(index)
1927 grid_ui.addWidget(lang_combo, 1, 1)
1928 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1929 if not self.config.is_modifiable('language'):
1930 for w in [lang_combo, lang_label]: w.setEnabled(False)
1932 currencies = self.exchanger.get_currencies()
1933 currencies.insert(0, "None")
1935 cur_label=QLabel(_('Currency') + ':')
1936 grid_ui.addWidget(cur_label , 2, 0)
1937 cur_combo = QComboBox()
1938 cur_combo.addItems(currencies)
1940 index = currencies.index(self.config.get('currency', "None"))
1943 cur_combo.setCurrentIndex(index)
1944 grid_ui.addWidget(cur_combo, 2, 1)
1945 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1947 expert_cb = QCheckBox(_('Expert mode'))
1948 expert_cb.setChecked(self.expert_mode)
1949 grid_ui.addWidget(expert_cb, 3, 0)
1950 hh = _('In expert mode, your client will:') + '\n' \
1951 + _(' - Show change addresses in the Receive tab') + '\n' \
1952 + _(' - Display the balance of each address') + '\n' \
1953 + _(' - Add freeze/prioritize actions to addresses.')
1954 grid_ui.addWidget(HelpButton(hh), 3, 2)
1955 grid_ui.setRowStretch(4,1)
1959 grid_wallet = QGridLayout(tab2)
1960 grid_wallet.setColumnStretch(0,1)
1961 tabs.addTab(tab2, _('Wallet') )
1963 fee_label = QLabel(_('Transaction fee'))
1964 grid_wallet.addWidget(fee_label, 0, 0)
1965 fee_e = AmountEdit(self.base_unit)
1966 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1967 grid_wallet.addWidget(fee_e, 0, 2)
1968 msg = _('Fee per kilobyte of transaction.') + ' ' \
1969 + _('Recommended value') + ': ' + self.format_amount(20000)
1970 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1971 if not self.config.is_modifiable('fee_per_kb'):
1972 for w in [fee_e, fee_label]: w.setEnabled(False)
1974 usechange_cb = QCheckBox(_('Use change addresses'))
1975 usechange_cb.setChecked(self.wallet.use_change)
1976 grid_wallet.addWidget(usechange_cb, 1, 0)
1977 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1978 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1980 gap_label = QLabel(_('Gap limit'))
1981 grid_wallet.addWidget(gap_label, 2, 0)
1982 gap_e = AmountEdit(None,True)
1983 gap_e.setText("%d"% self.wallet.gap_limit)
1984 grid_wallet.addWidget(gap_e, 2, 2)
1985 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1986 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1987 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1988 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1989 + _('Warning') + ': ' \
1990 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1991 + _('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'
1992 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1993 if not self.config.is_modifiable('gap_limit'):
1994 for w in [gap_e, gap_label]: w.setEnabled(False)
1996 units = ['BTC', 'mBTC']
1997 unit_label = QLabel(_('Base unit'))
1998 grid_wallet.addWidget(unit_label, 3, 0)
1999 unit_combo = QComboBox()
2000 unit_combo.addItems(units)
2001 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2002 grid_wallet.addWidget(unit_combo, 3, 2)
2003 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2004 + '\n1BTC=1000mBTC.\n' \
2005 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2006 grid_wallet.setRowStretch(4,1)
2011 grid_io = QGridLayout(tab3)
2012 grid_io.setColumnStretch(0,1)
2013 tabs.addTab(tab3, _('Import/Export') )
2015 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2016 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2017 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2018 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2020 grid_io.addWidget(QLabel(_('History')), 2, 0)
2021 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2022 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2024 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2026 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2027 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2028 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2030 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2031 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2032 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2033 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2034 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2037 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2038 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2039 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2040 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2042 grid_io.setRowStretch(5,1)
2047 tab5 = QScrollArea()
2048 grid_plugins = QGridLayout(tab5)
2049 grid_plugins.setColumnStretch(0,1)
2050 tabs.addTab(tab5, _('Plugins') )
2051 def mk_toggle(cb, p):
2052 return lambda: cb.setChecked(p.toggle())
2053 for i, p in enumerate(self.plugins):
2055 name, description = p.get_info()
2056 cb = QCheckBox(name)
2057 cb.setDisabled(not p.is_available())
2058 cb.setChecked(p.is_enabled())
2059 cb.clicked.connect(mk_toggle(cb,p))
2060 grid_plugins.addWidget(cb, i, 0)
2061 if p.requires_settings():
2062 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2063 grid_plugins.addWidget(HelpButton(description), i, 2)
2065 print_msg("Error: cannot display plugin", p)
2066 traceback.print_exc(file=sys.stdout)
2067 grid_plugins.setRowStretch(i+1,1)
2069 self.run_hook('create_settings_tab', tabs)
2071 vbox.addLayout(ok_cancel_buttons(d))
2075 if not d.exec_(): return
2077 fee = unicode(fee_e.text())
2079 fee = self.read_amount(fee)
2081 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2084 if self.wallet.fee != fee:
2085 self.wallet.fee = fee
2088 nz = unicode(nz_e.text())
2093 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2096 if self.wallet.num_zeros != nz:
2097 self.wallet.num_zeros = nz
2098 self.config.set_key('num_zeros', nz, True)
2099 self.update_history_tab()
2100 self.update_receive_tab()
2102 usechange_result = usechange_cb.isChecked()
2103 if self.wallet.use_change != usechange_result:
2104 self.wallet.use_change = usechange_result
2105 self.config.set_key('use_change', self.wallet.use_change, True)
2107 unit_result = units[unit_combo.currentIndex()]
2108 if self.base_unit() != unit_result:
2109 self.decimal_point = 8 if unit_result == 'BTC' else 5
2110 self.config.set_key('decimal_point', self.decimal_point, True)
2111 self.update_history_tab()
2112 self.update_status()
2115 n = int(gap_e.text())
2117 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2120 if self.wallet.gap_limit != n:
2121 r = self.wallet.change_gap_limit(n)
2123 self.update_receive_tab()
2124 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2126 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2128 need_restart = False
2130 lang_request = languages.keys()[lang_combo.currentIndex()]
2131 if lang_request != self.config.get('language'):
2132 self.config.set_key("language", lang_request, True)
2135 cur_request = str(currencies[cur_combo.currentIndex()])
2136 if cur_request != self.config.get('currency', "None"):
2137 self.config.set_key('currency', cur_request, True)
2138 self.update_wallet()
2140 self.run_hook('close_settings_dialog')
2143 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2145 self.receive_tab_set_mode(expert_cb.isChecked())
2149 def network_dialog(wallet, parent=None):
2150 interface = wallet.interface
2152 if interface.is_connected:
2153 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2155 status = _("Not connected")
2156 server = interface.server
2159 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2160 server = interface.server
2162 plist, servers_list = interface.get_servers_list()
2166 d.setWindowTitle(_('Server'))
2167 d.setMinimumSize(375, 20)
2169 vbox = QVBoxLayout()
2172 hbox = QHBoxLayout()
2174 l.setPixmap(QPixmap(":icons/network.png"))
2177 hbox.addWidget(QLabel(status))
2179 vbox.addLayout(hbox)
2183 grid = QGridLayout()
2185 vbox.addLayout(grid)
2188 server_protocol = QComboBox()
2189 server_host = QLineEdit()
2190 server_host.setFixedWidth(200)
2191 server_port = QLineEdit()
2192 server_port.setFixedWidth(60)
2194 protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2195 protocol_letters = 'thsg'
2196 server_protocol.addItems(protocol_names)
2198 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2199 grid.addWidget(server_protocol, 0, 1)
2200 grid.addWidget(server_host, 0, 2)
2201 grid.addWidget(server_port, 0, 3)
2203 def change_protocol(p):
2204 protocol = protocol_letters[p]
2205 host = unicode(server_host.text())
2206 pp = plist.get(host,DEFAULT_PORTS)
2207 if protocol not in pp.keys():
2208 protocol = pp.keys()[0]
2210 server_host.setText( host )
2211 server_port.setText( port )
2213 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2215 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2216 servers_list_widget = QTreeWidget(parent)
2217 servers_list_widget.setHeaderLabels( [ label, _('Limit') ] )
2218 servers_list_widget.setMaximumHeight(150)
2219 servers_list_widget.setColumnWidth(0, 240)
2220 for _host in servers_list.keys():
2221 pruning_level = servers_list[_host].get('pruning','')
2222 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2223 servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2225 def change_server(host, protocol=None):
2226 pp = plist.get(host,DEFAULT_PORTS)
2228 port = pp.get(protocol)
2229 if not port: protocol = None
2232 if 's' in pp.keys():
2234 port = pp.get(protocol)
2236 protocol = pp.keys()[0]
2237 port = pp.get(protocol)
2239 server_host.setText( host )
2240 server_port.setText( port )
2241 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2243 if not plist: return
2244 for p in protocol_letters:
2245 i = protocol_letters.index(p)
2246 j = server_protocol.model().index(i,0)
2247 if p not in pp.keys() and interface.is_connected:
2248 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2250 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2254 host, port, protocol = server.split(':')
2255 change_server(host,protocol)
2257 servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'),
2258 lambda x,y: change_server(unicode(x.text(0))))
2259 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2261 if not wallet.config.is_modifiable('server'):
2262 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2265 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2266 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2267 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2268 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2271 proxy_mode = QComboBox()
2272 proxy_host = QLineEdit()
2273 proxy_host.setFixedWidth(200)
2274 proxy_port = QLineEdit()
2275 proxy_port.setFixedWidth(60)
2276 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2278 def check_for_disable(index = False):
2279 if proxy_mode.currentText() != 'NONE':
2280 proxy_host.setEnabled(True)
2281 proxy_port.setEnabled(True)
2283 proxy_host.setEnabled(False)
2284 proxy_port.setEnabled(False)
2287 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2289 if not wallet.config.is_modifiable('proxy'):
2290 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2292 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2293 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2294 proxy_host.setText(proxy_config.get("host"))
2295 proxy_port.setText(proxy_config.get("port"))
2297 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2298 grid.addWidget(proxy_mode, 2, 1)
2299 grid.addWidget(proxy_host, 2, 2)
2300 grid.addWidget(proxy_port, 2, 3)
2303 vbox.addLayout(ok_cancel_buttons(d))
2306 if not d.exec_(): return
2308 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2309 if proxy_mode.currentText() != 'NONE':
2310 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2314 wallet.config.set_key("proxy", proxy, True)
2315 wallet.config.set_key("server", server, True)
2316 interface.set_server(server, proxy)
2317 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2320 def closeEvent(self, event):
2322 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2323 self.save_column_widths()
2324 self.config.set_key("column_widths", self.column_widths, True)
2325 self.config.set_key("console-history",self.console.history[-50:])
2331 def __init__(self, wallet, config, app=None):
2332 self.wallet = wallet
2333 self.config = config
2335 self.app = QApplication(sys.argv)
2338 def restore_or_create(self):
2339 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2340 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2341 if r==2: return None
2342 return 'restore' if r==1 else 'create'
2344 def seed_dialog(self):
2345 return ElectrumWindow.seed_dialog( self.wallet )
2347 def network_dialog(self):
2348 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2351 def show_seed(self):
2352 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2355 def password_dialog(self):
2356 if self.wallet.seed:
2357 ElectrumWindow.change_password_dialog(self.wallet)
2360 def restore_wallet(self):
2361 wallet = self.wallet
2362 # wait until we are connected, because the user might have selected another server
2363 if not wallet.interface.is_connected:
2364 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2365 waiting_dialog(waiting)
2367 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2368 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2370 wallet.set_up_to_date(False)
2371 wallet.interface.poke('synchronizer')
2372 waiting_dialog(waiting)
2373 if wallet.is_found():
2374 print_error( "Recovery successful" )
2376 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2383 w = ElectrumWindow(self.wallet, self.config)
2384 if url: w.set_url(url)