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)
290 self.create_status_bar()
292 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
293 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
294 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
295 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
297 self.expert_mode = config.get('classic_expert_mode', False)
299 set_language(config.get('language'))
301 self.funds_error = False
302 self.completions = QStringListModel()
304 self.tabs = tabs = QTabWidget(self)
305 self.column_widths = self.config.get("column_widths", default_column_widths )
306 tabs.addTab(self.create_history_tab(), _('History') )
307 tabs.addTab(self.create_send_tab(), _('Send') )
308 tabs.addTab(self.create_receive_tab(), _('Receive') )
309 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
310 tabs.addTab(self.create_console_tab(), _('Console') )
311 tabs.setMinimumSize(600, 400)
312 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
313 self.setCentralWidget(tabs)
315 g = self.config.get("winpos-qt",[100, 100, 840, 400])
316 self.setGeometry(g[0], g[1], g[2], g[3])
317 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
318 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
319 self.setWindowTitle( title )
321 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
322 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
323 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
324 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
326 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
327 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
328 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
329 self.history_list.setFocus(True)
331 self.exchanger = exchange_rate.Exchanger(self)
332 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
334 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
335 if platform.system() == 'Windows':
336 n = 3 if self.wallet.seed else 2
337 tabs.setCurrentIndex (n)
338 tabs.setCurrentIndex (0)
340 # set initial message
341 self.console.showMessage(self.wallet.interface.banner)
343 # plugins that need to change the GUI do it here
344 self.run_hook('init_gui')
348 def init_plugins(self):
349 import imp, pkgutil, __builtin__
350 if __builtin__.use_local_modules:
351 fp, pathname, description = imp.find_module('plugins')
352 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
353 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
354 imp.load_module('electrum_plugins', fp, pathname, description)
355 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
357 import electrum_plugins
358 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
359 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
364 self.plugins.append( p.Plugin(self) )
366 print_msg("Error:cannot initialize plugin",p)
367 traceback.print_exc(file=sys.stdout)
370 def run_hook(self, name, *args):
371 for p in self.plugins:
372 if not p.is_enabled():
382 def set_label(self, name, text = None):
384 old_text = self.wallet.labels.get(name)
387 self.wallet.labels[name] = text
391 self.wallet.labels.pop(name)
393 self.run_hook('set_label', name, text, changed)
397 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
398 def getOpenFileName(self, title, filter = None):
399 directory = self.config.get('io_dir', os.path.expanduser('~'))
400 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
401 if fileName and directory != os.path.dirname(fileName):
402 self.config.set_key('io_dir', os.path.dirname(fileName), True)
405 def getSaveFileName(self, title, filename, filter = None):
406 directory = self.config.get('io_dir', os.path.expanduser('~'))
407 path = os.path.join( directory, filename )
408 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
409 if fileName and directory != os.path.dirname(fileName):
410 self.config.set_key('io_dir', os.path.dirname(fileName), True)
416 QMainWindow.close(self)
417 self.run_hook('close_main_window')
419 def connect_slots(self, sender):
420 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
421 self.previous_payto_e=''
423 def timer_actions(self):
424 self.run_hook('timer_actions')
426 def update_status(self):
427 if self.wallet.interface and self.wallet.interface.is_connected:
428 if not self.wallet.up_to_date:
429 text = _("Synchronizing...")
430 icon = QIcon(":icons/status_waiting.png")
432 c, u = self.wallet.get_balance()
433 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
434 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
435 text += self.create_quote_text(Decimal(c+u)/100000000)
436 icon = QIcon(":icons/status_connected.png")
438 text = _("Not connected")
439 icon = QIcon(":icons/status_disconnected.png")
441 self.status_text = text
442 self.statusBar().showMessage(text)
443 self.status_button.setIcon( icon )
445 def update_wallet(self):
447 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
448 self.update_history_tab()
449 self.update_receive_tab()
450 self.update_contacts_tab()
451 self.update_completions()
454 def create_quote_text(self, btc_balance):
455 quote_currency = self.config.get("currency", "None")
456 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
457 if quote_balance is None:
460 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
463 def create_history_tab(self):
464 self.history_list = l = MyTreeWidget(self)
466 for i,width in enumerate(self.column_widths['history']):
467 l.setColumnWidth(i, width)
468 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
469 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
470 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
472 l.setContextMenuPolicy(Qt.CustomContextMenu)
473 l.customContextMenuRequested.connect(self.create_history_menu)
477 def create_history_menu(self, position):
478 self.history_list.selectedIndexes()
479 item = self.history_list.currentItem()
481 tx_hash = str(item.data(0, Qt.UserRole).toString())
482 if not tx_hash: return
484 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
485 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
486 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
487 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
490 def show_tx_details(self, tx):
491 dialog = QDialog(self)
493 dialog.setWindowTitle(_("Transaction Details"))
495 dialog.setLayout(vbox)
496 dialog.setMinimumSize(600,300)
499 if tx_hash in self.wallet.transactions.keys():
500 is_mine, v, fee = self.wallet.get_tx_value(tx)
501 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
503 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
509 vbox.addWidget(QLabel("Transaction ID:"))
510 e = QLineEdit(tx_hash)
514 vbox.addWidget(QLabel("Date: %s"%time_str))
515 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
518 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
519 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
521 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
522 vbox.addWidget(QLabel("Transaction fee: unknown"))
524 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
526 vbox.addWidget( self.generate_transaction_information_widget(tx) )
528 ok_button = QPushButton(_("Close"))
529 ok_button.setDefault(True)
530 ok_button.clicked.connect(dialog.accept)
534 hbox.addWidget(ok_button)
538 def tx_label_clicked(self, item, column):
539 if column==2 and item.isSelected():
541 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
542 self.history_list.editItem( item, column )
543 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
546 def tx_label_changed(self, item, column):
550 tx_hash = str(item.data(0, Qt.UserRole).toString())
551 tx = self.wallet.transactions.get(tx_hash)
552 text = unicode( item.text(2) )
553 self.set_label(tx_hash, text)
555 item.setForeground(2, QBrush(QColor('black')))
557 text = self.wallet.get_default_label(tx_hash)
558 item.setText(2, text)
559 item.setForeground(2, QBrush(QColor('gray')))
563 def edit_label(self, is_recv):
564 l = self.receive_list if is_recv else self.contacts_list
565 item = l.currentItem()
566 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
567 l.editItem( item, 1 )
568 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
572 def address_label_clicked(self, item, column, l, column_addr, column_label):
573 if column == column_label and item.isSelected():
574 is_editable = item.data(0, 32).toBool()
577 addr = unicode( item.text(column_addr) )
578 label = unicode( item.text(column_label) )
579 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
580 l.editItem( item, column )
581 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
584 def address_label_changed(self, item, column, l, column_addr, column_label):
585 if column == column_label:
586 addr = unicode( item.text(column_addr) )
587 text = unicode( item.text(column_label) )
588 is_editable = item.data(0, 32).toBool()
592 changed = self.set_label(addr, text)
594 self.update_history_tab()
595 self.update_completions()
597 self.current_item_changed(item)
599 self.run_hook('item_changed', item, column)
602 def current_item_changed(self, a):
603 self.run_hook('current_item_changed', a)
607 def update_history_tab(self):
609 self.history_list.clear()
610 for item in self.wallet.get_tx_history():
611 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
614 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
620 icon = QIcon(":icons/unconfirmed.png")
622 icon = QIcon(":icons/clock%d.png"%conf)
624 icon = QIcon(":icons/confirmed.png")
627 icon = QIcon(":icons/unconfirmed.png")
629 if value is not None:
630 v_str = format_satoshis(value, True, self.wallet.num_zeros)
634 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
637 label, is_default_label = self.wallet.get_label(tx_hash)
639 label = _('Pruned transaction outputs')
640 is_default_label = False
642 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
643 item.setFont(2, QFont(MONOSPACE_FONT))
644 item.setFont(3, QFont(MONOSPACE_FONT))
645 item.setFont(4, QFont(MONOSPACE_FONT))
647 item.setForeground(3, QBrush(QColor("#BC1E1E")))
649 item.setData(0, Qt.UserRole, tx_hash)
650 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
652 item.setForeground(2, QBrush(QColor('grey')))
654 item.setIcon(0, icon)
655 self.history_list.insertTopLevelItem(0,item)
658 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
661 def create_send_tab(self):
666 grid.setColumnMinimumWidth(3,300)
667 grid.setColumnStretch(5,1)
669 self.payto_e = QLineEdit()
670 grid.addWidget(QLabel(_('Pay to')), 1, 0)
671 grid.addWidget(self.payto_e, 1, 1, 1, 3)
673 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)
675 completer = QCompleter()
676 completer.setCaseSensitivity(False)
677 self.payto_e.setCompleter(completer)
678 completer.setModel(self.completions)
680 self.message_e = QLineEdit()
681 grid.addWidget(QLabel(_('Description')), 2, 0)
682 grid.addWidget(self.message_e, 2, 1, 1, 3)
683 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)
685 self.amount_e = QLineEdit()
686 grid.addWidget(QLabel(_('Amount')), 3, 0)
687 grid.addWidget(self.amount_e, 3, 1, 1, 2)
688 grid.addWidget(HelpButton(
689 _('Amount to be sent.') + '\n\n' \
690 + _('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.')
691 + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
693 self.fee_e = QLineEdit()
694 grid.addWidget(QLabel(_('Fee')), 4, 0)
695 grid.addWidget(self.fee_e, 4, 1, 1, 2)
696 grid.addWidget(HelpButton(
697 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
698 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
699 + _('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)
702 b = EnterButton(_("Send"), self.do_send)
704 b = EnterButton(_("Create unsigned transaction"), self.do_send)
705 grid.addWidget(b, 6, 1)
707 b = EnterButton(_("Clear"),self.do_clear)
708 grid.addWidget(b, 6, 2)
710 self.payto_sig = QLabel('')
711 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
713 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
714 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
723 def entry_changed( is_fee ):
724 self.funds_error = False
726 if self.amount_e.text() == '!':
727 c, u = self.wallet.get_balance()
728 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0 )
729 fee = self.wallet.estimated_fee(inputs)
731 self.amount_e.setText( str( Decimal( amount ) / 100000000 ) )
732 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
735 amount = numbify(self.amount_e)
736 fee = numbify(self.fee_e)
737 if not is_fee: fee = None
740 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
742 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
745 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
746 text = self.status_text
749 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
750 self.funds_error = True
751 text = _( "Not enough funds" )
753 self.statusBar().showMessage(text)
754 self.amount_e.setPalette(palette)
755 self.fee_e.setPalette(palette)
757 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
758 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
760 self.run_hook('create_send_tab', grid)
764 def update_completions(self):
766 for addr,label in self.wallet.labels.items():
767 if addr in self.wallet.addressbook:
768 l.append( label + ' <' + addr + '>')
770 self.run_hook('update_completions', l)
771 self.completions.setStringList(l)
775 return lambda s, *args: s.do_protect(func, args)
779 def do_send(self, password):
781 label = unicode( self.message_e.text() )
782 r = unicode( self.payto_e.text() )
785 # label or alias, with address in brackets
786 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
787 to_address = m.group(2) if m else r
789 if not is_valid(to_address):
790 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
794 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
796 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
799 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
801 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
805 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
806 except BaseException, e:
807 self.show_message(str(e))
810 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
811 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
814 self.run_hook('send_tx', tx)
817 self.set_label(tx.hash(), label)
820 h = self.wallet.send_tx(tx)
821 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
822 status, msg = self.wallet.receive_tx( h )
824 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
826 self.update_contacts_tab()
828 QMessageBox.warning(self, _('Error'), msg, _('OK'))
830 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
832 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
833 with open(fileName,'w') as f:
834 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
835 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
837 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
842 def set_url(self, url):
843 address, amount, label, message, signature, identity, url = util.parse_url(url)
845 if label and self.wallet.labels.get(address) != label:
846 if self.question('Give label "%s" to address %s ?'%(label,address)):
847 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
848 self.wallet.addressbook.append(address)
849 self.set_label(address, label)
851 self.run_hook('set_url', url, self.show_message, self.question)
853 self.tabs.setCurrentIndex(1)
854 label = self.wallet.labels.get(address)
855 m_addr = label + ' <'+ address +'>' if label else address
856 self.payto_e.setText(m_addr)
858 self.message_e.setText(message)
859 self.amount_e.setText(amount)
861 self.set_frozen(self.payto_e,True)
862 self.set_frozen(self.amount_e,True)
863 self.set_frozen(self.message_e,True)
864 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
866 self.payto_sig.setVisible(False)
869 self.payto_sig.setVisible(False)
870 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
872 self.set_frozen(e,False)
874 def set_frozen(self,entry,frozen):
876 entry.setReadOnly(True)
877 entry.setFrame(False)
879 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
880 entry.setPalette(palette)
882 entry.setReadOnly(False)
885 palette.setColor(entry.backgroundRole(), QColor('white'))
886 entry.setPalette(palette)
889 def toggle_freeze(self,addr):
891 if addr in self.wallet.frozen_addresses:
892 self.wallet.unfreeze(addr)
894 self.wallet.freeze(addr)
895 self.update_receive_tab()
897 def toggle_priority(self,addr):
899 if addr in self.wallet.prioritized_addresses:
900 self.wallet.unprioritize(addr)
902 self.wallet.prioritize(addr)
903 self.update_receive_tab()
906 def create_list_tab(self, headers):
907 "generic tab creation method"
908 l = MyTreeWidget(self)
909 l.setColumnCount( len(headers) )
910 l.setHeaderLabels( headers )
920 vbox.addWidget(buttons)
925 buttons.setLayout(hbox)
930 def create_receive_tab(self):
931 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
932 l.setContextMenuPolicy(Qt.CustomContextMenu)
933 l.customContextMenuRequested.connect(self.create_receive_menu)
934 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
935 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
936 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
937 self.receive_list = l
938 self.receive_buttons_hbox = hbox
943 def receive_tab_set_mode(self, i):
944 self.save_column_widths()
945 self.expert_mode = (i == 1)
946 self.config.set_key('classic_expert_mode', self.expert_mode, True)
948 self.update_receive_tab()
951 def save_column_widths(self):
952 if not self.expert_mode:
953 widths = [ self.receive_list.columnWidth(0) ]
956 for i in range(self.receive_list.columnCount() -1):
957 widths.append(self.receive_list.columnWidth(i))
958 self.column_widths["receive"][self.expert_mode] = widths
960 self.column_widths["history"] = []
961 for i in range(self.history_list.columnCount() - 1):
962 self.column_widths["history"].append(self.history_list.columnWidth(i))
964 self.column_widths["contacts"] = []
965 for i in range(self.contacts_list.columnCount() - 1):
966 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
969 def create_contacts_tab(self):
970 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
971 l.setContextMenuPolicy(Qt.CustomContextMenu)
972 l.customContextMenuRequested.connect(self.create_contact_menu)
973 for i,width in enumerate(self.column_widths['contacts']):
974 l.setColumnWidth(i, width)
976 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
977 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
978 self.contacts_list = l
979 self.contacts_buttons_hbox = hbox
980 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
985 def delete_imported_key(self, addr):
986 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
987 self.wallet.imported_keys.pop(addr)
988 self.update_receive_tab()
989 self.update_history_tab()
993 def create_receive_menu(self, position):
994 # fixme: this function apparently has a side effect.
995 # if it is not called the menu pops up several times
996 #self.receive_list.selectedIndexes()
998 item = self.receive_list.itemAt(position)
1000 addr = unicode(item.text(0))
1001 if not is_valid(addr):
1002 item.setExpanded(not item.isExpanded())
1005 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1006 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1007 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1008 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1009 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1010 if addr in self.wallet.imported_keys:
1011 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1013 if self.expert_mode:
1014 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1015 menu.addAction(t, lambda: self.toggle_freeze(addr))
1016 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1017 menu.addAction(t, lambda: self.toggle_priority(addr))
1019 self.run_hook('receive_menu', menu)
1020 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1023 def payto(self, addr):
1025 label = self.wallet.labels.get(addr)
1026 m_addr = label + ' <' + addr + '>' if label else addr
1027 self.tabs.setCurrentIndex(1)
1028 self.payto_e.setText(m_addr)
1029 self.amount_e.setFocus()
1032 def delete_contact(self, x):
1033 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1034 if x in self.wallet.addressbook:
1035 self.wallet.addressbook.remove(x)
1036 self.set_label(x, None)
1037 self.update_history_tab()
1038 self.update_contacts_tab()
1039 self.update_completions()
1042 def create_contact_menu(self, position):
1043 item = self.contacts_list.itemAt(position)
1045 addr = unicode(item.text(0))
1046 label = unicode(item.text(1))
1047 is_editable = item.data(0,32).toBool()
1048 payto_addr = item.data(0,33).toString()
1050 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1051 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1052 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1054 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1055 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1057 self.run_hook('create_contact_menu', menu, item)
1058 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1061 def update_receive_item(self, item):
1062 item.setFont(0, QFont(MONOSPACE_FONT))
1063 address = str(item.data(0,0).toString())
1064 label = self.wallet.labels.get(address,'')
1065 item.setData(1,0,label)
1066 item.setData(0,32, True) # is editable
1068 self.run_hook('update_receive_item', address, item)
1070 c, u = self.wallet.get_addr_balance(address)
1071 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1072 item.setData(2,0,balance)
1074 if self.expert_mode:
1075 if address in self.wallet.frozen_addresses:
1076 item.setBackgroundColor(0, QColor('lightblue'))
1077 elif address in self.wallet.prioritized_addresses:
1078 item.setBackgroundColor(0, QColor('lightgreen'))
1081 def update_receive_tab(self):
1082 l = self.receive_list
1085 l.setColumnHidden(2, not self.expert_mode)
1086 l.setColumnHidden(3, not self.expert_mode)
1087 if not self.expert_mode:
1088 width = self.column_widths['receive'][0][0]
1089 l.setColumnWidth(0, width)
1091 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1092 l.setColumnWidth(i, width)
1095 for k, account in self.wallet.accounts.items():
1096 name = account.get('name',str(k))
1097 c,u = self.wallet.get_account_balance(k)
1098 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1099 l.addTopLevelItem(account_item)
1100 account_item.setExpanded(True)
1102 for is_change in ([0,1] if self.expert_mode else [0]):
1103 if self.expert_mode:
1104 name = "Receiving" if not is_change else "Change"
1105 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1106 account_item.addChild(seq_item)
1107 if not is_change: seq_item.setExpanded(True)
1109 seq_item = account_item
1113 for address in account[is_change]:
1114 h = self.wallet.history.get(address,[])
1118 if gap > self.wallet.gap_limit:
1123 num_tx = '*' if h == ['*'] else "%d"%len(h)
1124 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1125 self.update_receive_item(item)
1127 item.setBackgroundColor(1, QColor('red'))
1128 seq_item.addChild(item)
1130 if self.wallet.imported_keys:
1131 c,u = self.wallet.get_imported_balance()
1132 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1133 l.addTopLevelItem(account_item)
1134 account_item.setExpanded(True)
1135 for address in self.wallet.imported_keys.keys():
1136 item = QTreeWidgetItem( [ address, '', '', ''] )
1137 self.update_receive_item(item)
1138 account_item.addChild(item)
1141 # we use column 1 because column 0 may be hidden
1142 l.setCurrentItem(l.topLevelItem(0),1)
1145 def update_contacts_tab(self):
1147 l = self.contacts_list
1150 for address in self.wallet.addressbook:
1151 label = self.wallet.labels.get(address,'')
1152 n = self.wallet.get_num_tx(address)
1153 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1154 item.setFont(0, QFont(MONOSPACE_FONT))
1155 # 32 = label can be edited (bool)
1156 item.setData(0,32, True)
1158 item.setData(0,33, address)
1159 l.addTopLevelItem(item)
1161 self.run_hook('update_contacts_tab', l)
1162 l.setCurrentItem(l.topLevelItem(0))
1166 def create_console_tab(self):
1167 from qt_console import Console
1168 self.console = console = Console()
1169 self.console.history = self.config.get("console-history",[])
1170 self.console.history_index = len(self.console.history)
1172 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1173 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1175 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1177 def mkfunc(f, method):
1178 return lambda *args: apply( f, (method, args, self.password_dialog ))
1180 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1181 methods[m] = mkfunc(c._run, m)
1183 console.updateNamespace(methods)
1187 def create_status_bar(self):
1188 self.status_text = ""
1190 sb.setFixedHeight(35)
1191 qtVersion = qVersion()
1193 update_notification = UpdateLabel(self.config)
1194 if(update_notification.new_version):
1195 sb.addPermanentWidget(update_notification)
1197 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1198 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1199 if self.wallet.seed:
1200 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1201 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1202 sb.addPermanentWidget( self.password_button )
1203 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1204 if self.wallet.seed:
1205 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1206 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1207 sb.addPermanentWidget( self.status_button )
1209 self.run_hook('create_status_bar', (sb,))
1211 self.setStatusBar(sb)
1215 self.config.set_key('gui', 'lite', True)
1218 self.lite.mini.show()
1220 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1221 self.lite.main(None)
1223 def new_contact_dialog(self):
1224 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1225 address = unicode(text)
1227 if is_valid(address):
1228 self.wallet.addressbook.append(address)
1230 self.update_contacts_tab()
1231 self.update_history_tab()
1232 self.update_completions()
1234 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1236 def show_master_public_key(self):
1237 dialog = QDialog(self)
1239 dialog.setWindowTitle(_("Master Public Key"))
1241 main_text = QTextEdit()
1242 main_text.setText(self.wallet.get_master_public_key())
1243 main_text.setReadOnly(True)
1244 main_text.setMaximumHeight(170)
1245 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1247 ok_button = QPushButton(_("OK"))
1248 ok_button.setDefault(True)
1249 ok_button.clicked.connect(dialog.accept)
1251 main_layout = QGridLayout()
1252 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1254 main_layout.addWidget(main_text, 1, 0)
1255 main_layout.addWidget(qrw, 1, 1 )
1257 vbox = QVBoxLayout()
1258 vbox.addLayout(main_layout)
1259 hbox = QHBoxLayout()
1261 hbox.addWidget(ok_button)
1262 vbox.addLayout(hbox)
1264 dialog.setLayout(vbox)
1269 def show_seed_dialog(self, password):
1270 if not self.wallet.seed:
1271 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1274 seed = self.wallet.decode_seed(password)
1276 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1278 self.show_seed(seed, self.wallet.imported_keys, self)
1282 def show_seed(self, seed, imported_keys, parent=None):
1283 dialog = QDialog(parent)
1285 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1287 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1289 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1291 seed_text = QTextEdit(brainwallet)
1292 seed_text.setReadOnly(True)
1293 seed_text.setMaximumHeight(130)
1295 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1296 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1297 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1298 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1300 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1301 label2 = QLabel(msg2)
1302 label2.setWordWrap(True)
1305 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1306 logo.setMaximumWidth(60)
1308 qrw = QRCodeWidget(seed)
1310 ok_button = QPushButton(_("OK"))
1311 ok_button.setDefault(True)
1312 ok_button.clicked.connect(dialog.accept)
1314 grid = QGridLayout()
1315 #main_layout.addWidget(logo, 0, 0)
1317 grid.addWidget(logo, 0, 0)
1318 grid.addWidget(label1, 0, 1)
1320 grid.addWidget(seed_text, 1, 0, 1, 2)
1322 grid.addWidget(qrw, 0, 2, 2, 1)
1324 vbox = QVBoxLayout()
1325 vbox.addLayout(grid)
1326 vbox.addWidget(label2)
1328 hbox = QHBoxLayout()
1330 hbox.addWidget(ok_button)
1331 vbox.addLayout(hbox)
1333 dialog.setLayout(vbox)
1336 def show_qrcode(self, data, title = "QR code"):
1340 d.setWindowTitle(title)
1341 d.setMinimumSize(270, 300)
1342 vbox = QVBoxLayout()
1343 qrw = QRCodeWidget(data)
1344 vbox.addWidget(qrw, 1)
1345 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1346 hbox = QHBoxLayout()
1350 filename = "qrcode.bmp"
1351 bmp.save_qrcode(qrw.qr, filename)
1352 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1354 b = QPushButton(_("Save"))
1356 b.clicked.connect(print_qr)
1358 b = QPushButton(_("Close"))
1360 b.clicked.connect(d.accept)
1363 vbox.addLayout(hbox)
1368 def do_protect(self, func, args):
1369 if self.wallet.use_encryption:
1370 password = self.password_dialog()
1376 if args != (False,):
1377 args = (self,) + args + (password,)
1379 args = (self,password)
1384 def show_private_key(self, address, password):
1385 if not address: return
1387 pk = self.wallet.get_private_key(address, password)
1388 except BaseException, e:
1389 self.show_message(str(e))
1391 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1395 def do_sign(self, address, message, signature, password):
1397 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1398 signature.setText(sig)
1399 except BaseException, e:
1400 self.show_message(str(e))
1402 def sign_message(self, address):
1403 if not address: return
1406 d.setWindowTitle(_('Sign Message'))
1407 d.setMinimumSize(410, 290)
1409 tab_widget = QTabWidget()
1411 layout = QGridLayout(tab)
1413 sign_address = QLineEdit()
1415 sign_address.setText(address)
1416 layout.addWidget(QLabel(_('Address')), 1, 0)
1417 layout.addWidget(sign_address, 1, 1)
1419 sign_message = QTextEdit()
1420 layout.addWidget(QLabel(_('Message')), 2, 0)
1421 layout.addWidget(sign_message, 2, 1)
1422 layout.setRowStretch(2,3)
1424 sign_signature = QTextEdit()
1425 layout.addWidget(QLabel(_('Signature')), 3, 0)
1426 layout.addWidget(sign_signature, 3, 1)
1427 layout.setRowStretch(3,1)
1430 hbox = QHBoxLayout()
1431 b = QPushButton(_("Sign"))
1433 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1434 b = QPushButton(_("Close"))
1435 b.clicked.connect(d.accept)
1437 layout.addLayout(hbox, 4, 1)
1438 tab_widget.addTab(tab, _("Sign"))
1442 layout = QGridLayout(tab)
1444 verify_address = QLineEdit()
1445 layout.addWidget(QLabel(_('Address')), 1, 0)
1446 layout.addWidget(verify_address, 1, 1)
1448 verify_message = QTextEdit()
1449 layout.addWidget(QLabel(_('Message')), 2, 0)
1450 layout.addWidget(verify_message, 2, 1)
1451 layout.setRowStretch(2,3)
1453 verify_signature = QTextEdit()
1454 layout.addWidget(QLabel(_('Signature')), 3, 0)
1455 layout.addWidget(verify_signature, 3, 1)
1456 layout.setRowStretch(3,1)
1460 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1461 self.show_message(_("Signature verified"))
1462 except BaseException, e:
1463 self.show_message(str(e))
1466 hbox = QHBoxLayout()
1467 b = QPushButton(_("Verify"))
1468 b.clicked.connect(do_verify)
1470 b = QPushButton(_("Close"))
1471 b.clicked.connect(d.accept)
1473 layout.addLayout(hbox, 4, 1)
1474 tab_widget.addTab(tab, _("Verify"))
1476 vbox = QVBoxLayout()
1477 vbox.addWidget(tab_widget)
1484 def question(self, msg):
1485 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1487 def show_message(self, msg):
1488 QMessageBox.information(self, _('Message'), msg, _('OK'))
1490 def password_dialog(self ):
1497 vbox = QVBoxLayout()
1498 msg = _('Please enter your password')
1499 vbox.addWidget(QLabel(msg))
1501 grid = QGridLayout()
1503 grid.addWidget(QLabel(_('Password')), 1, 0)
1504 grid.addWidget(pw, 1, 1)
1505 vbox.addLayout(grid)
1507 vbox.addLayout(ok_cancel_buttons(d))
1510 self.run_hook('password_dialog', pw, grid, 1)
1511 if not d.exec_(): return
1512 return unicode(pw.text())
1519 def change_password_dialog( wallet, parent=None ):
1522 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1530 new_pw = QLineEdit()
1531 new_pw.setEchoMode(2)
1532 conf_pw = QLineEdit()
1533 conf_pw.setEchoMode(2)
1535 vbox = QVBoxLayout()
1537 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1538 +_('To disable wallet encryption, enter an empty new password.')) \
1539 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1541 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1542 +_("Leave these fields empty if you want to disable encryption.")
1543 vbox.addWidget(QLabel(msg))
1545 grid = QGridLayout()
1548 if wallet.use_encryption:
1549 grid.addWidget(QLabel(_('Password')), 1, 0)
1550 grid.addWidget(pw, 1, 1)
1552 grid.addWidget(QLabel(_('New Password')), 2, 0)
1553 grid.addWidget(new_pw, 2, 1)
1555 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1556 grid.addWidget(conf_pw, 3, 1)
1557 vbox.addLayout(grid)
1559 vbox.addLayout(ok_cancel_buttons(d))
1562 if not d.exec_(): return
1564 password = unicode(pw.text()) if wallet.use_encryption else None
1565 new_password = unicode(new_pw.text())
1566 new_password2 = unicode(conf_pw.text())
1569 seed = wallet.decode_seed(password)
1571 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1574 if new_password != new_password2:
1575 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1576 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1578 wallet.update_password(seed, password, new_password)
1580 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1581 parent.password_button.setIcon( icon )
1585 def seed_dialog(wallet, parent=None):
1589 vbox = QVBoxLayout()
1590 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1591 vbox.addWidget(QLabel(msg))
1593 grid = QGridLayout()
1596 seed_e = QLineEdit()
1597 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1598 grid.addWidget(seed_e, 1, 1)
1599 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1603 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1604 grid.addWidget(gap_e, 2, 1)
1605 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1606 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1607 vbox.addLayout(grid)
1609 vbox.addLayout(ok_cancel_buttons(d))
1612 if not d.exec_(): return
1615 gap = int(unicode(gap_e.text()))
1617 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1621 seed = str(seed_e.text())
1624 print_error("Warning: Not hex, trying decode")
1626 seed = mnemonic.mn_decode( seed.split(' ') )
1628 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1632 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1637 def generate_transaction_information_widget(self, tx):
1638 tabs = QTabWidget(self)
1641 grid_ui = QGridLayout(tab1)
1642 grid_ui.setColumnStretch(0,1)
1643 tabs.addTab(tab1, _('Outputs') )
1645 tree_widget = MyTreeWidget(self)
1646 tree_widget.setColumnCount(2)
1647 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1648 tree_widget.setColumnWidth(0, 300)
1649 tree_widget.setColumnWidth(1, 50)
1651 for address, value in tx.outputs:
1652 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1653 tree_widget.addTopLevelItem(item)
1655 tree_widget.setMaximumHeight(100)
1657 grid_ui.addWidget(tree_widget)
1660 grid_ui = QGridLayout(tab2)
1661 grid_ui.setColumnStretch(0,1)
1662 tabs.addTab(tab2, _('Inputs') )
1664 tree_widget = MyTreeWidget(self)
1665 tree_widget.setColumnCount(2)
1666 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1668 for input_line in tx.inputs:
1669 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1670 tree_widget.addTopLevelItem(item)
1672 tree_widget.setMaximumHeight(100)
1674 grid_ui.addWidget(tree_widget)
1678 def tx_dict_from_text(self, txt):
1680 tx_dict = json.loads(str(txt))
1681 assert "hex" in tx_dict.keys()
1682 assert "complete" in tx_dict.keys()
1683 if not tx_dict["complete"]:
1684 assert "input_info" in tx_dict.keys()
1686 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1691 def read_tx_from_file(self):
1692 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1696 with open(fileName, "r") as f:
1697 file_content = f.read()
1698 except (ValueError, IOError, os.error), reason:
1699 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1701 return self.tx_dict_from_text(file_content)
1705 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1707 self.wallet.signrawtransaction(tx, input_info, [], password)
1709 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1711 with open(fileName, "w+") as f:
1712 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1713 self.show_message(_("Transaction saved successfully"))
1716 except BaseException, e:
1717 self.show_message(str(e))
1720 def send_raw_transaction(self, raw_tx, dialog = ""):
1721 result, result_message = self.wallet.sendtx( raw_tx )
1723 self.show_message("Transaction successfully sent: %s" % (result_message))
1727 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1729 def do_process_from_text(self):
1730 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1733 tx_dict = self.tx_dict_from_text(text)
1735 self.create_process_transaction_window(tx_dict)
1737 def do_process_from_file(self):
1738 tx_dict = self.read_tx_from_file()
1740 self.create_process_transaction_window(tx_dict)
1742 def create_process_transaction_window(self, tx_dict):
1743 tx = Transaction(tx_dict["hex"])
1745 dialog = QDialog(self)
1746 dialog.setMinimumWidth(500)
1747 dialog.setWindowTitle(_('Process raw transaction'))
1753 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1754 l.addWidget(QLabel(_("Actions")), 4,0)
1756 if tx_dict["complete"] == False:
1757 l.addWidget(QLabel(_("Unsigned")), 3,1)
1758 if self.wallet.seed :
1759 b = QPushButton("Sign transaction")
1760 input_info = json.loads(tx_dict["input_info"])
1761 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1762 l.addWidget(b, 4, 1)
1764 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1766 l.addWidget(QLabel(_("Signed")), 3,1)
1767 b = QPushButton("Broadcast transaction")
1768 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1771 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1772 cancelButton = QPushButton(_("Cancel"))
1773 cancelButton.clicked.connect(lambda: dialog.done(0))
1774 l.addWidget(cancelButton, 4,2)
1780 def do_export_privkeys(self, password):
1781 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.")))
1784 select_export = _('Select file to export your private keys to')
1785 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1787 with open(fileName, "w+") as csvfile:
1788 transaction = csv.writer(csvfile)
1789 transaction.writerow(["address", "private_key"])
1792 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1793 transaction.writerow(["%34s"%addr,pk])
1795 self.show_message(_("Private keys exported."))
1797 except (IOError, os.error), reason:
1798 export_error_label = _("Electrum was unable to produce a private key-export.")
1799 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1801 except BaseException, e:
1802 self.show_message(str(e))
1806 def do_import_labels(self):
1807 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1808 if not labelsFile: return
1810 f = open(labelsFile, 'r')
1813 for key, value in json.loads(data).items():
1814 self.wallet.labels[key] = value
1816 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1817 except (IOError, os.error), reason:
1818 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1821 def do_export_labels(self):
1822 labels = self.wallet.labels
1824 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1826 with open(fileName, 'w+') as f:
1827 json.dump(labels, f)
1828 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1829 except (IOError, os.error), reason:
1830 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1833 def do_export_history(self):
1834 from gui_lite import csv_transaction
1835 csv_transaction(self.wallet)
1839 def do_import_privkey(self, password):
1840 if not self.wallet.imported_keys:
1841 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1842 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1843 + _('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>' \
1844 + _('Are you sure you understand what you are doing?'), 3, 4)
1847 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1850 text = str(text).split()
1855 addr = self.wallet.import_key(key, password)
1856 except BaseException as e:
1862 addrlist.append(addr)
1864 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1866 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1867 self.update_receive_tab()
1868 self.update_history_tab()
1871 def settings_dialog(self):
1873 d.setWindowTitle(_('Electrum Settings'))
1875 vbox = QVBoxLayout()
1877 tabs = QTabWidget(self)
1878 self.settings_tab = tabs
1879 vbox.addWidget(tabs)
1882 grid_ui = QGridLayout(tab1)
1883 grid_ui.setColumnStretch(0,1)
1884 tabs.addTab(tab1, _('Display') )
1886 nz_label = QLabel(_('Display zeros'))
1887 grid_ui.addWidget(nz_label, 0, 0)
1889 nz_e.setText("%d"% self.wallet.num_zeros)
1890 grid_ui.addWidget(nz_e, 0, 1)
1891 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1892 grid_ui.addWidget(HelpButton(msg), 0, 2)
1893 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1894 if not self.config.is_modifiable('num_zeros'):
1895 for w in [nz_e, nz_label]: w.setEnabled(False)
1897 lang_label=QLabel(_('Language') + ':')
1898 grid_ui.addWidget(lang_label, 1, 0)
1899 lang_combo = QComboBox()
1900 from i18n import languages
1901 lang_combo.addItems(languages.values())
1903 index = languages.keys().index(self.config.get("language",''))
1906 lang_combo.setCurrentIndex(index)
1907 grid_ui.addWidget(lang_combo, 1, 1)
1908 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1909 if not self.config.is_modifiable('language'):
1910 for w in [lang_combo, lang_label]: w.setEnabled(False)
1912 currencies = self.exchanger.get_currencies()
1913 currencies.insert(0, "None")
1915 cur_label=QLabel(_('Currency') + ':')
1916 grid_ui.addWidget(cur_label , 2, 0)
1917 cur_combo = QComboBox()
1918 cur_combo.addItems(currencies)
1920 index = currencies.index(self.config.get('currency', "None"))
1923 cur_combo.setCurrentIndex(index)
1924 grid_ui.addWidget(cur_combo, 2, 1)
1925 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1927 expert_cb = QCheckBox(_('Expert mode'))
1928 expert_cb.setChecked(self.expert_mode)
1929 grid_ui.addWidget(expert_cb, 3, 0)
1930 hh = _('In expert mode, your client will:') + '\n' \
1931 + _(' - Show change addresses in the Receive tab') + '\n' \
1932 + _(' - Display the balance of each address') + '\n' \
1933 + _(' - Add freeze/prioritize actions to addresses.')
1934 grid_ui.addWidget(HelpButton(hh), 3, 2)
1935 grid_ui.setRowStretch(4,1)
1939 grid_wallet = QGridLayout(tab2)
1940 grid_wallet.setColumnStretch(0,1)
1941 tabs.addTab(tab2, _('Wallet') )
1943 fee_label = QLabel(_('Transaction fee'))
1944 grid_wallet.addWidget(fee_label, 0, 0)
1946 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1947 grid_wallet.addWidget(fee_e, 0, 2)
1948 msg = _('Fee per kilobyte of transaction.') + ' ' \
1949 + _('Recommended value') + ': 0.0001'
1950 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1951 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1952 if not self.config.is_modifiable('fee_per_kb'):
1953 for w in [fee_e, fee_label]: w.setEnabled(False)
1955 usechange_label = QLabel(_('Use change addresses'))
1956 grid_wallet.addWidget(usechange_label, 1, 0)
1957 usechange_combo = QComboBox()
1958 usechange_combo.addItems([_('Yes'), _('No')])
1959 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1960 grid_wallet.addWidget(usechange_combo, 1, 2)
1961 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1962 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1964 gap_label = QLabel(_('Gap limit'))
1965 grid_wallet.addWidget(gap_label, 2, 0)
1967 gap_e.setText("%d"% self.wallet.gap_limit)
1968 grid_wallet.addWidget(gap_e, 2, 2)
1969 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1970 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1971 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1972 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1973 + _('Warning') + ': ' \
1974 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1975 + _('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'
1976 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1977 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1978 if not self.config.is_modifiable('gap_limit'):
1979 for w in [gap_e, gap_label]: w.setEnabled(False)
1981 grid_wallet.setRowStretch(3,1)
1986 grid_io = QGridLayout(tab3)
1987 grid_io.setColumnStretch(0,1)
1988 tabs.addTab(tab3, _('Import/Export') )
1990 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1991 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1992 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1993 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1995 grid_io.addWidget(QLabel(_('History')), 2, 0)
1996 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1997 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1999 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2001 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2002 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2003 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2005 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2006 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2007 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2008 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2009 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2012 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2013 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2014 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2015 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2017 grid_io.setRowStretch(5,1)
2022 tab5 = QScrollArea()
2023 grid_plugins = QGridLayout(tab5)
2024 grid_plugins.setColumnStretch(0,1)
2025 tabs.addTab(tab5, _('Plugins') )
2026 def mk_toggle(cb, p):
2027 return lambda: cb.setChecked(p.toggle())
2028 for i, p in enumerate(self.plugins):
2030 name, description = p.get_info()
2031 cb = QCheckBox(name)
2032 cb.setDisabled(not p.is_available())
2033 cb.setChecked(p.is_enabled())
2034 cb.clicked.connect(mk_toggle(cb,p))
2035 grid_plugins.addWidget(cb, i, 0)
2036 if p.requires_settings():
2037 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2038 grid_plugins.addWidget(HelpButton(description), i, 2)
2040 print_msg("Error: cannot display plugin", p)
2041 traceback.print_exc(file=sys.stdout)
2042 grid_plugins.setRowStretch(i+1,1)
2044 self.run_hook('create_settings_tab', tabs)
2046 vbox.addLayout(ok_cancel_buttons(d))
2050 if not d.exec_(): return
2052 fee = unicode(fee_e.text())
2054 fee = int( 100000000 * Decimal(fee) )
2056 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2059 if self.wallet.fee != fee:
2060 self.wallet.fee = fee
2063 nz = unicode(nz_e.text())
2068 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2071 if self.wallet.num_zeros != nz:
2072 self.wallet.num_zeros = nz
2073 self.config.set_key('num_zeros', nz, True)
2074 self.update_history_tab()
2075 self.update_receive_tab()
2077 usechange_result = usechange_combo.currentIndex() == 0
2078 if self.wallet.use_change != usechange_result:
2079 self.wallet.use_change = usechange_result
2080 self.config.set_key('use_change', self.wallet.use_change, True)
2083 n = int(gap_e.text())
2085 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2088 if self.wallet.gap_limit != n:
2089 r = self.wallet.change_gap_limit(n)
2091 self.update_receive_tab()
2092 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2094 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2096 need_restart = False
2098 lang_request = languages.keys()[lang_combo.currentIndex()]
2099 if lang_request != self.config.get('language'):
2100 self.config.set_key("language", lang_request, True)
2103 cur_request = str(currencies[cur_combo.currentIndex()])
2104 if cur_request != self.config.get('currency', "None"):
2105 self.config.set_key('currency', cur_request, True)
2106 self.update_wallet()
2108 self.run_hook('close_settings_dialog')
2111 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2113 self.receive_tab_set_mode(expert_cb.isChecked())
2117 def network_dialog(wallet, parent=None):
2118 interface = wallet.interface
2120 if interface.is_connected:
2121 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2123 status = _("Not connected")
2124 server = interface.server
2127 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2128 server = interface.server
2130 plist, servers_list = interface.get_servers_list()
2134 d.setWindowTitle(_('Server'))
2135 d.setMinimumSize(375, 20)
2137 vbox = QVBoxLayout()
2140 hbox = QHBoxLayout()
2142 l.setPixmap(QPixmap(":icons/network.png"))
2145 hbox.addWidget(QLabel(status))
2147 vbox.addLayout(hbox)
2151 grid = QGridLayout()
2153 vbox.addLayout(grid)
2156 server_protocol = QComboBox()
2157 server_host = QLineEdit()
2158 server_host.setFixedWidth(200)
2159 server_port = QLineEdit()
2160 server_port.setFixedWidth(60)
2162 protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2163 protocol_letters = 'thsg'
2164 server_protocol.addItems(protocol_names)
2166 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2167 grid.addWidget(server_protocol, 0, 1)
2168 grid.addWidget(server_host, 0, 2)
2169 grid.addWidget(server_port, 0, 3)
2171 def change_protocol(p):
2172 protocol = protocol_letters[p]
2173 host = unicode(server_host.text())
2174 pp = plist.get(host,DEFAULT_PORTS)
2175 if protocol not in pp.keys():
2176 protocol = pp.keys()[0]
2178 server_host.setText( host )
2179 server_port.setText( port )
2181 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2183 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2184 servers_list_widget = QTreeWidget(parent)
2185 servers_list_widget.setHeaderLabels( [ label, _('Pruning') ] )
2186 servers_list_widget.setMaximumHeight(150)
2187 servers_list_widget.setColumnWidth(0, 240)
2188 for _host in servers_list.keys():
2189 pruning_level = servers_list[_host].get('pruning')
2190 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2191 servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2193 def change_server(host, protocol=None):
2194 pp = plist.get(host,DEFAULT_PORTS)
2196 port = pp.get(protocol)
2197 if not port: protocol = None
2200 if 's' in pp.keys():
2202 port = pp.get(protocol)
2204 protocol = pp.keys()[0]
2205 port = pp.get(protocol)
2207 server_host.setText( host )
2208 server_port.setText( port )
2209 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2211 if not plist: return
2212 for p in protocol_letters:
2213 i = protocol_letters.index(p)
2214 j = server_protocol.model().index(i,0)
2215 if p not in pp.keys() and interface.is_connected:
2216 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2218 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2222 host, port, protocol = server.split(':')
2223 change_server(host,protocol)
2225 servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'),
2226 lambda x,y: change_server(unicode(x.text(0))))
2227 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2229 if not wallet.config.is_modifiable('server'):
2230 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2233 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2234 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2235 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2236 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2239 proxy_mode = QComboBox()
2240 proxy_host = QLineEdit()
2241 proxy_host.setFixedWidth(200)
2242 proxy_port = QLineEdit()
2243 proxy_port.setFixedWidth(60)
2244 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2246 def check_for_disable(index = False):
2247 if proxy_mode.currentText() != 'NONE':
2248 proxy_host.setEnabled(True)
2249 proxy_port.setEnabled(True)
2251 proxy_host.setEnabled(False)
2252 proxy_port.setEnabled(False)
2255 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2257 if not wallet.config.is_modifiable('proxy'):
2258 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2260 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2261 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2262 proxy_host.setText(proxy_config.get("host"))
2263 proxy_port.setText(proxy_config.get("port"))
2265 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2266 grid.addWidget(proxy_mode, 2, 1)
2267 grid.addWidget(proxy_host, 2, 2)
2268 grid.addWidget(proxy_port, 2, 3)
2271 vbox.addLayout(ok_cancel_buttons(d))
2274 if not d.exec_(): return
2276 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2277 if proxy_mode.currentText() != 'NONE':
2278 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2282 wallet.config.set_key("proxy", proxy, True)
2283 wallet.config.set_key("server", server, True)
2284 interface.set_server(server, proxy)
2285 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2288 def closeEvent(self, event):
2290 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2291 self.save_column_widths()
2292 self.config.set_key("column_widths", self.column_widths, True)
2293 self.config.set_key("console-history",self.console.history[-50:])
2299 def __init__(self, wallet, config, app=None):
2300 self.wallet = wallet
2301 self.config = config
2303 self.app = QApplication(sys.argv)
2306 def restore_or_create(self):
2307 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2308 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2309 if r==2: return None
2310 return 'restore' if r==1 else 'create'
2312 def seed_dialog(self):
2313 return ElectrumWindow.seed_dialog( self.wallet )
2315 def network_dialog(self):
2316 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2319 def show_seed(self):
2320 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2323 def password_dialog(self):
2324 if self.wallet.seed:
2325 ElectrumWindow.change_password_dialog(self.wallet)
2328 def restore_wallet(self):
2329 wallet = self.wallet
2330 # wait until we are connected, because the user might have selected another server
2331 if not wallet.interface.is_connected:
2332 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2333 waiting_dialog(waiting)
2335 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2336 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2338 wallet.set_up_to_date(False)
2339 wallet.interface.poke('synchronizer')
2340 waiting_dialog(waiting)
2341 if wallet.is_found():
2342 print_error( "Recovery successful" )
2344 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2351 w = ElectrumWindow(self.wallet, self.config)
2352 if url: w.set_url(url)