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 decimal import Decimal
58 if platform.system() == 'Windows':
59 MONOSPACE_FONT = 'Lucida Console'
60 elif platform.system() == 'Darwin':
61 MONOSPACE_FONT = 'Monaco'
63 MONOSPACE_FONT = 'monospace'
65 from electrum import ELECTRUM_VERSION
68 class UpdateLabel(QtGui.QLabel):
69 def __init__(self, config, parent=None):
70 QtGui.QLabel.__init__(self, parent)
71 self.new_version = False
74 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
75 con.request("GET", "/version")
76 res = con.getresponse()
77 except socket.error as msg:
78 print_error("Could not retrieve version information")
82 self.latest_version = res.read()
83 self.latest_version = self.latest_version.replace("\n","")
84 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
86 self.current_version = ELECTRUM_VERSION
87 if(self.compare_versions(self.latest_version, self.current_version) == 1):
88 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
89 if(self.compare_versions(self.latest_version, latest_seen) == 1):
90 self.new_version = True
91 self.setText(_("New version available") + ": " + self.latest_version)
94 def compare_versions(self, version1, version2):
96 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
97 return cmp(normalize(version1), normalize(version2))
99 def ignore_this_version(self):
101 self.config.set_key("last_seen_version", self.latest_version, True)
102 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
105 def ignore_all_version(self):
107 self.config.set_key("last_seen_version", "9.9.9", True)
108 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
111 def open_website(self):
112 webbrowser.open("http://electrum.org/download.html")
115 def mouseReleaseEvent(self, event):
116 dialog = QDialog(self)
117 dialog.setWindowTitle(_('Electrum update'))
120 main_layout = QGridLayout()
121 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
123 ignore_version = QPushButton(_("Ignore this version"))
124 ignore_version.clicked.connect(self.ignore_this_version)
126 ignore_all_versions = QPushButton(_("Ignore all versions"))
127 ignore_all_versions.clicked.connect(self.ignore_all_version)
129 open_website = QPushButton(_("Goto download page"))
130 open_website.clicked.connect(self.open_website)
132 main_layout.addWidget(ignore_version, 1, 0)
133 main_layout.addWidget(ignore_all_versions, 1, 1)
134 main_layout.addWidget(open_website, 1, 2)
136 dialog.setLayout(main_layout)
140 if not dialog.exec_(): return
142 def numbify(entry, is_int = False):
143 text = unicode(entry.text()).strip()
144 pos = entry.cursorPosition()
146 if not is_int: chars +='.'
147 s = ''.join([i for i in text if i in chars])
151 s = s.replace('.','')
152 s = s[:p] + '.' + s[p:p+8]
154 amount = int( Decimal(s) * 100000000 )
163 entry.setCursorPosition(pos)
167 class Timer(QtCore.QThread):
170 self.emit(QtCore.SIGNAL('timersignal'))
173 class HelpButton(QPushButton):
174 def __init__(self, text):
175 QPushButton.__init__(self, '?')
176 self.setFocusPolicy(Qt.NoFocus)
177 self.setFixedWidth(20)
178 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
181 class EnterButton(QPushButton):
182 def __init__(self, text, func):
183 QPushButton.__init__(self, text)
185 self.clicked.connect(func)
187 def keyPressEvent(self, e):
188 if e.key() == QtCore.Qt.Key_Return:
191 class MyTreeWidget(QTreeWidget):
192 def __init__(self, parent):
193 QTreeWidget.__init__(self, parent)
196 for i in range(0,self.viewport().height()/5):
197 if self.itemAt(QPoint(0,i*5)) == item:
201 for j in range(0,30):
202 if self.itemAt(QPoint(0,i*5 + j)) != item:
204 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
206 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
211 class StatusBarButton(QPushButton):
212 def __init__(self, icon, tooltip, func):
213 QPushButton.__init__(self, icon, '')
214 self.setToolTip(tooltip)
216 self.setMaximumWidth(25)
217 self.clicked.connect(func)
220 def keyPressEvent(self, e):
221 if e.key() == QtCore.Qt.Key_Return:
228 def waiting_dialog(f):
234 w.setWindowTitle('Electrum')
244 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
249 def ok_cancel_buttons(dialog, ok_label=_("OK") ):
252 b = QPushButton(_("Cancel"))
254 b.clicked.connect(dialog.reject)
255 b = QPushButton(ok_label)
257 b.clicked.connect(dialog.accept)
262 def text_dialog(parent, title, label, ok_label):
263 dialog = QDialog(parent)
264 dialog.setMinimumWidth(500)
265 dialog.setWindowTitle(title)
269 l.addWidget(QLabel(label))
272 l.addLayout(ok_cancel_buttons(dialog, ok_label))
274 return unicode(txt.toPlainText())
278 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
279 "receive":[[370],[370,200,130]] }
281 class ElectrumWindow(QMainWindow):
283 def __init__(self, wallet, config):
284 QMainWindow.__init__(self)
288 self.current_account = self.config.get("current_account", None)
291 self.create_status_bar()
293 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
294 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
295 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
296 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
298 self.expert_mode = config.get('classic_expert_mode', False)
300 set_language(config.get('language'))
302 self.funds_error = False
303 self.completions = QStringListModel()
305 self.tabs = tabs = QTabWidget(self)
306 self.column_widths = self.config.get("column_widths", default_column_widths )
307 tabs.addTab(self.create_history_tab(), _('History') )
308 tabs.addTab(self.create_send_tab(), _('Send') )
309 tabs.addTab(self.create_receive_tab(), _('Receive') )
310 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
311 tabs.addTab(self.create_console_tab(), _('Console') )
312 tabs.setMinimumSize(600, 400)
313 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
314 self.setCentralWidget(tabs)
316 g = self.config.get("winpos-qt",[100, 100, 840, 400])
317 self.setGeometry(g[0], g[1], g[2], g[3])
318 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
319 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
320 self.setWindowTitle( title )
322 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
323 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
324 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
325 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
327 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
328 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
329 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
330 self.history_list.setFocus(True)
332 self.exchanger = exchange_rate.Exchanger(self)
333 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
335 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
336 if platform.system() == 'Windows':
337 n = 3 if self.wallet.seed else 2
338 tabs.setCurrentIndex (n)
339 tabs.setCurrentIndex (0)
341 # set initial message
342 self.console.showMessage(self.wallet.interface.banner)
344 # plugins that need to change the GUI do it here
345 self.run_hook('init_gui')
349 def init_plugins(self):
350 import imp, pkgutil, __builtin__
351 if __builtin__.use_local_modules:
352 fp, pathname, description = imp.find_module('plugins')
353 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
354 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
355 imp.load_module('electrum_plugins', fp, pathname, description)
356 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
358 import electrum_plugins
359 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
360 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
365 self.plugins.append( p.Plugin(self) )
367 print_msg("Error:cannot initialize plugin",p)
368 traceback.print_exc(file=sys.stdout)
371 def run_hook(self, name, *args):
372 for p in self.plugins:
373 if not p.is_enabled():
383 def set_label(self, name, text = None):
385 old_text = self.wallet.labels.get(name)
388 self.wallet.labels[name] = text
392 self.wallet.labels.pop(name)
394 self.run_hook('set_label', name, text, changed)
398 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
399 def getOpenFileName(self, title, filter = None):
400 directory = self.config.get('io_dir', os.path.expanduser('~'))
401 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
402 if fileName and directory != os.path.dirname(fileName):
403 self.config.set_key('io_dir', os.path.dirname(fileName), True)
406 def getSaveFileName(self, title, filename, filter = None):
407 directory = self.config.get('io_dir', os.path.expanduser('~'))
408 path = os.path.join( directory, filename )
409 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
410 if fileName and directory != os.path.dirname(fileName):
411 self.config.set_key('io_dir', os.path.dirname(fileName), True)
417 QMainWindow.close(self)
418 self.run_hook('close_main_window')
420 def connect_slots(self, sender):
421 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
422 self.previous_payto_e=''
424 def timer_actions(self):
425 self.run_hook('timer_actions')
427 def update_status(self):
428 if self.wallet.interface and self.wallet.interface.is_connected:
429 if not self.wallet.up_to_date:
430 text = _("Synchronizing...")
431 icon = QIcon(":icons/status_waiting.png")
433 c, u = self.wallet.get_account_balance(self.current_account)
434 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
435 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
436 text += self.create_quote_text(Decimal(c+u)/100000000)
437 icon = QIcon(":icons/status_connected.png")
439 text = _("Not connected")
440 icon = QIcon(":icons/status_disconnected.png")
442 self.status_text = text
443 self.statusBar().showMessage(text)
444 self.status_button.setIcon( icon )
446 def update_wallet(self):
448 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
449 self.update_history_tab()
450 self.update_receive_tab()
451 self.update_contacts_tab()
452 self.update_completions()
455 def create_quote_text(self, btc_balance):
456 quote_currency = self.config.get("currency", "None")
457 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
458 if quote_balance is None:
461 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
464 def create_history_tab(self):
465 self.history_list = l = MyTreeWidget(self)
467 for i,width in enumerate(self.column_widths['history']):
468 l.setColumnWidth(i, width)
469 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
470 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
471 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
473 l.setContextMenuPolicy(Qt.CustomContextMenu)
474 l.customContextMenuRequested.connect(self.create_history_menu)
478 def create_history_menu(self, position):
479 self.history_list.selectedIndexes()
480 item = self.history_list.currentItem()
482 tx_hash = str(item.data(0, Qt.UserRole).toString())
483 if not tx_hash: return
485 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
486 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
487 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
488 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
491 def show_tx_details(self, tx):
492 dialog = QDialog(self)
494 dialog.setWindowTitle(_("Transaction Details"))
496 dialog.setLayout(vbox)
497 dialog.setMinimumSize(600,300)
500 if tx_hash in self.wallet.transactions.keys():
501 is_mine, v, fee = self.wallet.get_tx_value(tx)
502 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
504 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
510 vbox.addWidget(QLabel("Transaction ID:"))
511 e = QLineEdit(tx_hash)
515 vbox.addWidget(QLabel("Date: %s"%time_str))
516 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
519 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
520 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
522 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
523 vbox.addWidget(QLabel("Transaction fee: unknown"))
525 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
527 vbox.addWidget( self.generate_transaction_information_widget(tx) )
529 ok_button = QPushButton(_("Close"))
530 ok_button.setDefault(True)
531 ok_button.clicked.connect(dialog.accept)
535 hbox.addWidget(ok_button)
539 def tx_label_clicked(self, item, column):
540 if column==2 and item.isSelected():
542 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
543 self.history_list.editItem( item, column )
544 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
547 def tx_label_changed(self, item, column):
551 tx_hash = str(item.data(0, Qt.UserRole).toString())
552 tx = self.wallet.transactions.get(tx_hash)
553 text = unicode( item.text(2) )
554 self.set_label(tx_hash, text)
556 item.setForeground(2, QBrush(QColor('black')))
558 text = self.wallet.get_default_label(tx_hash)
559 item.setText(2, text)
560 item.setForeground(2, QBrush(QColor('gray')))
564 def edit_label(self, is_recv):
565 l = self.receive_list if is_recv else self.contacts_list
566 item = l.currentItem()
567 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
568 l.editItem( item, 1 )
569 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
573 def address_label_clicked(self, item, column, l, column_addr, column_label):
574 if column == column_label and item.isSelected():
575 is_editable = item.data(0, 32).toBool()
578 addr = unicode( item.text(column_addr) )
579 label = unicode( item.text(column_label) )
580 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
581 l.editItem( item, column )
582 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
585 def address_label_changed(self, item, column, l, column_addr, column_label):
586 if column == column_label:
587 addr = unicode( item.text(column_addr) )
588 text = unicode( item.text(column_label) )
589 is_editable = item.data(0, 32).toBool()
593 changed = self.set_label(addr, text)
595 self.update_history_tab()
596 self.update_completions()
598 self.current_item_changed(item)
600 self.run_hook('item_changed', item, column)
603 def current_item_changed(self, a):
604 self.run_hook('current_item_changed', a)
608 def update_history_tab(self):
610 self.history_list.clear()
611 for item in self.wallet.get_tx_history(self.current_account):
612 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
615 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
621 icon = QIcon(":icons/unconfirmed.png")
623 icon = QIcon(":icons/clock%d.png"%conf)
625 icon = QIcon(":icons/confirmed.png")
628 icon = QIcon(":icons/unconfirmed.png")
630 if value is not None:
631 v_str = format_satoshis(value, True, self.wallet.num_zeros)
635 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
638 label, is_default_label = self.wallet.get_label(tx_hash)
640 label = _('Pruned transaction outputs')
641 is_default_label = False
643 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
644 item.setFont(2, QFont(MONOSPACE_FONT))
645 item.setFont(3, QFont(MONOSPACE_FONT))
646 item.setFont(4, QFont(MONOSPACE_FONT))
648 item.setForeground(3, QBrush(QColor("#BC1E1E")))
650 item.setData(0, Qt.UserRole, tx_hash)
651 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
653 item.setForeground(2, QBrush(QColor('grey')))
655 item.setIcon(0, icon)
656 self.history_list.insertTopLevelItem(0,item)
659 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
662 def create_send_tab(self):
667 grid.setColumnMinimumWidth(3,300)
668 grid.setColumnStretch(5,1)
671 self.payto_e = QLineEdit()
672 grid.addWidget(QLabel(_('Pay to')), 1, 0)
673 grid.addWidget(self.payto_e, 1, 1, 1, 3)
675 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)
677 completer = QCompleter()
678 completer.setCaseSensitivity(False)
679 self.payto_e.setCompleter(completer)
680 completer.setModel(self.completions)
682 self.message_e = QLineEdit()
683 grid.addWidget(QLabel(_('Description')), 2, 0)
684 grid.addWidget(self.message_e, 2, 1, 1, 3)
685 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)
687 self.amount_e = QLineEdit()
688 grid.addWidget(QLabel(_('Amount')), 3, 0)
689 grid.addWidget(self.amount_e, 3, 1, 1, 2)
690 grid.addWidget(HelpButton(
691 _('Amount to be sent.') + '\n\n' \
692 + _('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.')
693 + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
695 self.fee_e = QLineEdit()
696 grid.addWidget(QLabel(_('Fee')), 4, 0)
697 grid.addWidget(self.fee_e, 4, 1, 1, 2)
698 grid.addWidget(HelpButton(
699 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
700 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
701 + _('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)
704 b = EnterButton(_("Send"), self.do_send)
706 b = EnterButton(_("Create unsigned transaction"), self.do_send)
707 grid.addWidget(b, 6, 1)
709 b = EnterButton(_("Clear"),self.do_clear)
710 grid.addWidget(b, 6, 2)
712 self.payto_sig = QLabel('')
713 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
715 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
716 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
725 def entry_changed( is_fee ):
726 self.funds_error = False
728 if self.amount_e.text() == '!':
729 c, u = self.wallet.get_account_balance(self.current_account)
730 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
731 fee = self.wallet.estimated_fee(inputs)
733 self.amount_e.setText( str( Decimal( amount ) / 100000000 ) )
734 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
737 amount = numbify(self.amount_e)
738 fee = numbify(self.fee_e)
739 if not is_fee: fee = None
742 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
744 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
747 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
748 text = self.status_text
751 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
752 self.funds_error = True
753 text = _( "Not enough funds" )
755 self.statusBar().showMessage(text)
756 self.amount_e.setPalette(palette)
757 self.fee_e.setPalette(palette)
759 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
760 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
762 self.run_hook('create_send_tab', grid)
766 def update_completions(self):
768 for addr,label in self.wallet.labels.items():
769 if addr in self.wallet.addressbook:
770 l.append( label + ' <' + addr + '>')
772 self.run_hook('update_completions', l)
773 self.completions.setStringList(l)
777 return lambda s, *args: s.do_protect(func, args)
781 def do_send(self, password):
783 label = unicode( self.message_e.text() )
784 r = unicode( self.payto_e.text() )
787 # label or alias, with address in brackets
788 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
789 to_address = m.group(2) if m else r
791 if not is_valid(to_address):
792 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
796 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
798 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
801 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
803 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
807 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
808 except BaseException, e:
809 self.show_message(str(e))
812 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
813 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
816 self.run_hook('send_tx', tx)
819 self.set_label(tx.hash(), label)
822 h = self.wallet.send_tx(tx)
823 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
824 status, msg = self.wallet.receive_tx( h )
826 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
828 self.update_contacts_tab()
830 QMessageBox.warning(self, _('Error'), msg, _('OK'))
832 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
834 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
835 with open(fileName,'w') as f:
836 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
837 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
839 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
844 def set_url(self, url):
845 address, amount, label, message, signature, identity, url = util.parse_url(url)
847 if label and self.wallet.labels.get(address) != label:
848 if self.question('Give label "%s" to address %s ?'%(label,address)):
849 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
850 self.wallet.addressbook.append(address)
851 self.set_label(address, label)
853 self.run_hook('set_url', url, self.show_message, self.question)
855 self.tabs.setCurrentIndex(1)
856 label = self.wallet.labels.get(address)
857 m_addr = label + ' <'+ address +'>' if label else address
858 self.payto_e.setText(m_addr)
860 self.message_e.setText(message)
861 self.amount_e.setText(amount)
863 self.set_frozen(self.payto_e,True)
864 self.set_frozen(self.amount_e,True)
865 self.set_frozen(self.message_e,True)
866 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
868 self.payto_sig.setVisible(False)
871 self.payto_sig.setVisible(False)
872 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
874 self.set_frozen(e,False)
876 def set_frozen(self,entry,frozen):
878 entry.setReadOnly(True)
879 entry.setFrame(False)
881 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
882 entry.setPalette(palette)
884 entry.setReadOnly(False)
887 palette.setColor(entry.backgroundRole(), QColor('white'))
888 entry.setPalette(palette)
891 def toggle_freeze(self,addr):
893 if addr in self.wallet.frozen_addresses:
894 self.wallet.unfreeze(addr)
896 self.wallet.freeze(addr)
897 self.update_receive_tab()
899 def toggle_priority(self,addr):
901 if addr in self.wallet.prioritized_addresses:
902 self.wallet.unprioritize(addr)
904 self.wallet.prioritize(addr)
905 self.update_receive_tab()
908 def create_list_tab(self, headers):
909 "generic tab creation method"
910 l = MyTreeWidget(self)
911 l.setColumnCount( len(headers) )
912 l.setHeaderLabels( headers )
922 vbox.addWidget(buttons)
927 buttons.setLayout(hbox)
932 def create_receive_tab(self):
933 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
934 l.setContextMenuPolicy(Qt.CustomContextMenu)
935 l.customContextMenuRequested.connect(self.create_receive_menu)
936 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
937 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
938 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
939 self.receive_list = l
940 self.receive_buttons_hbox = hbox
945 def receive_tab_set_mode(self, i):
946 self.save_column_widths()
947 self.expert_mode = (i == 1)
948 self.config.set_key('classic_expert_mode', self.expert_mode, True)
950 self.update_receive_tab()
953 def save_column_widths(self):
954 if not self.expert_mode:
955 widths = [ self.receive_list.columnWidth(0) ]
958 for i in range(self.receive_list.columnCount() -1):
959 widths.append(self.receive_list.columnWidth(i))
960 self.column_widths["receive"][self.expert_mode] = widths
962 self.column_widths["history"] = []
963 for i in range(self.history_list.columnCount() - 1):
964 self.column_widths["history"].append(self.history_list.columnWidth(i))
966 self.column_widths["contacts"] = []
967 for i in range(self.contacts_list.columnCount() - 1):
968 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
971 def create_contacts_tab(self):
972 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
973 l.setContextMenuPolicy(Qt.CustomContextMenu)
974 l.customContextMenuRequested.connect(self.create_contact_menu)
975 for i,width in enumerate(self.column_widths['contacts']):
976 l.setColumnWidth(i, width)
978 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
979 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
980 self.contacts_list = l
981 self.contacts_buttons_hbox = hbox
982 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
987 def delete_imported_key(self, addr):
988 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
989 self.wallet.imported_keys.pop(addr)
990 self.update_receive_tab()
991 self.update_history_tab()
995 def create_receive_menu(self, position):
996 # fixme: this function apparently has a side effect.
997 # if it is not called the menu pops up several times
998 #self.receive_list.selectedIndexes()
1000 item = self.receive_list.itemAt(position)
1002 addr = unicode(item.text(0))
1003 if not is_valid(addr):
1004 item.setExpanded(not item.isExpanded())
1007 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1008 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1009 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1010 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1011 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1012 if addr in self.wallet.imported_keys:
1013 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1015 if self.expert_mode:
1016 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1017 menu.addAction(t, lambda: self.toggle_freeze(addr))
1018 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1019 menu.addAction(t, lambda: self.toggle_priority(addr))
1021 self.run_hook('receive_menu', menu)
1022 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1025 def payto(self, addr):
1027 label = self.wallet.labels.get(addr)
1028 m_addr = label + ' <' + addr + '>' if label else addr
1029 self.tabs.setCurrentIndex(1)
1030 self.payto_e.setText(m_addr)
1031 self.amount_e.setFocus()
1034 def delete_contact(self, x):
1035 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1036 if x in self.wallet.addressbook:
1037 self.wallet.addressbook.remove(x)
1038 self.set_label(x, None)
1039 self.update_history_tab()
1040 self.update_contacts_tab()
1041 self.update_completions()
1044 def create_contact_menu(self, position):
1045 item = self.contacts_list.itemAt(position)
1047 addr = unicode(item.text(0))
1048 label = unicode(item.text(1))
1049 is_editable = item.data(0,32).toBool()
1050 payto_addr = item.data(0,33).toString()
1052 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1053 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1054 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1056 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1057 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1059 self.run_hook('create_contact_menu', menu, item)
1060 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1063 def update_receive_item(self, item):
1064 item.setFont(0, QFont(MONOSPACE_FONT))
1065 address = str(item.data(0,0).toString())
1066 label = self.wallet.labels.get(address,'')
1067 item.setData(1,0,label)
1068 item.setData(0,32, True) # is editable
1070 self.run_hook('update_receive_item', address, item)
1072 c, u = self.wallet.get_addr_balance(address)
1073 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1074 item.setData(2,0,balance)
1076 if self.expert_mode:
1077 if address in self.wallet.frozen_addresses:
1078 item.setBackgroundColor(0, QColor('lightblue'))
1079 elif address in self.wallet.prioritized_addresses:
1080 item.setBackgroundColor(0, QColor('lightgreen'))
1083 def update_receive_tab(self):
1084 l = self.receive_list
1087 l.setColumnHidden(2, not self.expert_mode)
1088 l.setColumnHidden(3, not self.expert_mode)
1089 if not self.expert_mode:
1090 width = self.column_widths['receive'][0][0]
1091 l.setColumnWidth(0, width)
1093 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1094 l.setColumnWidth(i, width)
1096 if self.current_account is None:
1097 account_items = self.wallet.accounts.items()
1098 elif self.current_account != -1:
1099 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1103 print self.current_account
1105 for k, account in account_items:
1106 name = account.get('name',str(k))
1107 c,u = self.wallet.get_account_balance(k)
1108 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1109 l.addTopLevelItem(account_item)
1110 account_item.setExpanded(True)
1112 for is_change in ([0,1] if self.expert_mode else [0]):
1113 if self.expert_mode:
1114 name = "Receiving" if not is_change else "Change"
1115 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1116 account_item.addChild(seq_item)
1117 if not is_change: seq_item.setExpanded(True)
1119 seq_item = account_item
1123 for address in account[is_change]:
1124 h = self.wallet.history.get(address,[])
1128 if gap > self.wallet.gap_limit:
1133 num_tx = '*' if h == ['*'] else "%d"%len(h)
1134 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1135 self.update_receive_item(item)
1137 item.setBackgroundColor(1, QColor('red'))
1138 seq_item.addChild(item)
1141 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1142 c,u = self.wallet.get_imported_balance()
1143 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1144 l.addTopLevelItem(account_item)
1145 account_item.setExpanded(True)
1146 for address in self.wallet.imported_keys.keys():
1147 item = QTreeWidgetItem( [ address, '', '', ''] )
1148 self.update_receive_item(item)
1149 account_item.addChild(item)
1152 # we use column 1 because column 0 may be hidden
1153 l.setCurrentItem(l.topLevelItem(0),1)
1156 def update_contacts_tab(self):
1158 l = self.contacts_list
1161 for address in self.wallet.addressbook:
1162 label = self.wallet.labels.get(address,'')
1163 n = self.wallet.get_num_tx(address)
1164 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1165 item.setFont(0, QFont(MONOSPACE_FONT))
1166 # 32 = label can be edited (bool)
1167 item.setData(0,32, True)
1169 item.setData(0,33, address)
1170 l.addTopLevelItem(item)
1172 self.run_hook('update_contacts_tab', l)
1173 l.setCurrentItem(l.topLevelItem(0))
1177 def create_console_tab(self):
1178 from qt_console import Console
1179 self.console = console = Console()
1180 self.console.history = self.config.get("console-history",[])
1181 self.console.history_index = len(self.console.history)
1183 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1184 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1186 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1188 def mkfunc(f, method):
1189 return lambda *args: apply( f, (method, args, self.password_dialog ))
1191 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1192 methods[m] = mkfunc(c._run, m)
1194 console.updateNamespace(methods)
1197 def change_account(self,s):
1198 if s == _("All accounts"):
1199 self.current_account = None
1201 accounts = self.wallet.get_accounts()
1202 for k, v in accounts.items():
1204 self.current_account = k
1205 self.update_history_tab()
1206 self.update_status()
1207 self.update_receive_tab()
1209 def create_status_bar(self):
1210 self.status_text = ""
1212 sb.setFixedHeight(35)
1213 qtVersion = qVersion()
1215 update_notification = UpdateLabel(self.config)
1216 if(update_notification.new_version):
1217 sb.addPermanentWidget(update_notification)
1219 accounts = self.wallet.get_accounts()
1220 if len(accounts) > 1:
1221 from_combo = QComboBox()
1222 from_combo.addItems([_("All accounts")] + accounts.values())
1223 from_combo.setCurrentIndex(0)
1224 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1225 sb.addPermanentWidget(from_combo)
1227 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1228 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1229 if self.wallet.seed:
1230 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1231 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1232 sb.addPermanentWidget( self.password_button )
1233 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1234 if self.wallet.seed:
1235 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1236 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1237 sb.addPermanentWidget( self.status_button )
1239 self.run_hook('create_status_bar', (sb,))
1241 self.setStatusBar(sb)
1245 self.config.set_key('gui', 'lite', True)
1248 self.lite.mini.show()
1250 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1251 self.lite.main(None)
1253 def new_contact_dialog(self):
1254 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1255 address = unicode(text)
1257 if is_valid(address):
1258 self.wallet.addressbook.append(address)
1260 self.update_contacts_tab()
1261 self.update_history_tab()
1262 self.update_completions()
1264 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1266 def show_master_public_key(self):
1267 dialog = QDialog(self)
1269 dialog.setWindowTitle(_("Master Public Key"))
1271 main_text = QTextEdit()
1272 main_text.setText(self.wallet.get_master_public_key())
1273 main_text.setReadOnly(True)
1274 main_text.setMaximumHeight(170)
1275 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1277 ok_button = QPushButton(_("OK"))
1278 ok_button.setDefault(True)
1279 ok_button.clicked.connect(dialog.accept)
1281 main_layout = QGridLayout()
1282 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1284 main_layout.addWidget(main_text, 1, 0)
1285 main_layout.addWidget(qrw, 1, 1 )
1287 vbox = QVBoxLayout()
1288 vbox.addLayout(main_layout)
1289 hbox = QHBoxLayout()
1291 hbox.addWidget(ok_button)
1292 vbox.addLayout(hbox)
1294 dialog.setLayout(vbox)
1299 def show_seed_dialog(self, password):
1300 if not self.wallet.seed:
1301 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1304 seed = self.wallet.decode_seed(password)
1306 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1308 self.show_seed(seed, self.wallet.imported_keys, self)
1312 def show_seed(self, seed, imported_keys, parent=None):
1313 dialog = QDialog(parent)
1315 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1317 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1319 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1321 seed_text = QTextEdit(brainwallet)
1322 seed_text.setReadOnly(True)
1323 seed_text.setMaximumHeight(130)
1325 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1326 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1327 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1328 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1330 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1331 label2 = QLabel(msg2)
1332 label2.setWordWrap(True)
1335 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1336 logo.setMaximumWidth(60)
1338 qrw = QRCodeWidget(seed)
1340 ok_button = QPushButton(_("OK"))
1341 ok_button.setDefault(True)
1342 ok_button.clicked.connect(dialog.accept)
1344 grid = QGridLayout()
1345 #main_layout.addWidget(logo, 0, 0)
1347 grid.addWidget(logo, 0, 0)
1348 grid.addWidget(label1, 0, 1)
1350 grid.addWidget(seed_text, 1, 0, 1, 2)
1352 grid.addWidget(qrw, 0, 2, 2, 1)
1354 vbox = QVBoxLayout()
1355 vbox.addLayout(grid)
1356 vbox.addWidget(label2)
1358 hbox = QHBoxLayout()
1360 hbox.addWidget(ok_button)
1361 vbox.addLayout(hbox)
1363 dialog.setLayout(vbox)
1366 def show_qrcode(self, data, title = "QR code"):
1370 d.setWindowTitle(title)
1371 d.setMinimumSize(270, 300)
1372 vbox = QVBoxLayout()
1373 qrw = QRCodeWidget(data)
1374 vbox.addWidget(qrw, 1)
1375 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1376 hbox = QHBoxLayout()
1380 filename = "qrcode.bmp"
1381 bmp.save_qrcode(qrw.qr, filename)
1382 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1384 b = QPushButton(_("Save"))
1386 b.clicked.connect(print_qr)
1388 b = QPushButton(_("Close"))
1390 b.clicked.connect(d.accept)
1393 vbox.addLayout(hbox)
1398 def do_protect(self, func, args):
1399 if self.wallet.use_encryption:
1400 password = self.password_dialog()
1406 if args != (False,):
1407 args = (self,) + args + (password,)
1409 args = (self,password)
1414 def show_private_key(self, address, password):
1415 if not address: return
1417 pk = self.wallet.get_private_key(address, password)
1418 except BaseException, e:
1419 self.show_message(str(e))
1421 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1425 def do_sign(self, address, message, signature, password):
1427 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1428 signature.setText(sig)
1429 except BaseException, e:
1430 self.show_message(str(e))
1432 def sign_message(self, address):
1433 if not address: return
1436 d.setWindowTitle(_('Sign Message'))
1437 d.setMinimumSize(410, 290)
1439 tab_widget = QTabWidget()
1441 layout = QGridLayout(tab)
1443 sign_address = QLineEdit()
1445 sign_address.setText(address)
1446 layout.addWidget(QLabel(_('Address')), 1, 0)
1447 layout.addWidget(sign_address, 1, 1)
1449 sign_message = QTextEdit()
1450 layout.addWidget(QLabel(_('Message')), 2, 0)
1451 layout.addWidget(sign_message, 2, 1)
1452 layout.setRowStretch(2,3)
1454 sign_signature = QTextEdit()
1455 layout.addWidget(QLabel(_('Signature')), 3, 0)
1456 layout.addWidget(sign_signature, 3, 1)
1457 layout.setRowStretch(3,1)
1460 hbox = QHBoxLayout()
1461 b = QPushButton(_("Sign"))
1463 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1464 b = QPushButton(_("Close"))
1465 b.clicked.connect(d.accept)
1467 layout.addLayout(hbox, 4, 1)
1468 tab_widget.addTab(tab, _("Sign"))
1472 layout = QGridLayout(tab)
1474 verify_address = QLineEdit()
1475 layout.addWidget(QLabel(_('Address')), 1, 0)
1476 layout.addWidget(verify_address, 1, 1)
1478 verify_message = QTextEdit()
1479 layout.addWidget(QLabel(_('Message')), 2, 0)
1480 layout.addWidget(verify_message, 2, 1)
1481 layout.setRowStretch(2,3)
1483 verify_signature = QTextEdit()
1484 layout.addWidget(QLabel(_('Signature')), 3, 0)
1485 layout.addWidget(verify_signature, 3, 1)
1486 layout.setRowStretch(3,1)
1490 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1491 self.show_message(_("Signature verified"))
1492 except BaseException, e:
1493 self.show_message(str(e))
1496 hbox = QHBoxLayout()
1497 b = QPushButton(_("Verify"))
1498 b.clicked.connect(do_verify)
1500 b = QPushButton(_("Close"))
1501 b.clicked.connect(d.accept)
1503 layout.addLayout(hbox, 4, 1)
1504 tab_widget.addTab(tab, _("Verify"))
1506 vbox = QVBoxLayout()
1507 vbox.addWidget(tab_widget)
1514 def question(self, msg):
1515 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1517 def show_message(self, msg):
1518 QMessageBox.information(self, _('Message'), msg, _('OK'))
1520 def password_dialog(self ):
1527 vbox = QVBoxLayout()
1528 msg = _('Please enter your password')
1529 vbox.addWidget(QLabel(msg))
1531 grid = QGridLayout()
1533 grid.addWidget(QLabel(_('Password')), 1, 0)
1534 grid.addWidget(pw, 1, 1)
1535 vbox.addLayout(grid)
1537 vbox.addLayout(ok_cancel_buttons(d))
1540 self.run_hook('password_dialog', pw, grid, 1)
1541 if not d.exec_(): return
1542 return unicode(pw.text())
1549 def change_password_dialog( wallet, parent=None ):
1552 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1560 new_pw = QLineEdit()
1561 new_pw.setEchoMode(2)
1562 conf_pw = QLineEdit()
1563 conf_pw.setEchoMode(2)
1565 vbox = QVBoxLayout()
1567 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1568 +_('To disable wallet encryption, enter an empty new password.')) \
1569 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1571 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1572 +_("Leave these fields empty if you want to disable encryption.")
1573 vbox.addWidget(QLabel(msg))
1575 grid = QGridLayout()
1578 if wallet.use_encryption:
1579 grid.addWidget(QLabel(_('Password')), 1, 0)
1580 grid.addWidget(pw, 1, 1)
1582 grid.addWidget(QLabel(_('New Password')), 2, 0)
1583 grid.addWidget(new_pw, 2, 1)
1585 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1586 grid.addWidget(conf_pw, 3, 1)
1587 vbox.addLayout(grid)
1589 vbox.addLayout(ok_cancel_buttons(d))
1592 if not d.exec_(): return
1594 password = unicode(pw.text()) if wallet.use_encryption else None
1595 new_password = unicode(new_pw.text())
1596 new_password2 = unicode(conf_pw.text())
1599 seed = wallet.decode_seed(password)
1601 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1604 if new_password != new_password2:
1605 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1606 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1608 wallet.update_password(seed, password, new_password)
1610 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1611 parent.password_button.setIcon( icon )
1615 def seed_dialog(wallet, parent=None):
1619 vbox = QVBoxLayout()
1620 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1621 vbox.addWidget(QLabel(msg))
1623 grid = QGridLayout()
1626 seed_e = QLineEdit()
1627 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1628 grid.addWidget(seed_e, 1, 1)
1629 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1633 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1634 grid.addWidget(gap_e, 2, 1)
1635 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1636 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1637 vbox.addLayout(grid)
1639 vbox.addLayout(ok_cancel_buttons(d))
1642 if not d.exec_(): return
1645 gap = int(unicode(gap_e.text()))
1647 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1651 seed = str(seed_e.text())
1654 print_error("Warning: Not hex, trying decode")
1656 seed = mnemonic.mn_decode( seed.split(' ') )
1658 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1662 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1667 def generate_transaction_information_widget(self, tx):
1668 tabs = QTabWidget(self)
1671 grid_ui = QGridLayout(tab1)
1672 grid_ui.setColumnStretch(0,1)
1673 tabs.addTab(tab1, _('Outputs') )
1675 tree_widget = MyTreeWidget(self)
1676 tree_widget.setColumnCount(2)
1677 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1678 tree_widget.setColumnWidth(0, 300)
1679 tree_widget.setColumnWidth(1, 50)
1681 for address, value in tx.outputs:
1682 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1683 tree_widget.addTopLevelItem(item)
1685 tree_widget.setMaximumHeight(100)
1687 grid_ui.addWidget(tree_widget)
1690 grid_ui = QGridLayout(tab2)
1691 grid_ui.setColumnStretch(0,1)
1692 tabs.addTab(tab2, _('Inputs') )
1694 tree_widget = MyTreeWidget(self)
1695 tree_widget.setColumnCount(2)
1696 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1698 for input_line in tx.inputs:
1699 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1700 tree_widget.addTopLevelItem(item)
1702 tree_widget.setMaximumHeight(100)
1704 grid_ui.addWidget(tree_widget)
1708 def tx_dict_from_text(self, txt):
1710 tx_dict = json.loads(str(txt))
1711 assert "hex" in tx_dict.keys()
1712 assert "complete" in tx_dict.keys()
1713 if not tx_dict["complete"]:
1714 assert "input_info" in tx_dict.keys()
1716 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1721 def read_tx_from_file(self):
1722 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1726 with open(fileName, "r") as f:
1727 file_content = f.read()
1728 except (ValueError, IOError, os.error), reason:
1729 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1731 return self.tx_dict_from_text(file_content)
1735 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1737 self.wallet.signrawtransaction(tx, input_info, [], password)
1739 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1741 with open(fileName, "w+") as f:
1742 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1743 self.show_message(_("Transaction saved successfully"))
1746 except BaseException, e:
1747 self.show_message(str(e))
1750 def send_raw_transaction(self, raw_tx, dialog = ""):
1751 result, result_message = self.wallet.sendtx( raw_tx )
1753 self.show_message("Transaction successfully sent: %s" % (result_message))
1757 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1759 def do_process_from_text(self):
1760 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1763 tx_dict = self.tx_dict_from_text(text)
1765 self.create_process_transaction_window(tx_dict)
1767 def do_process_from_file(self):
1768 tx_dict = self.read_tx_from_file()
1770 self.create_process_transaction_window(tx_dict)
1772 def create_process_transaction_window(self, tx_dict):
1773 tx = Transaction(tx_dict["hex"])
1775 dialog = QDialog(self)
1776 dialog.setMinimumWidth(500)
1777 dialog.setWindowTitle(_('Process raw transaction'))
1783 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1784 l.addWidget(QLabel(_("Actions")), 4,0)
1786 if tx_dict["complete"] == False:
1787 l.addWidget(QLabel(_("Unsigned")), 3,1)
1788 if self.wallet.seed :
1789 b = QPushButton("Sign transaction")
1790 input_info = json.loads(tx_dict["input_info"])
1791 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1792 l.addWidget(b, 4, 1)
1794 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1796 l.addWidget(QLabel(_("Signed")), 3,1)
1797 b = QPushButton("Broadcast transaction")
1798 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1801 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1802 cancelButton = QPushButton(_("Cancel"))
1803 cancelButton.clicked.connect(lambda: dialog.done(0))
1804 l.addWidget(cancelButton, 4,2)
1810 def do_export_privkeys(self, password):
1811 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.")))
1814 select_export = _('Select file to export your private keys to')
1815 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1817 with open(fileName, "w+") as csvfile:
1818 transaction = csv.writer(csvfile)
1819 transaction.writerow(["address", "private_key"])
1822 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1823 transaction.writerow(["%34s"%addr,pk])
1825 self.show_message(_("Private keys exported."))
1827 except (IOError, os.error), reason:
1828 export_error_label = _("Electrum was unable to produce a private key-export.")
1829 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1831 except BaseException, e:
1832 self.show_message(str(e))
1836 def do_import_labels(self):
1837 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1838 if not labelsFile: return
1840 f = open(labelsFile, 'r')
1843 for key, value in json.loads(data).items():
1844 self.wallet.labels[key] = value
1846 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1847 except (IOError, os.error), reason:
1848 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1851 def do_export_labels(self):
1852 labels = self.wallet.labels
1854 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1856 with open(fileName, 'w+') as f:
1857 json.dump(labels, f)
1858 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1859 except (IOError, os.error), reason:
1860 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1863 def do_export_history(self):
1864 from gui_lite import csv_transaction
1865 csv_transaction(self.wallet)
1869 def do_import_privkey(self, password):
1870 if not self.wallet.imported_keys:
1871 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1872 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1873 + _('In addition, when you send bitcoins from one of your imported addresses, the "change" will be sent to an address derived from your seed, unless you disabled this option.') + '<p>' \
1874 + _('Are you sure you understand what you are doing?'), 3, 4)
1877 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1880 text = str(text).split()
1885 addr = self.wallet.import_key(key, password)
1886 except BaseException as e:
1892 addrlist.append(addr)
1894 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1896 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1897 self.update_receive_tab()
1898 self.update_history_tab()
1901 def settings_dialog(self):
1903 d.setWindowTitle(_('Electrum Settings'))
1905 vbox = QVBoxLayout()
1907 tabs = QTabWidget(self)
1908 self.settings_tab = tabs
1909 vbox.addWidget(tabs)
1912 grid_ui = QGridLayout(tab1)
1913 grid_ui.setColumnStretch(0,1)
1914 tabs.addTab(tab1, _('Display') )
1916 nz_label = QLabel(_('Display zeros'))
1917 grid_ui.addWidget(nz_label, 0, 0)
1919 nz_e.setText("%d"% self.wallet.num_zeros)
1920 grid_ui.addWidget(nz_e, 0, 1)
1921 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1922 grid_ui.addWidget(HelpButton(msg), 0, 2)
1923 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1924 if not self.config.is_modifiable('num_zeros'):
1925 for w in [nz_e, nz_label]: w.setEnabled(False)
1927 lang_label=QLabel(_('Language') + ':')
1928 grid_ui.addWidget(lang_label, 1, 0)
1929 lang_combo = QComboBox()
1930 from i18n import languages
1931 lang_combo.addItems(languages.values())
1933 index = languages.keys().index(self.config.get("language",''))
1936 lang_combo.setCurrentIndex(index)
1937 grid_ui.addWidget(lang_combo, 1, 1)
1938 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1939 if not self.config.is_modifiable('language'):
1940 for w in [lang_combo, lang_label]: w.setEnabled(False)
1942 currencies = self.exchanger.get_currencies()
1943 currencies.insert(0, "None")
1945 cur_label=QLabel(_('Currency') + ':')
1946 grid_ui.addWidget(cur_label , 2, 0)
1947 cur_combo = QComboBox()
1948 cur_combo.addItems(currencies)
1950 index = currencies.index(self.config.get('currency', "None"))
1953 cur_combo.setCurrentIndex(index)
1954 grid_ui.addWidget(cur_combo, 2, 1)
1955 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1957 expert_cb = QCheckBox(_('Expert mode'))
1958 expert_cb.setChecked(self.expert_mode)
1959 grid_ui.addWidget(expert_cb, 3, 0)
1960 hh = _('In expert mode, your client will:') + '\n' \
1961 + _(' - Show change addresses in the Receive tab') + '\n' \
1962 + _(' - Display the balance of each address') + '\n' \
1963 + _(' - Add freeze/prioritize actions to addresses.')
1964 grid_ui.addWidget(HelpButton(hh), 3, 2)
1965 grid_ui.setRowStretch(4,1)
1969 grid_wallet = QGridLayout(tab2)
1970 grid_wallet.setColumnStretch(0,1)
1971 tabs.addTab(tab2, _('Wallet') )
1973 fee_label = QLabel(_('Transaction fee'))
1974 grid_wallet.addWidget(fee_label, 0, 0)
1976 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1977 grid_wallet.addWidget(fee_e, 0, 2)
1978 msg = _('Fee per kilobyte of transaction.') + ' ' \
1979 + _('Recommended value') + ': 0.0001'
1980 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1981 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1982 if not self.config.is_modifiable('fee_per_kb'):
1983 for w in [fee_e, fee_label]: w.setEnabled(False)
1985 usechange_label = QLabel(_('Use change addresses'))
1986 grid_wallet.addWidget(usechange_label, 1, 0)
1987 usechange_combo = QComboBox()
1988 usechange_combo.addItems([_('Yes'), _('No')])
1989 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1990 grid_wallet.addWidget(usechange_combo, 1, 2)
1991 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1992 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1994 gap_label = QLabel(_('Gap limit'))
1995 grid_wallet.addWidget(gap_label, 2, 0)
1997 gap_e.setText("%d"% self.wallet.gap_limit)
1998 grid_wallet.addWidget(gap_e, 2, 2)
1999 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2000 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2001 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2002 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2003 + _('Warning') + ': ' \
2004 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2005 + _('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'
2006 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2007 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
2008 if not self.config.is_modifiable('gap_limit'):
2009 for w in [gap_e, gap_label]: w.setEnabled(False)
2011 grid_wallet.setRowStretch(3,1)
2016 grid_io = QGridLayout(tab3)
2017 grid_io.setColumnStretch(0,1)
2018 tabs.addTab(tab3, _('Import/Export') )
2020 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2021 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2022 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2023 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2025 grid_io.addWidget(QLabel(_('History')), 2, 0)
2026 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2027 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2029 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2031 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2032 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2033 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2035 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2036 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2037 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2038 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2039 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2042 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2043 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2044 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2045 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2047 grid_io.setRowStretch(5,1)
2052 tab5 = QScrollArea()
2053 grid_plugins = QGridLayout(tab5)
2054 grid_plugins.setColumnStretch(0,1)
2055 tabs.addTab(tab5, _('Plugins') )
2056 def mk_toggle(cb, p):
2057 return lambda: cb.setChecked(p.toggle())
2058 for i, p in enumerate(self.plugins):
2060 name, description = p.get_info()
2061 cb = QCheckBox(name)
2062 cb.setDisabled(not p.is_available())
2063 cb.setChecked(p.is_enabled())
2064 cb.clicked.connect(mk_toggle(cb,p))
2065 grid_plugins.addWidget(cb, i, 0)
2066 if p.requires_settings():
2067 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2068 grid_plugins.addWidget(HelpButton(description), i, 2)
2070 print_msg("Error: cannot display plugin", p)
2071 traceback.print_exc(file=sys.stdout)
2072 grid_plugins.setRowStretch(i+1,1)
2074 self.run_hook('create_settings_tab', tabs)
2076 vbox.addLayout(ok_cancel_buttons(d))
2080 if not d.exec_(): return
2082 fee = unicode(fee_e.text())
2084 fee = int( 100000000 * Decimal(fee) )
2086 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2089 if self.wallet.fee != fee:
2090 self.wallet.fee = fee
2093 nz = unicode(nz_e.text())
2098 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2101 if self.wallet.num_zeros != nz:
2102 self.wallet.num_zeros = nz
2103 self.config.set_key('num_zeros', nz, True)
2104 self.update_history_tab()
2105 self.update_receive_tab()
2107 usechange_result = usechange_combo.currentIndex() == 0
2108 if self.wallet.use_change != usechange_result:
2109 self.wallet.use_change = usechange_result
2110 self.config.set_key('use_change', self.wallet.use_change, True)
2113 n = int(gap_e.text())
2115 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2118 if self.wallet.gap_limit != n:
2119 r = self.wallet.change_gap_limit(n)
2121 self.update_receive_tab()
2122 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2124 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2126 need_restart = False
2128 lang_request = languages.keys()[lang_combo.currentIndex()]
2129 if lang_request != self.config.get('language'):
2130 self.config.set_key("language", lang_request, True)
2133 cur_request = str(currencies[cur_combo.currentIndex()])
2134 if cur_request != self.config.get('currency', "None"):
2135 self.config.set_key('currency', cur_request, True)
2136 self.update_wallet()
2138 self.run_hook('close_settings_dialog')
2141 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2143 self.receive_tab_set_mode(expert_cb.isChecked())
2147 def network_dialog(wallet, parent=None):
2148 interface = wallet.interface
2150 if interface.is_connected:
2151 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2153 status = _("Not connected")
2154 server = interface.server
2157 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2158 server = interface.server
2160 plist, servers_list = interface.get_servers_list()
2164 d.setWindowTitle(_('Server'))
2165 d.setMinimumSize(375, 20)
2167 vbox = QVBoxLayout()
2170 hbox = QHBoxLayout()
2172 l.setPixmap(QPixmap(":icons/network.png"))
2175 hbox.addWidget(QLabel(status))
2177 vbox.addLayout(hbox)
2181 grid = QGridLayout()
2183 vbox.addLayout(grid)
2186 server_protocol = QComboBox()
2187 server_host = QLineEdit()
2188 server_host.setFixedWidth(200)
2189 server_port = QLineEdit()
2190 server_port.setFixedWidth(60)
2192 protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2193 protocol_letters = 'thsg'
2194 server_protocol.addItems(protocol_names)
2196 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2197 grid.addWidget(server_protocol, 0, 1)
2198 grid.addWidget(server_host, 0, 2)
2199 grid.addWidget(server_port, 0, 3)
2201 def change_protocol(p):
2202 protocol = protocol_letters[p]
2203 host = unicode(server_host.text())
2204 pp = plist.get(host,DEFAULT_PORTS)
2205 if protocol not in pp.keys():
2206 protocol = pp.keys()[0]
2208 server_host.setText( host )
2209 server_port.setText( port )
2211 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2213 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2214 servers_list_widget = QTreeWidget(parent)
2215 servers_list_widget.setHeaderLabels( [ label, _('Pruning') ] )
2216 servers_list_widget.setMaximumHeight(150)
2217 servers_list_widget.setColumnWidth(0, 240)
2218 for _host in servers_list.keys():
2219 pruning_level = servers_list[_host].get('pruning')
2220 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2221 servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2223 def change_server(host, protocol=None):
2224 pp = plist.get(host,DEFAULT_PORTS)
2226 port = pp.get(protocol)
2227 if not port: protocol = None
2230 if 's' in pp.keys():
2232 port = pp.get(protocol)
2234 protocol = pp.keys()[0]
2235 port = pp.get(protocol)
2237 server_host.setText( host )
2238 server_port.setText( port )
2239 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2241 if not plist: return
2242 for p in protocol_letters:
2243 i = protocol_letters.index(p)
2244 j = server_protocol.model().index(i,0)
2245 if p not in pp.keys() and interface.is_connected:
2246 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2248 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2252 host, port, protocol = server.split(':')
2253 change_server(host,protocol)
2255 servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'),
2256 lambda x,y: change_server(unicode(x.text(0))))
2257 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2259 if not wallet.config.is_modifiable('server'):
2260 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2263 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2264 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2265 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2266 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2269 proxy_mode = QComboBox()
2270 proxy_host = QLineEdit()
2271 proxy_host.setFixedWidth(200)
2272 proxy_port = QLineEdit()
2273 proxy_port.setFixedWidth(60)
2274 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2276 def check_for_disable(index = False):
2277 if proxy_mode.currentText() != 'NONE':
2278 proxy_host.setEnabled(True)
2279 proxy_port.setEnabled(True)
2281 proxy_host.setEnabled(False)
2282 proxy_port.setEnabled(False)
2285 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2287 if not wallet.config.is_modifiable('proxy'):
2288 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2290 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2291 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2292 proxy_host.setText(proxy_config.get("host"))
2293 proxy_port.setText(proxy_config.get("port"))
2295 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2296 grid.addWidget(proxy_mode, 2, 1)
2297 grid.addWidget(proxy_host, 2, 2)
2298 grid.addWidget(proxy_port, 2, 3)
2301 vbox.addLayout(ok_cancel_buttons(d))
2304 if not d.exec_(): return
2306 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2307 if proxy_mode.currentText() != 'NONE':
2308 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2312 wallet.config.set_key("proxy", proxy, True)
2313 wallet.config.set_key("server", server, True)
2314 interface.set_server(server, proxy)
2315 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2318 def closeEvent(self, event):
2320 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2321 self.save_column_widths()
2322 self.config.set_key("column_widths", self.column_widths, True)
2323 self.config.set_key("console-history",self.console.history[-50:])
2329 def __init__(self, wallet, config, app=None):
2330 self.wallet = wallet
2331 self.config = config
2333 self.app = QApplication(sys.argv)
2336 def restore_or_create(self):
2337 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2338 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2339 if r==2: return None
2340 return 'restore' if r==1 else 'create'
2342 def seed_dialog(self):
2343 return ElectrumWindow.seed_dialog( self.wallet )
2345 def network_dialog(self):
2346 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2349 def show_seed(self):
2350 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2353 def password_dialog(self):
2354 if self.wallet.seed:
2355 ElectrumWindow.change_password_dialog(self.wallet)
2358 def restore_wallet(self):
2359 wallet = self.wallet
2360 # wait until we are connected, because the user might have selected another server
2361 if not wallet.interface.is_connected:
2362 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2363 waiting_dialog(waiting)
2365 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2366 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2368 wallet.set_up_to_date(False)
2369 wallet.interface.poke('synchronizer')
2370 waiting_dialog(waiting)
2371 if wallet.is_found():
2372 print_error( "Recovery successful" )
2374 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2381 w = ElectrumWindow(self.wallet, self.config)
2382 if url: w.set_url(url)