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 == '': 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.text() == '.':
721 c, u = self.wallet.get_account_balance(self.current_account)
722 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
723 fee = self.wallet.estimated_fee(inputs)
725 self.amount_e.setText( self.format_amount(amount) )
726 self.fee_e.setText( self.format_amount( fee ) )
729 amount = self.read_amount(str(self.amount_e.text()))
730 fee = self.read_amount(str(self.fee_e.text()))
732 if not is_fee: fee = None
735 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
737 self.fee_e.setText( self.format_amount( fee ) )
740 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
741 text = self.status_text
744 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
745 self.funds_error = True
746 text = _( "Not enough funds" )
748 self.statusBar().showMessage(text)
749 self.amount_e.setPalette(palette)
750 self.fee_e.setPalette(palette)
752 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
753 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
755 self.run_hook('create_send_tab', grid)
759 def update_completions(self):
761 for addr,label in self.wallet.labels.items():
762 if addr in self.wallet.addressbook:
763 l.append( label + ' <' + addr + '>')
765 self.run_hook('update_completions', l)
766 self.completions.setStringList(l)
770 return lambda s, *args: s.do_protect(func, args)
774 def do_send(self, password):
776 label = unicode( self.message_e.text() )
777 r = unicode( self.payto_e.text() )
780 # label or alias, with address in brackets
781 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
782 to_address = m.group(2) if m else r
784 if not is_valid(to_address):
785 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
789 amount = self.read_amount(unicode( self.amount_e.text()))
791 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
794 fee = self.read_amount(unicode( self.fee_e.text()))
796 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
800 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
801 except BaseException, e:
802 self.show_message(str(e))
805 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
806 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
809 self.run_hook('send_tx', tx)
812 self.set_label(tx.hash(), label)
815 h = self.wallet.send_tx(tx)
816 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
817 status, msg = self.wallet.receive_tx( h )
819 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
821 self.update_contacts_tab()
823 QMessageBox.warning(self, _('Error'), msg, _('OK'))
825 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
827 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
828 with open(fileName,'w') as f:
829 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
830 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
832 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
837 def set_url(self, url):
838 address, amount, label, message, signature, identity, url = util.parse_url(url)
839 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
841 if label and self.wallet.labels.get(address) != label:
842 if self.question('Give label "%s" to address %s ?'%(label,address)):
843 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
844 self.wallet.addressbook.append(address)
845 self.set_label(address, label)
847 self.run_hook('set_url', url, self.show_message, self.question)
849 self.tabs.setCurrentIndex(1)
850 label = self.wallet.labels.get(address)
851 m_addr = label + ' <'+ address +'>' if label else address
852 self.payto_e.setText(m_addr)
854 self.message_e.setText(message)
855 self.amount_e.setText(amount)
857 self.set_frozen(self.payto_e,True)
858 self.set_frozen(self.amount_e,True)
859 self.set_frozen(self.message_e,True)
860 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
862 self.payto_sig.setVisible(False)
865 self.payto_sig.setVisible(False)
866 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
868 self.set_frozen(e,False)
870 def set_frozen(self,entry,frozen):
872 entry.setReadOnly(True)
873 entry.setFrame(False)
875 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
876 entry.setPalette(palette)
878 entry.setReadOnly(False)
881 palette.setColor(entry.backgroundRole(), QColor('white'))
882 entry.setPalette(palette)
885 def toggle_freeze(self,addr):
887 if addr in self.wallet.frozen_addresses:
888 self.wallet.unfreeze(addr)
890 self.wallet.freeze(addr)
891 self.update_receive_tab()
893 def toggle_priority(self,addr):
895 if addr in self.wallet.prioritized_addresses:
896 self.wallet.unprioritize(addr)
898 self.wallet.prioritize(addr)
899 self.update_receive_tab()
902 def create_list_tab(self, headers):
903 "generic tab creation method"
904 l = MyTreeWidget(self)
905 l.setColumnCount( len(headers) )
906 l.setHeaderLabels( headers )
916 vbox.addWidget(buttons)
921 buttons.setLayout(hbox)
926 def create_receive_tab(self):
927 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
928 l.setContextMenuPolicy(Qt.CustomContextMenu)
929 l.customContextMenuRequested.connect(self.create_receive_menu)
930 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
931 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
932 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
933 self.receive_list = l
934 self.receive_buttons_hbox = hbox
939 def receive_tab_set_mode(self, i):
940 self.save_column_widths()
941 self.expert_mode = (i == 1)
942 self.config.set_key('classic_expert_mode', self.expert_mode, True)
944 self.update_receive_tab()
947 def save_column_widths(self):
948 if not self.expert_mode:
949 widths = [ self.receive_list.columnWidth(0) ]
952 for i in range(self.receive_list.columnCount() -1):
953 widths.append(self.receive_list.columnWidth(i))
954 self.column_widths["receive"][self.expert_mode] = widths
956 self.column_widths["history"] = []
957 for i in range(self.history_list.columnCount() - 1):
958 self.column_widths["history"].append(self.history_list.columnWidth(i))
960 self.column_widths["contacts"] = []
961 for i in range(self.contacts_list.columnCount() - 1):
962 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
965 def create_contacts_tab(self):
966 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
967 l.setContextMenuPolicy(Qt.CustomContextMenu)
968 l.customContextMenuRequested.connect(self.create_contact_menu)
969 for i,width in enumerate(self.column_widths['contacts']):
970 l.setColumnWidth(i, width)
972 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
973 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
974 self.contacts_list = l
975 self.contacts_buttons_hbox = hbox
976 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
981 def delete_imported_key(self, addr):
982 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
983 self.wallet.imported_keys.pop(addr)
984 self.update_receive_tab()
985 self.update_history_tab()
989 def create_receive_menu(self, position):
990 # fixme: this function apparently has a side effect.
991 # if it is not called the menu pops up several times
992 #self.receive_list.selectedIndexes()
994 item = self.receive_list.itemAt(position)
996 addr = unicode(item.text(0))
997 if not is_valid(addr):
998 item.setExpanded(not item.isExpanded())
1001 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1002 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1003 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1004 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1005 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1006 if addr in self.wallet.imported_keys:
1007 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1009 if self.expert_mode:
1010 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1011 menu.addAction(t, lambda: self.toggle_freeze(addr))
1012 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1013 menu.addAction(t, lambda: self.toggle_priority(addr))
1015 self.run_hook('receive_menu', menu)
1016 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1019 def payto(self, addr):
1021 label = self.wallet.labels.get(addr)
1022 m_addr = label + ' <' + addr + '>' if label else addr
1023 self.tabs.setCurrentIndex(1)
1024 self.payto_e.setText(m_addr)
1025 self.amount_e.setFocus()
1028 def delete_contact(self, x):
1029 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1030 if x in self.wallet.addressbook:
1031 self.wallet.addressbook.remove(x)
1032 self.set_label(x, None)
1033 self.update_history_tab()
1034 self.update_contacts_tab()
1035 self.update_completions()
1038 def create_contact_menu(self, position):
1039 item = self.contacts_list.itemAt(position)
1041 addr = unicode(item.text(0))
1042 label = unicode(item.text(1))
1043 is_editable = item.data(0,32).toBool()
1044 payto_addr = item.data(0,33).toString()
1046 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1047 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1048 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1050 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1051 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1053 self.run_hook('create_contact_menu', menu, item)
1054 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1057 def update_receive_item(self, item):
1058 item.setFont(0, QFont(MONOSPACE_FONT))
1059 address = str(item.data(0,0).toString())
1060 label = self.wallet.labels.get(address,'')
1061 item.setData(1,0,label)
1062 item.setData(0,32, True) # is editable
1064 self.run_hook('update_receive_item', address, item)
1066 c, u = self.wallet.get_addr_balance(address)
1067 balance = self.format_amount(c + u)
1068 item.setData(2,0,balance)
1070 if self.expert_mode:
1071 if address in self.wallet.frozen_addresses:
1072 item.setBackgroundColor(0, QColor('lightblue'))
1073 elif address in self.wallet.prioritized_addresses:
1074 item.setBackgroundColor(0, QColor('lightgreen'))
1077 def update_receive_tab(self):
1078 l = self.receive_list
1081 l.setColumnHidden(2, not self.expert_mode)
1082 l.setColumnHidden(3, not self.expert_mode)
1083 if not self.expert_mode:
1084 width = self.column_widths['receive'][0][0]
1085 l.setColumnWidth(0, width)
1087 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1088 l.setColumnWidth(i, width)
1090 if self.current_account is None:
1091 account_items = self.wallet.accounts.items()
1092 elif self.current_account != -1:
1093 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1097 for k, account in account_items:
1098 name = account.get('name',str(k))
1099 c,u = self.wallet.get_account_balance(k)
1100 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1101 l.addTopLevelItem(account_item)
1102 account_item.setExpanded(True)
1104 for is_change in ([0,1] if self.expert_mode else [0]):
1105 if self.expert_mode:
1106 name = "Receiving" if not is_change else "Change"
1107 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1108 account_item.addChild(seq_item)
1109 if not is_change: seq_item.setExpanded(True)
1111 seq_item = account_item
1115 for address in account[is_change]:
1116 h = self.wallet.history.get(address,[])
1120 if gap > self.wallet.gap_limit:
1125 num_tx = '*' if h == ['*'] else "%d"%len(h)
1126 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1127 self.update_receive_item(item)
1129 item.setBackgroundColor(1, QColor('red'))
1130 seq_item.addChild(item)
1133 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1134 c,u = self.wallet.get_imported_balance()
1135 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1136 l.addTopLevelItem(account_item)
1137 account_item.setExpanded(True)
1138 for address in self.wallet.imported_keys.keys():
1139 item = QTreeWidgetItem( [ address, '', '', ''] )
1140 self.update_receive_item(item)
1141 account_item.addChild(item)
1144 # we use column 1 because column 0 may be hidden
1145 l.setCurrentItem(l.topLevelItem(0),1)
1148 def update_contacts_tab(self):
1150 l = self.contacts_list
1153 for address in self.wallet.addressbook:
1154 label = self.wallet.labels.get(address,'')
1155 n = self.wallet.get_num_tx(address)
1156 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1157 item.setFont(0, QFont(MONOSPACE_FONT))
1158 # 32 = label can be edited (bool)
1159 item.setData(0,32, True)
1161 item.setData(0,33, address)
1162 l.addTopLevelItem(item)
1164 self.run_hook('update_contacts_tab', l)
1165 l.setCurrentItem(l.topLevelItem(0))
1169 def create_console_tab(self):
1170 from qt_console import Console
1171 self.console = console = Console()
1172 self.console.history = self.config.get("console-history",[])
1173 self.console.history_index = len(self.console.history)
1175 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1176 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1178 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1180 def mkfunc(f, method):
1181 return lambda *args: apply( f, (method, args, self.password_dialog ))
1183 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1184 methods[m] = mkfunc(c._run, m)
1186 console.updateNamespace(methods)
1189 def change_account(self,s):
1190 if s == _("All accounts"):
1191 self.current_account = None
1193 accounts = self.wallet.get_accounts()
1194 for k, v in accounts.items():
1196 self.current_account = k
1197 self.update_history_tab()
1198 self.update_status()
1199 self.update_receive_tab()
1201 def create_status_bar(self):
1202 self.status_text = ""
1204 sb.setFixedHeight(35)
1205 qtVersion = qVersion()
1207 update_notification = UpdateLabel(self.config)
1208 if(update_notification.new_version):
1209 sb.addPermanentWidget(update_notification)
1211 accounts = self.wallet.get_accounts()
1212 if len(accounts) > 1:
1213 from_combo = QComboBox()
1214 from_combo.addItems([_("All accounts")] + accounts.values())
1215 from_combo.setCurrentIndex(0)
1216 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1217 sb.addPermanentWidget(from_combo)
1219 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1220 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1221 if self.wallet.seed:
1222 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1223 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1224 sb.addPermanentWidget( self.password_button )
1225 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1226 if self.wallet.seed:
1227 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1228 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1229 sb.addPermanentWidget( self.status_button )
1231 self.run_hook('create_status_bar', (sb,))
1233 self.setStatusBar(sb)
1237 self.config.set_key('gui', 'lite', True)
1240 self.lite.mini.show()
1242 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1243 self.lite.main(None)
1245 def new_contact_dialog(self):
1246 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1247 address = unicode(text)
1249 if is_valid(address):
1250 self.wallet.addressbook.append(address)
1252 self.update_contacts_tab()
1253 self.update_history_tab()
1254 self.update_completions()
1256 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1258 def show_master_public_key(self):
1259 dialog = QDialog(self)
1261 dialog.setWindowTitle(_("Master Public Key"))
1263 main_text = QTextEdit()
1264 main_text.setText(self.wallet.get_master_public_key())
1265 main_text.setReadOnly(True)
1266 main_text.setMaximumHeight(170)
1267 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1269 ok_button = QPushButton(_("OK"))
1270 ok_button.setDefault(True)
1271 ok_button.clicked.connect(dialog.accept)
1273 main_layout = QGridLayout()
1274 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1276 main_layout.addWidget(main_text, 1, 0)
1277 main_layout.addWidget(qrw, 1, 1 )
1279 vbox = QVBoxLayout()
1280 vbox.addLayout(main_layout)
1281 hbox = QHBoxLayout()
1283 hbox.addWidget(ok_button)
1284 vbox.addLayout(hbox)
1286 dialog.setLayout(vbox)
1291 def show_seed_dialog(self, password):
1292 if not self.wallet.seed:
1293 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1296 seed = self.wallet.decode_seed(password)
1298 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1300 self.show_seed(seed, self.wallet.imported_keys, self)
1304 def show_seed(self, seed, imported_keys, parent=None):
1305 dialog = QDialog(parent)
1307 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1309 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1311 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1313 seed_text = QTextEdit(brainwallet)
1314 seed_text.setReadOnly(True)
1315 seed_text.setMaximumHeight(130)
1317 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1318 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1319 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1320 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1322 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1323 label2 = QLabel(msg2)
1324 label2.setWordWrap(True)
1327 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1328 logo.setMaximumWidth(60)
1330 qrw = QRCodeWidget(seed)
1332 ok_button = QPushButton(_("OK"))
1333 ok_button.setDefault(True)
1334 ok_button.clicked.connect(dialog.accept)
1336 grid = QGridLayout()
1337 #main_layout.addWidget(logo, 0, 0)
1339 grid.addWidget(logo, 0, 0)
1340 grid.addWidget(label1, 0, 1)
1342 grid.addWidget(seed_text, 1, 0, 1, 2)
1344 grid.addWidget(qrw, 0, 2, 2, 1)
1346 vbox = QVBoxLayout()
1347 vbox.addLayout(grid)
1348 vbox.addWidget(label2)
1350 hbox = QHBoxLayout()
1352 hbox.addWidget(ok_button)
1353 vbox.addLayout(hbox)
1355 dialog.setLayout(vbox)
1358 def show_qrcode(self, data, title = "QR code"):
1362 d.setWindowTitle(title)
1363 d.setMinimumSize(270, 300)
1364 vbox = QVBoxLayout()
1365 qrw = QRCodeWidget(data)
1366 vbox.addWidget(qrw, 1)
1367 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1368 hbox = QHBoxLayout()
1372 filename = "qrcode.bmp"
1373 bmp.save_qrcode(qrw.qr, filename)
1374 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1376 b = QPushButton(_("Save"))
1378 b.clicked.connect(print_qr)
1380 b = QPushButton(_("Close"))
1382 b.clicked.connect(d.accept)
1385 vbox.addLayout(hbox)
1390 def do_protect(self, func, args):
1391 if self.wallet.use_encryption:
1392 password = self.password_dialog()
1398 if args != (False,):
1399 args = (self,) + args + (password,)
1401 args = (self,password)
1406 def show_private_key(self, address, password):
1407 if not address: return
1409 pk = self.wallet.get_private_key(address, password)
1410 except BaseException, e:
1411 self.show_message(str(e))
1413 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1417 def do_sign(self, address, message, signature, password):
1419 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1420 signature.setText(sig)
1421 except BaseException, e:
1422 self.show_message(str(e))
1424 def sign_message(self, address):
1425 if not address: return
1428 d.setWindowTitle(_('Sign Message'))
1429 d.setMinimumSize(410, 290)
1431 tab_widget = QTabWidget()
1433 layout = QGridLayout(tab)
1435 sign_address = QLineEdit()
1437 sign_address.setText(address)
1438 layout.addWidget(QLabel(_('Address')), 1, 0)
1439 layout.addWidget(sign_address, 1, 1)
1441 sign_message = QTextEdit()
1442 layout.addWidget(QLabel(_('Message')), 2, 0)
1443 layout.addWidget(sign_message, 2, 1)
1444 layout.setRowStretch(2,3)
1446 sign_signature = QTextEdit()
1447 layout.addWidget(QLabel(_('Signature')), 3, 0)
1448 layout.addWidget(sign_signature, 3, 1)
1449 layout.setRowStretch(3,1)
1452 hbox = QHBoxLayout()
1453 b = QPushButton(_("Sign"))
1455 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1456 b = QPushButton(_("Close"))
1457 b.clicked.connect(d.accept)
1459 layout.addLayout(hbox, 4, 1)
1460 tab_widget.addTab(tab, _("Sign"))
1464 layout = QGridLayout(tab)
1466 verify_address = QLineEdit()
1467 layout.addWidget(QLabel(_('Address')), 1, 0)
1468 layout.addWidget(verify_address, 1, 1)
1470 verify_message = QTextEdit()
1471 layout.addWidget(QLabel(_('Message')), 2, 0)
1472 layout.addWidget(verify_message, 2, 1)
1473 layout.setRowStretch(2,3)
1475 verify_signature = QTextEdit()
1476 layout.addWidget(QLabel(_('Signature')), 3, 0)
1477 layout.addWidget(verify_signature, 3, 1)
1478 layout.setRowStretch(3,1)
1482 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1483 self.show_message(_("Signature verified"))
1484 except BaseException, e:
1485 self.show_message(str(e))
1488 hbox = QHBoxLayout()
1489 b = QPushButton(_("Verify"))
1490 b.clicked.connect(do_verify)
1492 b = QPushButton(_("Close"))
1493 b.clicked.connect(d.accept)
1495 layout.addLayout(hbox, 4, 1)
1496 tab_widget.addTab(tab, _("Verify"))
1498 vbox = QVBoxLayout()
1499 vbox.addWidget(tab_widget)
1506 def question(self, msg):
1507 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1509 def show_message(self, msg):
1510 QMessageBox.information(self, _('Message'), msg, _('OK'))
1512 def password_dialog(self ):
1519 vbox = QVBoxLayout()
1520 msg = _('Please enter your password')
1521 vbox.addWidget(QLabel(msg))
1523 grid = QGridLayout()
1525 grid.addWidget(QLabel(_('Password')), 1, 0)
1526 grid.addWidget(pw, 1, 1)
1527 vbox.addLayout(grid)
1529 vbox.addLayout(ok_cancel_buttons(d))
1532 self.run_hook('password_dialog', pw, grid, 1)
1533 if not d.exec_(): return
1534 return unicode(pw.text())
1541 def change_password_dialog( wallet, parent=None ):
1544 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1552 new_pw = QLineEdit()
1553 new_pw.setEchoMode(2)
1554 conf_pw = QLineEdit()
1555 conf_pw.setEchoMode(2)
1557 vbox = QVBoxLayout()
1559 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1560 +_('To disable wallet encryption, enter an empty new password.')) \
1561 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1563 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1564 +_("Leave these fields empty if you want to disable encryption.")
1565 vbox.addWidget(QLabel(msg))
1567 grid = QGridLayout()
1570 if wallet.use_encryption:
1571 grid.addWidget(QLabel(_('Password')), 1, 0)
1572 grid.addWidget(pw, 1, 1)
1574 grid.addWidget(QLabel(_('New Password')), 2, 0)
1575 grid.addWidget(new_pw, 2, 1)
1577 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1578 grid.addWidget(conf_pw, 3, 1)
1579 vbox.addLayout(grid)
1581 vbox.addLayout(ok_cancel_buttons(d))
1584 if not d.exec_(): return
1586 password = unicode(pw.text()) if wallet.use_encryption else None
1587 new_password = unicode(new_pw.text())
1588 new_password2 = unicode(conf_pw.text())
1591 seed = wallet.decode_seed(password)
1593 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1596 if new_password != new_password2:
1597 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1598 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1600 wallet.update_password(seed, password, new_password)
1602 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1603 parent.password_button.setIcon( icon )
1607 def seed_dialog(wallet, parent=None):
1611 vbox = QVBoxLayout()
1612 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1613 vbox.addWidget(QLabel(msg))
1615 grid = QGridLayout()
1618 seed_e = QLineEdit()
1619 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1620 grid.addWidget(seed_e, 1, 1)
1621 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1623 gap_e = AmountEdit(None, True)
1625 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1626 grid.addWidget(gap_e, 2, 1)
1627 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1628 vbox.addLayout(grid)
1630 vbox.addLayout(ok_cancel_buttons(d))
1633 if not d.exec_(): return
1636 gap = int(unicode(gap_e.text()))
1638 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1642 seed = str(seed_e.text())
1645 print_error("Warning: Not hex, trying decode")
1647 seed = mnemonic.mn_decode( seed.split(' ') )
1649 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1653 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1658 def generate_transaction_information_widget(self, tx):
1659 tabs = QTabWidget(self)
1662 grid_ui = QGridLayout(tab1)
1663 grid_ui.setColumnStretch(0,1)
1664 tabs.addTab(tab1, _('Outputs') )
1666 tree_widget = MyTreeWidget(self)
1667 tree_widget.setColumnCount(2)
1668 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1669 tree_widget.setColumnWidth(0, 300)
1670 tree_widget.setColumnWidth(1, 50)
1672 for address, value in tx.outputs:
1673 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1674 tree_widget.addTopLevelItem(item)
1676 tree_widget.setMaximumHeight(100)
1678 grid_ui.addWidget(tree_widget)
1681 grid_ui = QGridLayout(tab2)
1682 grid_ui.setColumnStretch(0,1)
1683 tabs.addTab(tab2, _('Inputs') )
1685 tree_widget = MyTreeWidget(self)
1686 tree_widget.setColumnCount(2)
1687 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1689 for input_line in tx.inputs:
1690 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1691 tree_widget.addTopLevelItem(item)
1693 tree_widget.setMaximumHeight(100)
1695 grid_ui.addWidget(tree_widget)
1699 def tx_dict_from_text(self, txt):
1701 tx_dict = json.loads(str(txt))
1702 assert "hex" in tx_dict.keys()
1703 assert "complete" in tx_dict.keys()
1704 if not tx_dict["complete"]:
1705 assert "input_info" in tx_dict.keys()
1707 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1712 def read_tx_from_file(self):
1713 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1717 with open(fileName, "r") as f:
1718 file_content = f.read()
1719 except (ValueError, IOError, os.error), reason:
1720 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1722 return self.tx_dict_from_text(file_content)
1726 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1728 self.wallet.signrawtransaction(tx, input_info, [], password)
1730 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1732 with open(fileName, "w+") as f:
1733 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1734 self.show_message(_("Transaction saved successfully"))
1737 except BaseException, e:
1738 self.show_message(str(e))
1741 def send_raw_transaction(self, raw_tx, dialog = ""):
1742 result, result_message = self.wallet.sendtx( raw_tx )
1744 self.show_message("Transaction successfully sent: %s" % (result_message))
1748 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1750 def do_process_from_text(self):
1751 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1754 tx_dict = self.tx_dict_from_text(text)
1756 self.create_process_transaction_window(tx_dict)
1758 def do_process_from_file(self):
1759 tx_dict = self.read_tx_from_file()
1761 self.create_process_transaction_window(tx_dict)
1763 def create_process_transaction_window(self, tx_dict):
1764 tx = Transaction(tx_dict["hex"])
1766 dialog = QDialog(self)
1767 dialog.setMinimumWidth(500)
1768 dialog.setWindowTitle(_('Process raw transaction'))
1774 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1775 l.addWidget(QLabel(_("Actions")), 4,0)
1777 if tx_dict["complete"] == False:
1778 l.addWidget(QLabel(_("Unsigned")), 3,1)
1779 if self.wallet.seed :
1780 b = QPushButton("Sign transaction")
1781 input_info = json.loads(tx_dict["input_info"])
1782 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1783 l.addWidget(b, 4, 1)
1785 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1787 l.addWidget(QLabel(_("Signed")), 3,1)
1788 b = QPushButton("Broadcast transaction")
1789 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1792 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1793 cancelButton = QPushButton(_("Cancel"))
1794 cancelButton.clicked.connect(lambda: dialog.done(0))
1795 l.addWidget(cancelButton, 4,2)
1801 def do_export_privkeys(self, password):
1802 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.")))
1805 select_export = _('Select file to export your private keys to')
1806 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1808 with open(fileName, "w+") as csvfile:
1809 transaction = csv.writer(csvfile)
1810 transaction.writerow(["address", "private_key"])
1813 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1814 transaction.writerow(["%34s"%addr,pk])
1816 self.show_message(_("Private keys exported."))
1818 except (IOError, os.error), reason:
1819 export_error_label = _("Electrum was unable to produce a private key-export.")
1820 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1822 except BaseException, e:
1823 self.show_message(str(e))
1827 def do_import_labels(self):
1828 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1829 if not labelsFile: return
1831 f = open(labelsFile, 'r')
1834 for key, value in json.loads(data).items():
1835 self.wallet.labels[key] = value
1837 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1838 except (IOError, os.error), reason:
1839 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1842 def do_export_labels(self):
1843 labels = self.wallet.labels
1845 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1847 with open(fileName, 'w+') as f:
1848 json.dump(labels, f)
1849 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1850 except (IOError, os.error), reason:
1851 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1854 def do_export_history(self):
1855 from gui_lite import csv_transaction
1856 csv_transaction(self.wallet)
1860 def do_import_privkey(self, password):
1861 if not self.wallet.imported_keys:
1862 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1863 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1864 + _('Are you sure you understand what you are doing?'), 3, 4)
1867 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1870 text = str(text).split()
1875 addr = self.wallet.import_key(key, password)
1876 except BaseException as e:
1882 addrlist.append(addr)
1884 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1886 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1887 self.update_receive_tab()
1888 self.update_history_tab()
1891 def settings_dialog(self):
1893 d.setWindowTitle(_('Electrum Settings'))
1895 vbox = QVBoxLayout()
1897 tabs = QTabWidget(self)
1898 self.settings_tab = tabs
1899 vbox.addWidget(tabs)
1902 grid_ui = QGridLayout(tab1)
1903 grid_ui.setColumnStretch(0,1)
1904 tabs.addTab(tab1, _('Display') )
1906 nz_label = QLabel(_('Display zeros'))
1907 grid_ui.addWidget(nz_label, 0, 0)
1908 nz_e = AmountEdit(None,True)
1909 nz_e.setText("%d"% self.wallet.num_zeros)
1910 grid_ui.addWidget(nz_e, 0, 1)
1911 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1912 grid_ui.addWidget(HelpButton(msg), 0, 2)
1913 if not self.config.is_modifiable('num_zeros'):
1914 for w in [nz_e, nz_label]: w.setEnabled(False)
1916 lang_label=QLabel(_('Language') + ':')
1917 grid_ui.addWidget(lang_label, 1, 0)
1918 lang_combo = QComboBox()
1919 from i18n import languages
1920 lang_combo.addItems(languages.values())
1922 index = languages.keys().index(self.config.get("language",''))
1925 lang_combo.setCurrentIndex(index)
1926 grid_ui.addWidget(lang_combo, 1, 1)
1927 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1928 if not self.config.is_modifiable('language'):
1929 for w in [lang_combo, lang_label]: w.setEnabled(False)
1931 currencies = self.exchanger.get_currencies()
1932 currencies.insert(0, "None")
1934 cur_label=QLabel(_('Currency') + ':')
1935 grid_ui.addWidget(cur_label , 2, 0)
1936 cur_combo = QComboBox()
1937 cur_combo.addItems(currencies)
1939 index = currencies.index(self.config.get('currency', "None"))
1942 cur_combo.setCurrentIndex(index)
1943 grid_ui.addWidget(cur_combo, 2, 1)
1944 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1946 expert_cb = QCheckBox(_('Expert mode'))
1947 expert_cb.setChecked(self.expert_mode)
1948 grid_ui.addWidget(expert_cb, 3, 0)
1949 hh = _('In expert mode, your client will:') + '\n' \
1950 + _(' - Show change addresses in the Receive tab') + '\n' \
1951 + _(' - Display the balance of each address') + '\n' \
1952 + _(' - Add freeze/prioritize actions to addresses.')
1953 grid_ui.addWidget(HelpButton(hh), 3, 2)
1954 grid_ui.setRowStretch(4,1)
1958 grid_wallet = QGridLayout(tab2)
1959 grid_wallet.setColumnStretch(0,1)
1960 tabs.addTab(tab2, _('Wallet') )
1962 fee_label = QLabel(_('Transaction fee'))
1963 grid_wallet.addWidget(fee_label, 0, 0)
1964 fee_e = AmountEdit(self.base_unit)
1965 fee_e.setText(self.format_amount(self.wallet.fee).strip())
1966 grid_wallet.addWidget(fee_e, 0, 2)
1967 msg = _('Fee per kilobyte of transaction.') + ' ' \
1968 + _('Recommended value') + ': ' + self.format_amount(20000)
1969 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1970 if not self.config.is_modifiable('fee_per_kb'):
1971 for w in [fee_e, fee_label]: w.setEnabled(False)
1973 usechange_cb = QCheckBox(_('Use change addresses'))
1974 usechange_cb.setChecked(self.wallet.use_change)
1975 grid_wallet.addWidget(usechange_cb, 1, 0)
1976 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1977 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1979 gap_label = QLabel(_('Gap limit'))
1980 grid_wallet.addWidget(gap_label, 2, 0)
1981 gap_e = AmountEdit(None,True)
1982 gap_e.setText("%d"% self.wallet.gap_limit)
1983 grid_wallet.addWidget(gap_e, 2, 2)
1984 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1985 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1986 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1987 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1988 + _('Warning') + ': ' \
1989 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1990 + _('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'
1991 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1992 if not self.config.is_modifiable('gap_limit'):
1993 for w in [gap_e, gap_label]: w.setEnabled(False)
1995 units = ['BTC', 'mBTC']
1996 unit_label = QLabel(_('Base unit'))
1997 grid_wallet.addWidget(unit_label, 3, 0)
1998 unit_combo = QComboBox()
1999 unit_combo.addItems(units)
2000 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2001 grid_wallet.addWidget(unit_combo, 3, 2)
2002 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2003 + '\n1BTC=1000mBTC.\n' \
2004 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2005 grid_wallet.setRowStretch(4,1)
2010 grid_io = QGridLayout(tab3)
2011 grid_io.setColumnStretch(0,1)
2012 tabs.addTab(tab3, _('Import/Export') )
2014 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2015 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2016 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2017 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2019 grid_io.addWidget(QLabel(_('History')), 2, 0)
2020 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2021 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2023 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2025 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2026 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2027 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2029 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2030 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2031 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2032 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2033 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2036 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2037 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2038 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2039 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2041 grid_io.setRowStretch(5,1)
2046 tab5 = QScrollArea()
2047 grid_plugins = QGridLayout(tab5)
2048 grid_plugins.setColumnStretch(0,1)
2049 tabs.addTab(tab5, _('Plugins') )
2050 def mk_toggle(cb, p):
2051 return lambda: cb.setChecked(p.toggle())
2052 for i, p in enumerate(self.plugins):
2054 name, description = p.get_info()
2055 cb = QCheckBox(name)
2056 cb.setDisabled(not p.is_available())
2057 cb.setChecked(p.is_enabled())
2058 cb.clicked.connect(mk_toggle(cb,p))
2059 grid_plugins.addWidget(cb, i, 0)
2060 if p.requires_settings():
2061 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2062 grid_plugins.addWidget(HelpButton(description), i, 2)
2064 print_msg("Error: cannot display plugin", p)
2065 traceback.print_exc(file=sys.stdout)
2066 grid_plugins.setRowStretch(i+1,1)
2068 self.run_hook('create_settings_tab', tabs)
2070 vbox.addLayout(ok_cancel_buttons(d))
2074 if not d.exec_(): return
2076 fee = unicode(fee_e.text())
2078 fee = self.read_amount(fee)
2080 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2083 if self.wallet.fee != fee:
2084 self.wallet.fee = fee
2087 nz = unicode(nz_e.text())
2092 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2095 if self.wallet.num_zeros != nz:
2096 self.wallet.num_zeros = nz
2097 self.config.set_key('num_zeros', nz, True)
2098 self.update_history_tab()
2099 self.update_receive_tab()
2101 usechange_result = usechange_cb.isChecked()
2102 if self.wallet.use_change != usechange_result:
2103 self.wallet.use_change = usechange_result
2104 self.config.set_key('use_change', self.wallet.use_change, True)
2106 unit_result = units[unit_combo.currentIndex()]
2107 if self.base_unit() != unit_result:
2108 self.decimal_point = 8 if unit_result == 'BTC' else 5
2109 self.config.set_key('decimal_point', self.decimal_point, True)
2110 self.update_history_tab()
2111 self.update_status()
2114 n = int(gap_e.text())
2116 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2119 if self.wallet.gap_limit != n:
2120 r = self.wallet.change_gap_limit(n)
2122 self.update_receive_tab()
2123 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2125 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2127 need_restart = False
2129 lang_request = languages.keys()[lang_combo.currentIndex()]
2130 if lang_request != self.config.get('language'):
2131 self.config.set_key("language", lang_request, True)
2134 cur_request = str(currencies[cur_combo.currentIndex()])
2135 if cur_request != self.config.get('currency', "None"):
2136 self.config.set_key('currency', cur_request, True)
2137 self.update_wallet()
2139 self.run_hook('close_settings_dialog')
2142 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2144 self.receive_tab_set_mode(expert_cb.isChecked())
2148 def network_dialog(wallet, parent=None):
2149 interface = wallet.interface
2151 if interface.is_connected:
2152 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2154 status = _("Not connected")
2155 server = interface.server
2158 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2159 server = interface.server
2161 plist, servers_list = interface.get_servers_list()
2165 d.setWindowTitle(_('Server'))
2166 d.setMinimumSize(375, 20)
2168 vbox = QVBoxLayout()
2171 hbox = QHBoxLayout()
2173 l.setPixmap(QPixmap(":icons/network.png"))
2176 hbox.addWidget(QLabel(status))
2178 vbox.addLayout(hbox)
2182 grid = QGridLayout()
2184 vbox.addLayout(grid)
2187 server_protocol = QComboBox()
2188 server_host = QLineEdit()
2189 server_host.setFixedWidth(200)
2190 server_port = QLineEdit()
2191 server_port.setFixedWidth(60)
2193 protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2194 protocol_letters = 'thsg'
2195 server_protocol.addItems(protocol_names)
2197 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2198 grid.addWidget(server_protocol, 0, 1)
2199 grid.addWidget(server_host, 0, 2)
2200 grid.addWidget(server_port, 0, 3)
2202 def change_protocol(p):
2203 protocol = protocol_letters[p]
2204 host = unicode(server_host.text())
2205 pp = plist.get(host,DEFAULT_PORTS)
2206 if protocol not in pp.keys():
2207 protocol = pp.keys()[0]
2209 server_host.setText( host )
2210 server_port.setText( port )
2212 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2214 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2215 servers_list_widget = QTreeWidget(parent)
2216 servers_list_widget.setHeaderLabels( [ label, _('Pruning') ] )
2217 servers_list_widget.setMaximumHeight(150)
2218 servers_list_widget.setColumnWidth(0, 240)
2219 for _host in servers_list.keys():
2220 pruning_level = servers_list[_host].get('pruning','')
2221 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2222 servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2224 def change_server(host, protocol=None):
2225 pp = plist.get(host,DEFAULT_PORTS)
2227 port = pp.get(protocol)
2228 if not port: protocol = None
2231 if 's' in pp.keys():
2233 port = pp.get(protocol)
2235 protocol = pp.keys()[0]
2236 port = pp.get(protocol)
2238 server_host.setText( host )
2239 server_port.setText( port )
2240 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2242 if not plist: return
2243 for p in protocol_letters:
2244 i = protocol_letters.index(p)
2245 j = server_protocol.model().index(i,0)
2246 if p not in pp.keys() and interface.is_connected:
2247 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2249 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2253 host, port, protocol = server.split(':')
2254 change_server(host,protocol)
2256 servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'),
2257 lambda x,y: change_server(unicode(x.text(0))))
2258 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2260 if not wallet.config.is_modifiable('server'):
2261 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2264 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2265 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2266 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2267 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2270 proxy_mode = QComboBox()
2271 proxy_host = QLineEdit()
2272 proxy_host.setFixedWidth(200)
2273 proxy_port = QLineEdit()
2274 proxy_port.setFixedWidth(60)
2275 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2277 def check_for_disable(index = False):
2278 if proxy_mode.currentText() != 'NONE':
2279 proxy_host.setEnabled(True)
2280 proxy_port.setEnabled(True)
2282 proxy_host.setEnabled(False)
2283 proxy_port.setEnabled(False)
2286 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2288 if not wallet.config.is_modifiable('proxy'):
2289 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2291 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2292 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2293 proxy_host.setText(proxy_config.get("host"))
2294 proxy_port.setText(proxy_config.get("port"))
2296 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2297 grid.addWidget(proxy_mode, 2, 1)
2298 grid.addWidget(proxy_host, 2, 2)
2299 grid.addWidget(proxy_port, 2, 3)
2302 vbox.addLayout(ok_cancel_buttons(d))
2305 if not d.exec_(): return
2307 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2308 if proxy_mode.currentText() != 'NONE':
2309 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2313 wallet.config.set_key("proxy", proxy, True)
2314 wallet.config.set_key("server", server, True)
2315 interface.set_server(server, proxy)
2316 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2319 def closeEvent(self, event):
2321 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2322 self.save_column_widths()
2323 self.config.set_key("column_widths", self.column_widths, True)
2324 self.config.set_key("console-history",self.console.history[-50:])
2330 def __init__(self, wallet, config, app=None):
2331 self.wallet = wallet
2332 self.config = config
2334 self.app = QApplication(sys.argv)
2337 def restore_or_create(self):
2338 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2339 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2340 if r==2: return None
2341 return 'restore' if r==1 else 'create'
2343 def seed_dialog(self):
2344 return ElectrumWindow.seed_dialog( self.wallet )
2346 def network_dialog(self):
2347 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2350 def show_seed(self):
2351 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2354 def password_dialog(self):
2355 if self.wallet.seed:
2356 ElectrumWindow.change_password_dialog(self.wallet)
2359 def restore_wallet(self):
2360 wallet = self.wallet
2361 # wait until we are connected, because the user might have selected another server
2362 if not wallet.interface.is_connected:
2363 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2364 waiting_dialog(waiting)
2366 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2367 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2369 wallet.set_up_to_date(False)
2370 wallet.interface.poke('synchronizer')
2371 waiting_dialog(waiting)
2372 if wallet.is_found():
2373 print_error( "Recovery successful" )
2375 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2382 w = ElectrumWindow(self.wallet, self.config)
2383 if url: w.set_url(url)