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
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.')), 3, 3)
692 self.fee_e = QLineEdit()
693 grid.addWidget(QLabel(_('Fee')), 4, 0)
694 grid.addWidget(self.fee_e, 4, 1, 1, 2)
695 grid.addWidget(HelpButton(
696 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
697 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
698 + _('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)
701 b = EnterButton(_("Send"), self.do_send)
703 b = EnterButton(_("Create unsigned transaction"), self.do_send)
704 grid.addWidget(b, 6, 1)
706 b = EnterButton(_("Clear"),self.do_clear)
707 grid.addWidget(b, 6, 2)
709 self.payto_sig = QLabel('')
710 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
712 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
713 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
722 def entry_changed( is_fee ):
723 self.funds_error = False
724 amount = numbify(self.amount_e)
725 fee = numbify(self.fee_e)
726 if not is_fee: fee = None
729 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
731 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
734 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
735 text = self.status_text
738 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
739 self.funds_error = True
740 text = _( "Not enough funds" )
742 self.statusBar().showMessage(text)
743 self.amount_e.setPalette(palette)
744 self.fee_e.setPalette(palette)
746 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
747 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
749 self.run_hook('create_send_tab', grid)
753 def update_completions(self):
755 for addr,label in self.wallet.labels.items():
756 if addr in self.wallet.addressbook:
757 l.append( label + ' <' + addr + '>')
759 self.run_hook('update_completions', l)
760 self.completions.setStringList(l)
764 return lambda s, *args: s.do_protect(func, args)
768 def do_send(self, password):
770 label = unicode( self.message_e.text() )
771 r = unicode( self.payto_e.text() )
774 # label or alias, with address in brackets
775 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
776 to_address = m.group(2) if m else r
778 if not is_valid(to_address):
779 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
783 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
785 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
788 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
790 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
794 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
795 except BaseException, e:
796 self.show_message(str(e))
799 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
800 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
803 self.run_hook('send_tx', tx)
806 self.set_label(tx.hash(), label)
809 h = self.wallet.send_tx(tx)
810 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
811 status, msg = self.wallet.receive_tx( h )
813 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
815 self.update_contacts_tab()
817 QMessageBox.warning(self, _('Error'), msg, _('OK'))
819 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
821 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
822 with open(fileName,'w') as f:
823 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
824 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
826 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
831 def set_url(self, url):
832 address, amount, label, message, signature, identity, url = util.parse_url(url)
834 if label and self.wallet.labels.get(address) != label:
835 if self.question('Give label "%s" to address %s ?'%(label,address)):
836 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
837 self.wallet.addressbook.append(address)
838 self.set_label(address, label)
840 self.run_hook('set_url', url, self.show_message, self.question)
842 self.tabs.setCurrentIndex(1)
843 label = self.wallet.labels.get(address)
844 m_addr = label + ' <'+ address +'>' if label else address
845 self.payto_e.setText(m_addr)
847 self.message_e.setText(message)
848 self.amount_e.setText(amount)
850 self.set_frozen(self.payto_e,True)
851 self.set_frozen(self.amount_e,True)
852 self.set_frozen(self.message_e,True)
853 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
855 self.payto_sig.setVisible(False)
858 self.payto_sig.setVisible(False)
859 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
861 self.set_frozen(e,False)
863 def set_frozen(self,entry,frozen):
865 entry.setReadOnly(True)
866 entry.setFrame(False)
868 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
869 entry.setPalette(palette)
871 entry.setReadOnly(False)
874 palette.setColor(entry.backgroundRole(), QColor('white'))
875 entry.setPalette(palette)
878 def toggle_freeze(self,addr):
880 if addr in self.wallet.frozen_addresses:
881 self.wallet.unfreeze(addr)
883 self.wallet.freeze(addr)
884 self.update_receive_tab()
886 def toggle_priority(self,addr):
888 if addr in self.wallet.prioritized_addresses:
889 self.wallet.unprioritize(addr)
891 self.wallet.prioritize(addr)
892 self.update_receive_tab()
895 def create_list_tab(self, headers):
896 "generic tab creation method"
897 l = MyTreeWidget(self)
898 l.setColumnCount( len(headers) )
899 l.setHeaderLabels( headers )
909 vbox.addWidget(buttons)
914 buttons.setLayout(hbox)
919 def create_receive_tab(self):
920 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
921 l.setContextMenuPolicy(Qt.CustomContextMenu)
922 l.customContextMenuRequested.connect(self.create_receive_menu)
923 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
924 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
925 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
926 self.receive_list = l
927 self.receive_buttons_hbox = hbox
932 def receive_tab_set_mode(self, i):
933 self.save_column_widths()
934 self.expert_mode = (i == 1)
935 self.config.set_key('classic_expert_mode', self.expert_mode, True)
937 self.update_receive_tab()
940 def save_column_widths(self):
941 if not self.expert_mode:
942 widths = [ self.receive_list.columnWidth(0) ]
945 for i in range(self.receive_list.columnCount() -1):
946 widths.append(self.receive_list.columnWidth(i))
947 self.column_widths["receive"][self.expert_mode] = widths
949 self.column_widths["history"] = []
950 for i in range(self.history_list.columnCount() - 1):
951 self.column_widths["history"].append(self.history_list.columnWidth(i))
953 self.column_widths["contacts"] = []
954 for i in range(self.contacts_list.columnCount() - 1):
955 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
958 def create_contacts_tab(self):
959 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
960 l.setContextMenuPolicy(Qt.CustomContextMenu)
961 l.customContextMenuRequested.connect(self.create_contact_menu)
962 for i,width in enumerate(self.column_widths['contacts']):
963 l.setColumnWidth(i, width)
965 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
966 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
967 self.contacts_list = l
968 self.contacts_buttons_hbox = hbox
969 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
974 def delete_imported_key(self, addr):
975 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
976 self.wallet.imported_keys.pop(addr)
977 self.update_receive_tab()
978 self.update_history_tab()
982 def create_receive_menu(self, position):
983 # fixme: this function apparently has a side effect.
984 # if it is not called the menu pops up several times
985 #self.receive_list.selectedIndexes()
987 item = self.receive_list.itemAt(position)
989 addr = unicode(item.text(0))
990 if not is_valid(addr):
991 item.setExpanded(not item.isExpanded())
994 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
995 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
996 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
997 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
998 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
999 if addr in self.wallet.imported_keys:
1000 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1002 if self.expert_mode:
1003 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1004 menu.addAction(t, lambda: self.toggle_freeze(addr))
1005 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1006 menu.addAction(t, lambda: self.toggle_priority(addr))
1008 self.run_hook('receive_menu', menu)
1009 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1012 def payto(self, addr):
1014 label = self.wallet.labels.get(addr)
1015 m_addr = label + ' <' + addr + '>' if label else addr
1016 self.tabs.setCurrentIndex(1)
1017 self.payto_e.setText(m_addr)
1018 self.amount_e.setFocus()
1021 def delete_contact(self, x):
1022 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1023 if x in self.wallet.addressbook:
1024 self.wallet.addressbook.remove(x)
1025 self.set_label(x, None)
1026 self.update_history_tab()
1027 self.update_contacts_tab()
1028 self.update_completions()
1031 def create_contact_menu(self, position):
1032 item = self.contacts_list.itemAt(position)
1034 addr = unicode(item.text(0))
1035 label = unicode(item.text(1))
1036 is_editable = item.data(0,32).toBool()
1037 payto_addr = item.data(0,33).toString()
1039 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1040 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1041 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1043 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1044 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1046 self.run_hook('create_contact_menu', menu, item)
1047 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1050 def update_receive_item(self, item):
1051 item.setFont(0, QFont(MONOSPACE_FONT))
1052 address = str(item.data(0,0).toString())
1053 label = self.wallet.labels.get(address,'')
1054 item.setData(1,0,label)
1055 item.setData(0,32, True) # is editable
1057 self.run_hook('update_receive_item', address, item)
1059 c, u = self.wallet.get_addr_balance(address)
1060 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1061 item.setData(2,0,balance)
1063 if self.expert_mode:
1064 if address in self.wallet.frozen_addresses:
1065 item.setBackgroundColor(0, QColor('lightblue'))
1066 elif address in self.wallet.prioritized_addresses:
1067 item.setBackgroundColor(0, QColor('lightgreen'))
1070 def update_receive_tab(self):
1071 l = self.receive_list
1074 l.setColumnHidden(2, not self.expert_mode)
1075 l.setColumnHidden(3, not self.expert_mode)
1076 if not self.expert_mode:
1077 width = self.column_widths['receive'][0][0]
1078 l.setColumnWidth(0, width)
1080 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1081 l.setColumnWidth(i, width)
1084 for k, account in self.wallet.accounts.items():
1085 name = account.get('name',str(k))
1086 c,u = self.wallet.get_account_balance(k)
1087 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1088 l.addTopLevelItem(account_item)
1089 account_item.setExpanded(True)
1092 for is_change in [0,1]:
1093 name = "Receiving" if not is_change else "Change"
1094 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1095 account_item.addChild(seq_item)
1096 if not is_change: seq_item.setExpanded(True)
1100 for address in account[is_change]:
1101 h = self.wallet.history.get(address,[])
1105 if gap > self.wallet.gap_limit:
1110 num_tx = '*' if h == ['*'] else "%d"%len(h)
1111 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1112 self.update_receive_item(item)
1114 item.setBackgroundColor(1, QColor('red'))
1115 seq_item.addChild(item)
1117 if self.wallet.imported_keys:
1118 c,u = self.wallet.get_imported_balance()
1119 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1120 l.addTopLevelItem(account_item)
1121 account_item.setExpanded(True)
1122 for address in self.wallet.imported_keys.keys():
1123 item = QTreeWidgetItem( [ address, '', '', ''] )
1124 self.update_receive_item(item)
1125 account_item.addChild(item)
1128 # we use column 1 because column 0 may be hidden
1129 l.setCurrentItem(l.topLevelItem(0),1)
1132 def update_contacts_tab(self):
1134 l = self.contacts_list
1137 for address in self.wallet.addressbook:
1138 label = self.wallet.labels.get(address,'')
1139 n = self.wallet.get_num_tx(address)
1140 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1141 item.setFont(0, QFont(MONOSPACE_FONT))
1142 # 32 = label can be edited (bool)
1143 item.setData(0,32, True)
1145 item.setData(0,33, address)
1146 l.addTopLevelItem(item)
1148 self.run_hook('update_contacts_tab', l)
1149 l.setCurrentItem(l.topLevelItem(0))
1153 def create_console_tab(self):
1154 from qt_console import Console
1155 self.console = console = Console()
1156 self.console.history = self.config.get("console-history",[])
1157 self.console.history_index = len(self.console.history)
1159 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1160 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1162 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1164 def mkfunc(f, method):
1165 return lambda *args: apply( f, (method, args, self.password_dialog ))
1167 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1168 methods[m] = mkfunc(c._run, m)
1170 console.updateNamespace(methods)
1174 def create_status_bar(self):
1175 self.status_text = ""
1177 sb.setFixedHeight(35)
1178 qtVersion = qVersion()
1180 update_notification = UpdateLabel(self.config)
1181 if(update_notification.new_version):
1182 sb.addPermanentWidget(update_notification)
1184 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1185 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1186 if self.wallet.seed:
1187 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1188 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1189 sb.addPermanentWidget( self.password_button )
1190 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1191 if self.wallet.seed:
1192 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1193 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1194 sb.addPermanentWidget( self.status_button )
1196 self.run_hook('create_status_bar', (sb,))
1198 self.setStatusBar(sb)
1202 self.config.set_key('gui', 'lite', True)
1205 self.lite.mini.show()
1207 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1208 self.lite.main(None)
1210 def new_contact_dialog(self):
1211 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1212 address = unicode(text)
1214 if is_valid(address):
1215 self.wallet.addressbook.append(address)
1217 self.update_contacts_tab()
1218 self.update_history_tab()
1219 self.update_completions()
1221 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1223 def show_master_public_key(self):
1224 dialog = QDialog(self)
1226 dialog.setWindowTitle(_("Master Public Key"))
1228 main_text = QTextEdit()
1229 main_text.setText(self.wallet.get_master_public_key())
1230 main_text.setReadOnly(True)
1231 main_text.setMaximumHeight(170)
1232 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1234 ok_button = QPushButton(_("OK"))
1235 ok_button.setDefault(True)
1236 ok_button.clicked.connect(dialog.accept)
1238 main_layout = QGridLayout()
1239 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1241 main_layout.addWidget(main_text, 1, 0)
1242 main_layout.addWidget(qrw, 1, 1 )
1244 vbox = QVBoxLayout()
1245 vbox.addLayout(main_layout)
1246 hbox = QHBoxLayout()
1248 hbox.addWidget(ok_button)
1249 vbox.addLayout(hbox)
1251 dialog.setLayout(vbox)
1256 def show_seed_dialog(self, password):
1257 if not self.wallet.seed:
1258 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1261 seed = self.wallet.decode_seed(password)
1263 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1265 self.show_seed(seed, self)
1269 def show_seed(self, seed, parent=None):
1270 dialog = QDialog(parent)
1272 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1274 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1276 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1278 seed_text = QTextEdit(brainwallet)
1279 seed_text.setReadOnly(True)
1280 seed_text.setMaximumHeight(130)
1282 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1283 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1284 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1285 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1286 label2 = QLabel(msg2)
1287 label2.setWordWrap(True)
1290 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1291 logo.setMaximumWidth(60)
1293 qrw = QRCodeWidget(seed)
1295 ok_button = QPushButton(_("OK"))
1296 ok_button.setDefault(True)
1297 ok_button.clicked.connect(dialog.accept)
1299 grid = QGridLayout()
1300 #main_layout.addWidget(logo, 0, 0)
1302 grid.addWidget(logo, 0, 0)
1303 grid.addWidget(label1, 0, 1)
1305 grid.addWidget(seed_text, 1, 0, 1, 2)
1307 grid.addWidget(qrw, 0, 2, 2, 1)
1309 vbox = QVBoxLayout()
1310 vbox.addLayout(grid)
1311 vbox.addWidget(label2)
1313 hbox = QHBoxLayout()
1315 hbox.addWidget(ok_button)
1316 vbox.addLayout(hbox)
1318 dialog.setLayout(vbox)
1321 def show_qrcode(self, data, title = "QR code"):
1325 d.setWindowTitle(title)
1326 d.setMinimumSize(270, 300)
1327 vbox = QVBoxLayout()
1328 qrw = QRCodeWidget(data)
1329 vbox.addWidget(qrw, 1)
1330 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1331 hbox = QHBoxLayout()
1335 filename = "qrcode.bmp"
1336 bmp.save_qrcode(qrw.qr, filename)
1337 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1339 b = QPushButton(_("Save"))
1341 b.clicked.connect(print_qr)
1343 b = QPushButton(_("Close"))
1345 b.clicked.connect(d.accept)
1348 vbox.addLayout(hbox)
1353 def do_protect(self, func, args):
1354 if self.wallet.use_encryption:
1355 password = self.password_dialog()
1361 if args != (False,):
1362 args = (self,) + args + (password,)
1364 args = (self,password)
1369 def show_private_key(self, address, password):
1370 if not address: return
1372 pk = self.wallet.get_private_key(address, password)
1373 except BaseException, e:
1374 self.show_message(str(e))
1376 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1380 def do_sign(self, address, message, signature, password):
1382 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1383 signature.setText(sig)
1384 except BaseException, e:
1385 self.show_message(str(e))
1387 def sign_message(self, address):
1388 if not address: return
1391 d.setWindowTitle(_('Sign Message'))
1392 d.setMinimumSize(410, 290)
1394 tab_widget = QTabWidget()
1396 layout = QGridLayout(tab)
1398 sign_address = QLineEdit()
1400 sign_address.setText(address)
1401 layout.addWidget(QLabel(_('Address')), 1, 0)
1402 layout.addWidget(sign_address, 1, 1)
1404 sign_message = QTextEdit()
1405 layout.addWidget(QLabel(_('Message')), 2, 0)
1406 layout.addWidget(sign_message, 2, 1)
1407 layout.setRowStretch(2,3)
1409 sign_signature = QTextEdit()
1410 layout.addWidget(QLabel(_('Signature')), 3, 0)
1411 layout.addWidget(sign_signature, 3, 1)
1412 layout.setRowStretch(3,1)
1415 hbox = QHBoxLayout()
1416 b = QPushButton(_("Sign"))
1418 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1419 b = QPushButton(_("Close"))
1420 b.clicked.connect(d.accept)
1422 layout.addLayout(hbox, 4, 1)
1423 tab_widget.addTab(tab, _("Sign"))
1427 layout = QGridLayout(tab)
1429 verify_address = QLineEdit()
1430 layout.addWidget(QLabel(_('Address')), 1, 0)
1431 layout.addWidget(verify_address, 1, 1)
1433 verify_message = QTextEdit()
1434 layout.addWidget(QLabel(_('Message')), 2, 0)
1435 layout.addWidget(verify_message, 2, 1)
1436 layout.setRowStretch(2,3)
1438 verify_signature = QTextEdit()
1439 layout.addWidget(QLabel(_('Signature')), 3, 0)
1440 layout.addWidget(verify_signature, 3, 1)
1441 layout.setRowStretch(3,1)
1445 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1446 self.show_message(_("Signature verified"))
1447 except BaseException, e:
1448 self.show_message(str(e))
1451 hbox = QHBoxLayout()
1452 b = QPushButton(_("Verify"))
1453 b.clicked.connect(do_verify)
1455 b = QPushButton(_("Close"))
1456 b.clicked.connect(d.accept)
1458 layout.addLayout(hbox, 4, 1)
1459 tab_widget.addTab(tab, _("Verify"))
1461 vbox = QVBoxLayout()
1462 vbox.addWidget(tab_widget)
1469 def question(self, msg):
1470 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1472 def show_message(self, msg):
1473 QMessageBox.information(self, _('Message'), msg, _('OK'))
1475 def password_dialog(self ):
1482 vbox = QVBoxLayout()
1483 msg = _('Please enter your password')
1484 vbox.addWidget(QLabel(msg))
1486 grid = QGridLayout()
1488 grid.addWidget(QLabel(_('Password')), 1, 0)
1489 grid.addWidget(pw, 1, 1)
1490 vbox.addLayout(grid)
1492 vbox.addLayout(ok_cancel_buttons(d))
1495 self.run_hook('password_dialog', pw, grid, 1)
1496 if not d.exec_(): return
1497 return unicode(pw.text())
1504 def change_password_dialog( wallet, parent=None ):
1507 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1515 new_pw = QLineEdit()
1516 new_pw.setEchoMode(2)
1517 conf_pw = QLineEdit()
1518 conf_pw.setEchoMode(2)
1520 vbox = QVBoxLayout()
1522 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1523 +_('To disable wallet encryption, enter an empty new password.')) \
1524 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1526 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1527 +_("Leave these fields empty if you want to disable encryption.")
1528 vbox.addWidget(QLabel(msg))
1530 grid = QGridLayout()
1533 if wallet.use_encryption:
1534 grid.addWidget(QLabel(_('Password')), 1, 0)
1535 grid.addWidget(pw, 1, 1)
1537 grid.addWidget(QLabel(_('New Password')), 2, 0)
1538 grid.addWidget(new_pw, 2, 1)
1540 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1541 grid.addWidget(conf_pw, 3, 1)
1542 vbox.addLayout(grid)
1544 vbox.addLayout(ok_cancel_buttons(d))
1547 if not d.exec_(): return
1549 password = unicode(pw.text()) if wallet.use_encryption else None
1550 new_password = unicode(new_pw.text())
1551 new_password2 = unicode(conf_pw.text())
1554 seed = wallet.decode_seed(password)
1556 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1559 if new_password != new_password2:
1560 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1561 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1563 wallet.update_password(seed, password, new_password)
1565 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1566 parent.password_button.setIcon( icon )
1570 def seed_dialog(wallet, parent=None):
1574 vbox = QVBoxLayout()
1575 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
1576 vbox.addWidget(QLabel(msg))
1578 grid = QGridLayout()
1581 seed_e = QLineEdit()
1582 grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
1583 grid.addWidget(seed_e, 1, 1)
1584 grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
1588 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1589 grid.addWidget(gap_e, 2, 1)
1590 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
1591 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1592 vbox.addLayout(grid)
1594 vbox.addLayout(ok_cancel_buttons(d))
1597 if not d.exec_(): return
1600 gap = int(unicode(gap_e.text()))
1602 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1606 seed = str(seed_e.text())
1609 print_error("Warning: Not hex, trying decode")
1611 seed = mnemonic.mn_decode( seed.split(' ') )
1613 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1617 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1622 def generate_transaction_information_widget(self, tx):
1623 tabs = QTabWidget(self)
1626 grid_ui = QGridLayout(tab1)
1627 grid_ui.setColumnStretch(0,1)
1628 tabs.addTab(tab1, _('Outputs') )
1630 tree_widget = MyTreeWidget(self)
1631 tree_widget.setColumnCount(2)
1632 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1633 tree_widget.setColumnWidth(0, 300)
1634 tree_widget.setColumnWidth(1, 50)
1636 for address, value in tx.outputs:
1637 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1638 tree_widget.addTopLevelItem(item)
1640 tree_widget.setMaximumHeight(100)
1642 grid_ui.addWidget(tree_widget)
1645 grid_ui = QGridLayout(tab2)
1646 grid_ui.setColumnStretch(0,1)
1647 tabs.addTab(tab2, _('Inputs') )
1649 tree_widget = MyTreeWidget(self)
1650 tree_widget.setColumnCount(2)
1651 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1653 for input_line in tx.inputs:
1654 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1655 tree_widget.addTopLevelItem(item)
1657 tree_widget.setMaximumHeight(100)
1659 grid_ui.addWidget(tree_widget)
1663 def tx_dict_from_text(self, txt):
1665 tx_dict = json.loads(str(txt))
1666 assert "hex" in tx_dict.keys()
1667 assert "complete" in tx_dict.keys()
1668 if not tx_dict["complete"]:
1669 assert "input_info" in tx_dict.keys()
1671 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1676 def read_tx_from_file(self):
1677 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1681 with open(fileName, "r") as f:
1682 file_content = f.read()
1683 except (ValueError, IOError, os.error), reason:
1684 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1686 return self.tx_dict_from_text(file_content)
1690 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1692 self.wallet.signrawtransaction(tx, input_info, [], password)
1694 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1696 with open(fileName, "w+") as f:
1697 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1698 self.show_message(_("Transaction saved successfully"))
1701 except BaseException, e:
1702 self.show_message(str(e))
1705 def send_raw_transaction(self, raw_tx, dialog = ""):
1706 result, result_message = self.wallet.sendtx( raw_tx )
1708 self.show_message("Transaction successfully sent: %s" % (result_message))
1712 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1714 def do_process_from_text(self):
1715 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1718 tx_dict = self.tx_dict_from_text(text)
1720 self.create_process_transaction_window(tx_dict)
1722 def do_process_from_file(self):
1723 tx_dict = self.read_tx_from_file()
1725 self.create_process_transaction_window(tx_dict)
1727 def create_process_transaction_window(self, tx_dict):
1728 tx = Transaction(tx_dict["hex"])
1730 dialog = QDialog(self)
1731 dialog.setMinimumWidth(500)
1732 dialog.setWindowTitle(_('Process raw transaction'))
1738 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1739 l.addWidget(QLabel(_("Actions")), 4,0)
1741 if tx_dict["complete"] == False:
1742 l.addWidget(QLabel(_("Unsigned")), 3,1)
1743 if self.wallet.seed :
1744 b = QPushButton("Sign transaction")
1745 input_info = json.loads(tx_dict["input_info"])
1746 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1747 l.addWidget(b, 4, 1)
1749 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1751 l.addWidget(QLabel(_("Signed")), 3,1)
1752 b = QPushButton("Broadcast transaction")
1753 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1756 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1757 cancelButton = QPushButton(_("Cancel"))
1758 cancelButton.clicked.connect(lambda: dialog.done(0))
1759 l.addWidget(cancelButton, 4,2)
1765 def do_export_privkeys(self, password):
1766 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.")))
1769 select_export = _('Select file to export your private keys to')
1770 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1772 with open(fileName, "w+") as csvfile:
1773 transaction = csv.writer(csvfile)
1774 transaction.writerow(["address", "private_key"])
1777 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1778 transaction.writerow(["%34s"%addr,pk])
1780 self.show_message(_("Private keys exported."))
1782 except (IOError, os.error), reason:
1783 export_error_label = _("Electrum was unable to produce a private key-export.")
1784 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1786 except BaseException, e:
1787 self.show_message(str(e))
1791 def do_import_labels(self):
1792 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1793 if not labelsFile: return
1795 f = open(labelsFile, 'r')
1798 for key, value in json.loads(data).items():
1799 self.wallet.labels[key] = value
1801 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1802 except (IOError, os.error), reason:
1803 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1806 def do_export_labels(self):
1807 labels = self.wallet.labels
1809 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1811 with open(fileName, 'w+') as f:
1812 json.dump(labels, f)
1813 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1814 except (IOError, os.error), reason:
1815 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1818 def do_export_history(self):
1819 from gui_lite import csv_transaction
1820 csv_transaction(self.wallet)
1824 def do_import_privkey(self, password):
1825 if not self.wallet.imported_keys:
1826 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1827 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1828 + _('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>' \
1829 + _('Are you sure you understand what you are doing?'), 3, 4)
1832 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1835 text = str(text).split()
1840 addr = self.wallet.import_key(key, password)
1841 except BaseException as e:
1847 addrlist.append(addr)
1849 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1851 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1852 self.update_receive_tab()
1853 self.update_history_tab()
1856 def settings_dialog(self):
1858 d.setWindowTitle(_('Electrum Settings'))
1860 vbox = QVBoxLayout()
1862 tabs = QTabWidget(self)
1863 self.settings_tab = tabs
1864 vbox.addWidget(tabs)
1867 grid_ui = QGridLayout(tab1)
1868 grid_ui.setColumnStretch(0,1)
1869 tabs.addTab(tab1, _('Display') )
1871 nz_label = QLabel(_('Display zeros'))
1872 grid_ui.addWidget(nz_label, 0, 0)
1874 nz_e.setText("%d"% self.wallet.num_zeros)
1875 grid_ui.addWidget(nz_e, 0, 1)
1876 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1877 grid_ui.addWidget(HelpButton(msg), 0, 2)
1878 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1879 if not self.config.is_modifiable('num_zeros'):
1880 for w in [nz_e, nz_label]: w.setEnabled(False)
1882 lang_label=QLabel(_('Language') + ':')
1883 grid_ui.addWidget(lang_label, 1, 0)
1884 lang_combo = QComboBox()
1885 from i18n import languages
1886 lang_combo.addItems(languages.values())
1888 index = languages.keys().index(self.config.get("language",''))
1891 lang_combo.setCurrentIndex(index)
1892 grid_ui.addWidget(lang_combo, 1, 1)
1893 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1894 if not self.config.is_modifiable('language'):
1895 for w in [lang_combo, lang_label]: w.setEnabled(False)
1897 currencies = self.exchanger.get_currencies()
1898 currencies.insert(0, "None")
1900 cur_label=QLabel(_('Currency') + ':')
1901 grid_ui.addWidget(cur_label , 2, 0)
1902 cur_combo = QComboBox()
1903 cur_combo.addItems(currencies)
1905 index = currencies.index(self.config.get('currency', "None"))
1908 cur_combo.setCurrentIndex(index)
1909 grid_ui.addWidget(cur_combo, 2, 1)
1910 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1912 view_label=QLabel(_('Receive Tab') + ':')
1913 grid_ui.addWidget(view_label , 3, 0)
1914 view_combo = QComboBox()
1915 view_combo.addItems([_('Simple'), _('Advanced')])
1916 view_combo.setCurrentIndex(self.expert_mode)
1917 grid_ui.addWidget(view_combo, 3, 1)
1918 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1919 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1920 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1922 grid_ui.addWidget(HelpButton(hh), 3, 2)
1923 grid_ui.setRowStretch(4,1)
1927 grid_wallet = QGridLayout(tab2)
1928 grid_wallet.setColumnStretch(0,1)
1929 tabs.addTab(tab2, _('Wallet') )
1931 fee_label = QLabel(_('Transaction fee'))
1932 grid_wallet.addWidget(fee_label, 0, 0)
1934 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1935 grid_wallet.addWidget(fee_e, 0, 2)
1936 msg = _('Fee per kilobyte of transaction.') + ' ' \
1937 + _('Recommended value') + ': 0.0001'
1938 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1939 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1940 if not self.config.is_modifiable('fee_per_kb'):
1941 for w in [fee_e, fee_label]: w.setEnabled(False)
1943 usechange_label = QLabel(_('Use change addresses'))
1944 grid_wallet.addWidget(usechange_label, 1, 0)
1945 usechange_combo = QComboBox()
1946 usechange_combo.addItems([_('Yes'), _('No')])
1947 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1948 grid_wallet.addWidget(usechange_combo, 1, 2)
1949 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1950 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1952 gap_label = QLabel(_('Gap limit'))
1953 grid_wallet.addWidget(gap_label, 2, 0)
1955 gap_e.setText("%d"% self.wallet.gap_limit)
1956 grid_wallet.addWidget(gap_e, 2, 2)
1957 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1958 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1959 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1960 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1961 + _('Warning') + ': ' \
1962 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1963 + _('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'
1964 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1965 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1966 if not self.config.is_modifiable('gap_limit'):
1967 for w in [gap_e, gap_label]: w.setEnabled(False)
1969 grid_wallet.setRowStretch(3,1)
1974 grid_io = QGridLayout(tab3)
1975 grid_io.setColumnStretch(0,1)
1976 tabs.addTab(tab3, _('Import/Export') )
1978 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1979 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1980 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1981 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1983 grid_io.addWidget(QLabel(_('History')), 2, 0)
1984 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1985 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1987 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1989 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1990 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1991 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1993 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1994 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1995 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1996 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1997 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2000 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2001 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2002 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2003 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2005 grid_io.setRowStretch(5,1)
2010 tab5 = QScrollArea()
2011 grid_plugins = QGridLayout(tab5)
2012 grid_plugins.setColumnStretch(0,1)
2013 tabs.addTab(tab5, _('Plugins') )
2014 def mk_toggle(cb, p):
2015 return lambda: cb.setChecked(p.toggle())
2016 for i, p in enumerate(self.plugins):
2018 name, description = p.get_info()
2019 cb = QCheckBox(name)
2020 cb.setDisabled(not p.is_available())
2021 cb.setChecked(p.is_enabled())
2022 cb.clicked.connect(mk_toggle(cb,p))
2023 grid_plugins.addWidget(cb, i, 0)
2024 if p.requires_settings():
2025 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2026 grid_plugins.addWidget(HelpButton(description), i, 2)
2028 print_msg("Error: cannot display plugin", p)
2029 traceback.print_exc(file=sys.stdout)
2030 grid_plugins.setRowStretch(i+1,1)
2032 self.run_hook('create_settings_tab', tabs)
2034 vbox.addLayout(ok_cancel_buttons(d))
2038 if not d.exec_(): return
2040 fee = unicode(fee_e.text())
2042 fee = int( 100000000 * Decimal(fee) )
2044 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2047 if self.wallet.fee != fee:
2048 self.wallet.fee = fee
2051 nz = unicode(nz_e.text())
2056 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2059 if self.wallet.num_zeros != nz:
2060 self.wallet.num_zeros = nz
2061 self.config.set_key('num_zeros', nz, True)
2062 self.update_history_tab()
2063 self.update_receive_tab()
2065 usechange_result = usechange_combo.currentIndex() == 0
2066 if self.wallet.use_change != usechange_result:
2067 self.wallet.use_change = usechange_result
2068 self.config.set_key('use_change', self.wallet.use_change, True)
2071 n = int(gap_e.text())
2073 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2076 if self.wallet.gap_limit != n:
2077 r = self.wallet.change_gap_limit(n)
2079 self.update_receive_tab()
2080 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2082 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2084 need_restart = False
2086 lang_request = languages.keys()[lang_combo.currentIndex()]
2087 if lang_request != self.config.get('language'):
2088 self.config.set_key("language", lang_request, True)
2091 cur_request = str(currencies[cur_combo.currentIndex()])
2092 if cur_request != self.config.get('currency', "None"):
2093 self.config.set_key('currency', cur_request, True)
2094 self.update_wallet()
2096 self.run_hook('close_settings_dialog')
2099 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2101 self.receive_tab_set_mode(view_combo.currentIndex())
2105 def network_dialog(wallet, parent=None):
2106 interface = wallet.interface
2108 if interface.is_connected:
2109 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2111 status = _("Not connected")
2112 server = interface.server
2115 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2116 server = interface.server
2118 plist, servers_list = interface.get_servers_list()
2122 d.setWindowTitle(_('Server'))
2123 d.setMinimumSize(375, 20)
2125 vbox = QVBoxLayout()
2128 hbox = QHBoxLayout()
2130 l.setPixmap(QPixmap(":icons/network.png"))
2133 hbox.addWidget(QLabel(status))
2135 vbox.addLayout(hbox)
2139 grid = QGridLayout()
2141 vbox.addLayout(grid)
2144 server_protocol = QComboBox()
2145 server_host = QLineEdit()
2146 server_host.setFixedWidth(200)
2147 server_port = QLineEdit()
2148 server_port.setFixedWidth(60)
2150 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2151 protocol_letters = 'thsg'
2152 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2153 server_protocol.addItems(protocol_names)
2155 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2156 grid.addWidget(server_protocol, 0, 1)
2157 grid.addWidget(server_host, 0, 2)
2158 grid.addWidget(server_port, 0, 3)
2160 def change_protocol(p):
2161 protocol = protocol_letters[p]
2162 host = unicode(server_host.text())
2163 pp = plist.get(host,DEFAULT_PORTS)
2164 if protocol not in pp.keys():
2165 protocol = pp.keys()[0]
2167 server_host.setText( host )
2168 server_port.setText( port )
2170 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2172 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2173 servers_list_widget = QTreeWidget(parent)
2174 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2175 servers_list_widget.setMaximumHeight(150)
2176 servers_list_widget.setColumnWidth(0, 240)
2177 for _host in servers_list.keys():
2178 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2179 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2181 def change_server(host, protocol=None):
2182 pp = plist.get(host,DEFAULT_PORTS)
2184 port = pp.get(protocol)
2185 if not port: protocol = None
2188 if 't' in pp.keys():
2190 port = pp.get(protocol)
2192 protocol = pp.keys()[0]
2193 port = pp.get(protocol)
2195 server_host.setText( host )
2196 server_port.setText( port )
2197 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2199 if not plist: return
2200 for p in protocol_letters:
2201 i = protocol_letters.index(p)
2202 j = server_protocol.model().index(i,0)
2203 if p not in pp.keys() and interface.is_connected:
2204 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2206 server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2210 host, port, protocol = server.split(':')
2211 change_server(host,protocol)
2213 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2214 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2216 if not wallet.config.is_modifiable('server'):
2217 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2220 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2221 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2222 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2223 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2226 proxy_mode = QComboBox()
2227 proxy_host = QLineEdit()
2228 proxy_host.setFixedWidth(200)
2229 proxy_port = QLineEdit()
2230 proxy_port.setFixedWidth(60)
2231 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2233 def check_for_disable(index = False):
2234 if proxy_mode.currentText() != 'NONE':
2235 proxy_host.setEnabled(True)
2236 proxy_port.setEnabled(True)
2238 proxy_host.setEnabled(False)
2239 proxy_port.setEnabled(False)
2242 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2244 if not wallet.config.is_modifiable('proxy'):
2245 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2247 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2248 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2249 proxy_host.setText(proxy_config.get("host"))
2250 proxy_port.setText(proxy_config.get("port"))
2252 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2253 grid.addWidget(proxy_mode, 2, 1)
2254 grid.addWidget(proxy_host, 2, 2)
2255 grid.addWidget(proxy_port, 2, 3)
2258 vbox.addLayout(ok_cancel_buttons(d))
2261 if not d.exec_(): return
2263 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2264 if proxy_mode.currentText() != 'NONE':
2265 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2269 wallet.config.set_key("proxy", proxy, True)
2270 wallet.config.set_key("server", server, True)
2271 interface.set_server(server, proxy)
2272 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2275 def closeEvent(self, event):
2277 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2278 self.save_column_widths()
2279 self.config.set_key("column_widths", self.column_widths, True)
2280 self.config.set_key("console-history",self.console.history[-50:])
2286 def __init__(self, wallet, config, app=None):
2287 self.wallet = wallet
2288 self.config = config
2290 self.app = QApplication(sys.argv)
2293 def restore_or_create(self):
2294 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2295 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2296 if r==2: return None
2297 return 'restore' if r==1 else 'create'
2299 def seed_dialog(self):
2300 return ElectrumWindow.seed_dialog( self.wallet )
2302 def network_dialog(self):
2303 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2306 def show_seed(self):
2307 ElectrumWindow.show_seed(self.wallet.seed)
2310 def password_dialog(self):
2311 if self.wallet.seed:
2312 ElectrumWindow.change_password_dialog(self.wallet)
2315 def restore_wallet(self):
2316 wallet = self.wallet
2317 # wait until we are connected, because the user might have selected another server
2318 if not wallet.interface.is_connected:
2319 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2320 waiting_dialog(waiting)
2322 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2323 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2325 wallet.set_up_to_date(False)
2326 wallet.interface.poke('synchronizer')
2327 waiting_dialog(waiting)
2328 if wallet.is_found():
2329 print_error( "Recovery successful" )
2331 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2338 w = ElectrumWindow(self.wallet, self.config)
2339 if url: w.set_url(url)