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
39 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41 from electrum.wallet import format_satoshis
42 from electrum.bitcoin import Transaction, is_valid
43 from electrum import mnemonic
44 from electrum import util, bitcoin, commands
46 import bmp, pyqrnative
49 from decimal import Decimal
57 if platform.system() == 'Windows':
58 MONOSPACE_FONT = 'Lucida Console'
59 elif platform.system() == 'Darwin':
60 MONOSPACE_FONT = 'Monaco'
62 MONOSPACE_FONT = 'monospace'
64 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
66 from electrum import ELECTRUM_VERSION
69 class UpdateLabel(QtGui.QLabel):
70 def __init__(self, config, parent=None):
71 QtGui.QLabel.__init__(self, parent)
72 self.new_version = False
75 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
76 con.request("GET", "/version")
77 res = con.getresponse()
78 except socket.error as msg:
79 print_error("Could not retrieve version information")
83 self.latest_version = res.read()
84 self.latest_version = self.latest_version.replace("\n","")
85 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
87 self.current_version = ELECTRUM_VERSION
88 if(self.compare_versions(self.latest_version, self.current_version) == 1):
89 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
90 if(self.compare_versions(self.latest_version, latest_seen) == 1):
91 self.new_version = True
92 self.setText(_("New version available") + ": " + self.latest_version)
95 def compare_versions(self, version1, version2):
97 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
98 return cmp(normalize(version1), normalize(version2))
100 def ignore_this_version(self):
102 self.config.set_key("last_seen_version", self.latest_version, True)
103 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
106 def ignore_all_version(self):
108 self.config.set_key("last_seen_version", "9.9.9", True)
109 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
112 def open_website(self):
113 webbrowser.open("http://electrum.org/download.html")
116 def mouseReleaseEvent(self, event):
117 dialog = QDialog(self)
118 dialog.setWindowTitle(_('Electrum update'))
121 main_layout = QGridLayout()
122 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
124 ignore_version = QPushButton(_("Ignore this version"))
125 ignore_version.clicked.connect(self.ignore_this_version)
127 ignore_all_versions = QPushButton(_("Ignore all versions"))
128 ignore_all_versions.clicked.connect(self.ignore_all_version)
130 open_website = QPushButton(_("Goto download page"))
131 open_website.clicked.connect(self.open_website)
133 main_layout.addWidget(ignore_version, 1, 0)
134 main_layout.addWidget(ignore_all_versions, 1, 1)
135 main_layout.addWidget(open_website, 1, 2)
137 dialog.setLayout(main_layout)
141 if not dialog.exec_(): return
143 def numbify(entry, is_int = False):
144 text = unicode(entry.text()).strip()
145 pos = entry.cursorPosition()
147 if not is_int: chars +='.'
148 s = ''.join([i for i in text if i in chars])
152 s = s.replace('.','')
153 s = s[:p] + '.' + s[p:p+8]
155 amount = int( Decimal(s) * 100000000 )
164 entry.setCursorPosition(pos)
168 class Timer(QtCore.QThread):
171 self.emit(QtCore.SIGNAL('timersignal'))
174 class HelpButton(QPushButton):
175 def __init__(self, text):
176 QPushButton.__init__(self, '?')
177 self.setFocusPolicy(Qt.NoFocus)
178 self.setFixedWidth(20)
179 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
182 class EnterButton(QPushButton):
183 def __init__(self, text, func):
184 QPushButton.__init__(self, text)
186 self.clicked.connect(func)
188 def keyPressEvent(self, e):
189 if e.key() == QtCore.Qt.Key_Return:
192 class MyTreeWidget(QTreeWidget):
193 def __init__(self, parent):
194 QTreeWidget.__init__(self, parent)
197 for i in range(0,self.viewport().height()/5):
198 if self.itemAt(QPoint(0,i*5)) == item:
202 for j in range(0,30):
203 if self.itemAt(QPoint(0,i*5 + j)) != item:
205 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
207 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
212 class StatusBarButton(QPushButton):
213 def __init__(self, icon, tooltip, func):
214 QPushButton.__init__(self, icon, '')
215 self.setToolTip(tooltip)
217 self.setMaximumWidth(25)
218 self.clicked.connect(func)
221 def keyPressEvent(self, e):
222 if e.key() == QtCore.Qt.Key_Return:
230 def waiting_dialog(f):
236 w.setWindowTitle('Electrum')
246 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
251 def ok_cancel_buttons(dialog):
254 b = QPushButton("Cancel")
256 b.clicked.connect(dialog.reject)
257 b = QPushButton("OK")
259 b.clicked.connect(dialog.accept)
264 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
265 "receive":[[370],[370,200,130]] }
267 class ElectrumWindow(QMainWindow):
269 def __init__(self, wallet, config):
270 QMainWindow.__init__(self)
276 self.create_status_bar()
278 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
279 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
280 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
281 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
283 self.expert_mode = config.get('classic_expert_mode', False)
285 set_language(config.get('language'))
287 self.funds_error = False
288 self.completions = QStringListModel()
290 self.tabs = tabs = QTabWidget(self)
291 self.column_widths = self.config.get("column-widths", default_column_widths )
292 tabs.addTab(self.create_history_tab(), _('History') )
293 tabs.addTab(self.create_send_tab(), _('Send') )
294 tabs.addTab(self.create_receive_tab(), _('Receive') )
295 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
296 tabs.addTab(self.create_console_tab(), _('Console') )
297 tabs.setMinimumSize(600, 400)
298 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
299 self.setCentralWidget(tabs)
301 g = self.config.get("winpos-qt",[100, 100, 840, 400])
302 self.setGeometry(g[0], g[1], g[2], g[3])
303 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
304 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
305 self.setWindowTitle( title )
307 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
308 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
309 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
310 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
312 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
313 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
314 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
315 self.history_list.setFocus(True)
317 self.exchanger = exchange_rate.Exchanger(self)
318 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
320 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
321 if platform.system() == 'Windows':
322 n = 3 if self.wallet.seed else 2
323 tabs.setCurrentIndex (n)
324 tabs.setCurrentIndex (0)
326 # set initial message
327 self.console.showMessage(self.wallet.banner)
331 def init_plugins(self):
333 if os.path.exists("plugins"):
334 fp, pathname, description = imp.find_module('plugins')
335 imp.load_module('electrum_plugins', fp, pathname, description)
336 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
337 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
339 import electrum_plugins
340 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
341 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
343 self.plugin_hooks = {}
344 for p in self.plugins:
348 print_msg("Error:cannot initialize plugin",p)
349 traceback.print_exc(file=sys.stdout)
351 def set_hook(self, name, callback):
352 h = self.plugin_hooks.get(name, [])
354 self.plugin_hooks[name] = h
356 def unset_hook(self, name, callback):
357 h = self.plugin_hooks.get(name,[])
358 if callback in h: h.remove(callback)
359 self.plugin_hooks[name] = h
361 def run_hook(self, name, args):
362 for cb in self.plugin_hooks.get(name,[]):
366 def set_label(self, name, text = None):
368 old_text = self.wallet.labels.get(name)
371 self.wallet.labels[name] = text
375 self.wallet.labels.pop(name)
380 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
381 def getOpenFileName(self, title, filter = None):
382 directory = self.config.get('io_dir', os.path.expanduser('~'))
383 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
384 if fileName and directory != os.path.dirname(fileName):
385 self.config.set_key('io_dir', os.path.dirname(fileName), True)
388 def getSaveFileName(self, title, filename, filter = None):
389 directory = self.config.get('io_dir', os.path.expanduser('~'))
390 path = os.path.join( directory, filename )
391 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
392 if fileName and directory != os.path.dirname(fileName):
393 self.config.set_key('io_dir', os.path.dirname(fileName), True)
399 QMainWindow.close(self)
400 self.run_hook('close_main_window', (self,))
402 def connect_slots(self, sender):
403 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
404 self.previous_payto_e=''
406 def timer_actions(self):
407 self.run_hook('timer_actions', (self,))
409 if self.payto_e.hasFocus():
411 r = unicode( self.payto_e.text() )
412 if r != self.previous_payto_e:
413 self.previous_payto_e = r
415 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
417 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
421 s = r + ' <' + to_address + '>'
422 self.payto_e.setText(s)
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 addr = unicode( item.text(column_addr) )
575 label = unicode( item.text(column_label) )
576 if label in self.wallet.aliases.keys():
578 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
579 l.editItem( item, column )
580 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
583 def address_label_changed(self, item, column, l, column_addr, column_label):
584 if column == column_label:
585 addr = unicode( item.text(column_addr) )
586 text = unicode( item.text(column_label) )
589 if text in self.wallet.aliases.keys():
590 print_error("Error: This is one of your aliases")
591 label = self.wallet.labels.get(addr,'')
592 item.setText(column_label, QString(label))
595 changed = self.set_label(addr, text)
597 self.update_history_tab()
598 self.update_completions()
600 self.current_item_changed(item)
602 self.run_hook('item_changed',(self, item, column))
605 def current_item_changed(self, a):
606 self.run_hook('current_item_changed',(self, a))
610 def update_history_tab(self):
612 self.history_list.clear()
613 for item in self.wallet.get_tx_history():
614 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
617 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
623 icon = QIcon(":icons/unconfirmed.png")
625 icon = QIcon(":icons/clock%d.png"%conf)
627 icon = QIcon(":icons/confirmed.png")
630 icon = QIcon(":icons/unconfirmed.png")
632 if value is not None:
633 v_str = format_satoshis(value, True, self.wallet.num_zeros)
637 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
640 label, is_default_label = self.wallet.get_label(tx_hash)
642 label = _('Pruned transaction outputs')
643 is_default_label = False
645 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
646 item.setFont(2, QFont(MONOSPACE_FONT))
647 item.setFont(3, QFont(MONOSPACE_FONT))
648 item.setFont(4, QFont(MONOSPACE_FONT))
650 item.setForeground(3, QBrush(QColor("#BC1E1E")))
652 item.setData(0, Qt.UserRole, tx_hash)
653 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
655 item.setForeground(2, QBrush(QColor('grey')))
657 item.setIcon(0, icon)
658 self.history_list.insertTopLevelItem(0,item)
661 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
664 def create_send_tab(self):
669 grid.setColumnMinimumWidth(3,300)
670 grid.setColumnStretch(5,1)
672 self.payto_e = QLineEdit()
673 grid.addWidget(QLabel(_('Pay to')), 1, 0)
674 grid.addWidget(self.payto_e, 1, 1, 1, 3)
676 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)
678 completer = QCompleter()
679 completer.setCaseSensitivity(False)
680 self.payto_e.setCompleter(completer)
681 completer.setModel(self.completions)
683 self.message_e = QLineEdit()
684 grid.addWidget(QLabel(_('Description')), 2, 0)
685 grid.addWidget(self.message_e, 2, 1, 1, 3)
686 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)
688 self.amount_e = QLineEdit()
689 grid.addWidget(QLabel(_('Amount')), 3, 0)
690 grid.addWidget(self.amount_e, 3, 1, 1, 2)
691 grid.addWidget(HelpButton(
692 _('Amount to be sent.') + '\n\n' \
693 + _('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)
695 self.fee_e = QLineEdit()
696 grid.addWidget(QLabel(_('Fee')), 4, 0)
697 grid.addWidget(self.fee_e, 4, 1, 1, 2)
698 grid.addWidget(HelpButton(
699 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
700 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
701 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
704 b = EnterButton(_("Send"), self.do_send)
706 b = EnterButton(_("Create unsigned transaction"), self.do_send)
707 grid.addWidget(b, 6, 1)
709 b = EnterButton(_("Clear"),self.do_clear)
710 grid.addWidget(b, 6, 2)
712 self.payto_sig = QLabel('')
713 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
715 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
716 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
725 def entry_changed( is_fee ):
726 self.funds_error = False
727 amount = numbify(self.amount_e)
728 fee = numbify(self.fee_e)
729 if not is_fee: fee = None
732 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
734 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
737 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
738 text = self.status_text
741 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
742 self.funds_error = True
743 text = _( "Not enough funds" )
745 self.statusBar().showMessage(text)
746 self.amount_e.setPalette(palette)
747 self.fee_e.setPalette(palette)
749 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
750 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
752 self.run_hook('create_send_tab',(self,grid))
756 def update_completions(self):
758 for addr,label in self.wallet.labels.items():
759 if addr in self.wallet.addressbook:
760 l.append( label + ' <' + addr + '>')
761 l = l + self.wallet.aliases.keys()
763 self.completions.setStringList(l)
767 return lambda s, *args: s.do_protect(func, args)
771 def do_send(self, password):
773 label = unicode( self.message_e.text() )
774 r = unicode( self.payto_e.text() )
778 m1 = re.match(ALIAS_REGEXP, r)
779 # label or alias, with address in brackets
780 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
783 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
787 to_address = m2.group(2)
791 if not is_valid(to_address):
792 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
796 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
798 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
801 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
803 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
807 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
808 except BaseException, e:
809 self.show_message(str(e))
812 self.run_hook('send_tx', (self.wallet, self, tx))
815 self.set_label(tx.hash(), label)
818 h = self.wallet.send_tx(tx)
819 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
820 status, msg = self.wallet.receive_tx( h )
822 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
824 self.update_contacts_tab()
826 QMessageBox.warning(self, _('Error'), msg, _('OK'))
828 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
830 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
831 with open(fileName,'w') as f:
832 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
833 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
835 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
840 def set_url(self, url):
841 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
842 self.tabs.setCurrentIndex(1)
843 label = self.wallet.labels.get(payto)
844 m_addr = label + ' <'+ payto+'>' if label else payto
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', (self, menu,))
1009 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1012 def payto(self, x, is_alias):
1019 label = self.wallet.labels.get(addr)
1020 m_addr = label + ' <' + addr + '>' if label else addr
1021 self.tabs.setCurrentIndex(1)
1022 self.payto_e.setText(m_addr)
1023 self.amount_e.setFocus()
1025 def delete_contact(self, x, is_alias):
1026 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1027 if not is_alias and x in self.wallet.addressbook:
1028 self.wallet.addressbook.remove(x)
1029 self.set_label(x, None)
1030 elif is_alias and x in self.wallet.aliases:
1031 self.wallet.aliases.pop(x)
1032 self.update_history_tab()
1033 self.update_contacts_tab()
1034 self.update_completions()
1036 def create_contact_menu(self, position):
1037 # fixme: this function apparently has a side effect.
1038 # if it is not called the menu pops up several times
1039 #self.contacts_list.selectedIndexes()
1041 item = self.contacts_list.itemAt(position)
1043 addr = unicode(item.text(0))
1044 label = unicode(item.text(1))
1045 is_alias = label in self.wallet.aliases.keys()
1046 x = label if is_alias else addr
1048 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1049 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1050 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1052 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1054 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1055 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1056 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1059 def update_receive_item(self, item):
1060 item.setFont(0, QFont(MONOSPACE_FONT))
1061 address = str(item.data(0,0).toString())
1062 label = self.wallet.labels.get(address,'')
1063 item.setData(1,0,label)
1065 self.run_hook('update_receive_item', (self, address, item))
1067 c, u = self.wallet.get_addr_balance(address)
1068 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1069 item.setData(2,0,balance)
1071 if self.expert_mode:
1072 if address in self.wallet.frozen_addresses:
1073 item.setBackgroundColor(0, QColor('lightblue'))
1074 elif address in self.wallet.prioritized_addresses:
1075 item.setBackgroundColor(0, QColor('lightgreen'))
1078 def update_receive_tab(self):
1079 l = self.receive_list
1082 l.setColumnHidden(2, not self.expert_mode)
1083 l.setColumnHidden(3, not self.expert_mode)
1084 if not self.expert_mode:
1085 width = self.column_widths['receive'][0][0]
1086 l.setColumnWidth(0, width)
1088 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1089 l.setColumnWidth(i, width)
1092 for k, account in self.wallet.accounts.items():
1093 name = account.get('name',str(k))
1094 c,u = self.wallet.get_account_balance(k)
1095 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1096 l.addTopLevelItem(account_item)
1097 account_item.setExpanded(True)
1100 for is_change in [0,1]:
1101 name = "Receiving" if not is_change else "Change"
1102 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1103 account_item.addChild(seq_item)
1104 if not is_change: seq_item.setExpanded(True)
1108 for address in account[is_change]:
1109 h = self.wallet.history.get(address,[])
1114 if gap > self.wallet.gap_limit:
1119 num_tx = '*' if h == ['*'] else "%d"%len(h)
1120 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1121 self.update_receive_item(item)
1123 item.setBackgroundColor(1, QColor('red'))
1124 seq_item.addChild(item)
1126 if self.wallet.imported_keys:
1127 c,u = self.wallet.get_imported_balance()
1128 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1129 l.addTopLevelItem(account_item)
1130 account_item.setExpanded(True)
1131 for address in self.wallet.imported_keys.keys():
1132 item = QTreeWidgetItem( [ address, '', '', ''] )
1133 self.update_receive_item(item)
1134 account_item.addChild(item)
1137 # we use column 1 because column 0 may be hidden
1138 l.setCurrentItem(l.topLevelItem(0),1)
1140 def show_contact_details(self, m):
1141 a = self.wallet.aliases.get(m)
1143 if a[0] in self.wallet.authorities.keys():
1144 s = self.wallet.authorities.get(a[0])
1147 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1148 QMessageBox.information(self, 'Alias', msg, 'OK')
1150 def update_contacts_tab(self):
1152 l = self.contacts_list
1156 for alias, v in self.wallet.aliases.items():
1158 alias_targets.append(target)
1159 item = QTreeWidgetItem( [ target, alias, '-'] )
1160 item.setBackgroundColor(0, QColor('lightgray'))
1161 l.addTopLevelItem(item)
1163 for address in self.wallet.addressbook:
1164 if address in alias_targets: continue
1165 label = self.wallet.labels.get(address,'')
1167 for tx in self.wallet.transactions.values():
1168 if address in map(lambda x: x[0], tx.outputs): n += 1
1170 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1171 item.setFont(0, QFont(MONOSPACE_FONT))
1172 l.addTopLevelItem(item)
1174 l.setCurrentItem(l.topLevelItem(0))
1177 def create_console_tab(self):
1178 from qt_console import Console
1179 self.console = console = Console()
1180 self.console.history = self.config.get("console-history",[])
1181 self.console.history_index = len(self.console.history)
1183 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1184 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1186 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1188 def mkfunc(f, method):
1189 return lambda *args: apply( f, (method, args, self.password_dialog ))
1191 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1192 methods[m] = mkfunc(c._run, m)
1194 console.updateNamespace(methods)
1198 def create_status_bar(self):
1199 self.status_text = ""
1201 sb.setFixedHeight(35)
1202 qtVersion = qVersion()
1204 update_notification = UpdateLabel(self.config)
1205 if(update_notification.new_version):
1206 sb.addPermanentWidget(update_notification)
1208 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1209 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1210 if self.wallet.seed:
1211 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1212 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1213 if self.wallet.seed:
1214 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1215 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1216 sb.addPermanentWidget( self.status_button )
1218 self.run_hook('create_status_bar', (sb,))
1220 self.setStatusBar(sb)
1224 self.config.set_key('gui', 'lite', True)
1227 self.lite.mini.show()
1229 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1230 self.lite.main(None)
1232 def new_contact_dialog(self):
1233 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1234 address = unicode(text)
1236 if is_valid(address):
1237 self.wallet.addressbook.append(address)
1239 self.update_contacts_tab()
1240 self.update_history_tab()
1241 self.update_completions()
1243 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1245 def show_master_public_key(self):
1246 dialog = QDialog(self)
1248 dialog.setWindowTitle(_("Master Public Key"))
1250 main_text = QTextEdit()
1251 main_text.setText(self.wallet.get_master_public_key())
1252 main_text.setReadOnly(True)
1253 main_text.setMaximumHeight(170)
1254 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1256 ok_button = QPushButton(_("OK"))
1257 ok_button.setDefault(True)
1258 ok_button.clicked.connect(dialog.accept)
1260 main_layout = QGridLayout()
1261 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1263 main_layout.addWidget(main_text, 1, 0)
1264 main_layout.addWidget(qrw, 1, 1 )
1266 vbox = QVBoxLayout()
1267 vbox.addLayout(main_layout)
1268 hbox = QHBoxLayout()
1270 hbox.addWidget(ok_button)
1271 vbox.addLayout(hbox)
1273 dialog.setLayout(vbox)
1278 def show_seed_dialog(self, password):
1279 if not self.wallet.seed:
1280 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1283 seed = self.wallet.decode_seed(password)
1285 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1287 self.show_seed(seed, self)
1291 def show_seed(self, seed, parent=None):
1292 dialog = QDialog(parent)
1294 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1296 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1298 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1300 seed_text = QTextEdit(brainwallet)
1301 seed_text.setReadOnly(True)
1302 seed_text.setMaximumHeight(130)
1304 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1305 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1306 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1307 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1308 label2 = QLabel(msg2)
1309 label2.setWordWrap(True)
1312 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1313 logo.setMaximumWidth(60)
1315 qrw = QRCodeWidget(seed)
1317 ok_button = QPushButton(_("OK"))
1318 ok_button.setDefault(True)
1319 ok_button.clicked.connect(dialog.accept)
1321 grid = QGridLayout()
1322 #main_layout.addWidget(logo, 0, 0)
1324 grid.addWidget(logo, 0, 0)
1325 grid.addWidget(label1, 0, 1)
1327 grid.addWidget(seed_text, 1, 0, 1, 2)
1329 grid.addWidget(qrw, 0, 2, 2, 1)
1331 vbox = QVBoxLayout()
1332 vbox.addLayout(grid)
1333 vbox.addWidget(label2)
1335 hbox = QHBoxLayout()
1337 hbox.addWidget(ok_button)
1338 vbox.addLayout(hbox)
1340 dialog.setLayout(vbox)
1343 def show_qrcode(self, data, title = "QR code"):
1347 d.setWindowTitle(title)
1348 d.setMinimumSize(270, 300)
1349 vbox = QVBoxLayout()
1350 qrw = QRCodeWidget(data)
1351 vbox.addWidget(qrw, 1)
1352 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1353 hbox = QHBoxLayout()
1357 filename = "qrcode.bmp"
1358 bmp.save_qrcode(qrw.qr, filename)
1359 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1361 b = QPushButton(_("Save"))
1363 b.clicked.connect(print_qr)
1365 b = QPushButton(_("Close"))
1367 b.clicked.connect(d.accept)
1370 vbox.addLayout(hbox)
1375 def do_protect(self, func, args):
1376 if self.wallet.use_encryption:
1377 password = self.password_dialog()
1383 if args != (False,):
1384 args = (self,) + args + (password,)
1386 args = (self,password)
1391 def show_private_key(self, address, password):
1392 if not address: return
1394 pk = self.wallet.get_private_key(address, password)
1395 except BaseException, e:
1396 self.show_message(str(e))
1398 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1402 def do_sign(self, address, message, signature, password):
1404 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1405 signature.setText(sig)
1406 except BaseException, e:
1407 self.show_message(str(e))
1409 def sign_message(self, address):
1410 if not address: return
1413 d.setWindowTitle(_('Sign Message'))
1414 d.setMinimumSize(410, 290)
1416 tab_widget = QTabWidget()
1418 layout = QGridLayout(tab)
1420 sign_address = QLineEdit()
1422 sign_address.setText(address)
1423 layout.addWidget(QLabel(_('Address')), 1, 0)
1424 layout.addWidget(sign_address, 1, 1)
1426 sign_message = QTextEdit()
1427 layout.addWidget(QLabel(_('Message')), 2, 0)
1428 layout.addWidget(sign_message, 2, 1)
1429 layout.setRowStretch(2,3)
1431 sign_signature = QTextEdit()
1432 layout.addWidget(QLabel(_('Signature')), 3, 0)
1433 layout.addWidget(sign_signature, 3, 1)
1434 layout.setRowStretch(3,1)
1437 hbox = QHBoxLayout()
1438 b = QPushButton(_("Sign"))
1440 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1441 b = QPushButton(_("Close"))
1442 b.clicked.connect(d.accept)
1444 layout.addLayout(hbox, 4, 1)
1445 tab_widget.addTab(tab, _("Sign"))
1449 layout = QGridLayout(tab)
1451 verify_address = QLineEdit()
1452 layout.addWidget(QLabel(_('Address')), 1, 0)
1453 layout.addWidget(verify_address, 1, 1)
1455 verify_message = QTextEdit()
1456 layout.addWidget(QLabel(_('Message')), 2, 0)
1457 layout.addWidget(verify_message, 2, 1)
1458 layout.setRowStretch(2,3)
1460 verify_signature = QTextEdit()
1461 layout.addWidget(QLabel(_('Signature')), 3, 0)
1462 layout.addWidget(verify_signature, 3, 1)
1463 layout.setRowStretch(3,1)
1467 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1468 self.show_message(_("Signature verified"))
1469 except BaseException, e:
1470 self.show_message(str(e))
1473 hbox = QHBoxLayout()
1474 b = QPushButton(_("Verify"))
1475 b.clicked.connect(do_verify)
1477 b = QPushButton(_("Close"))
1478 b.clicked.connect(d.accept)
1480 layout.addLayout(hbox, 4, 1)
1481 tab_widget.addTab(tab, _("Verify"))
1483 vbox = QVBoxLayout()
1484 vbox.addWidget(tab_widget)
1491 def question(self, msg):
1492 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1494 def show_message(self, msg):
1495 QMessageBox.information(self, _('Message'), msg, _('OK'))
1497 def password_dialog(self ):
1504 vbox = QVBoxLayout()
1505 msg = _('Please enter your password')
1506 vbox.addWidget(QLabel(msg))
1508 grid = QGridLayout()
1510 grid.addWidget(QLabel(_('Password')), 1, 0)
1511 grid.addWidget(pw, 1, 1)
1512 vbox.addLayout(grid)
1514 vbox.addLayout(ok_cancel_buttons(d))
1517 if not d.exec_(): return
1518 return unicode(pw.text())
1525 def change_password_dialog( wallet, parent=None ):
1528 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1536 new_pw = QLineEdit()
1537 new_pw.setEchoMode(2)
1538 conf_pw = QLineEdit()
1539 conf_pw.setEchoMode(2)
1541 vbox = QVBoxLayout()
1543 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1544 +_('To disable wallet encryption, enter an empty new password.')) \
1545 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1547 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1548 +_("Leave these fields empty if you want to disable encryption.")
1549 vbox.addWidget(QLabel(msg))
1551 grid = QGridLayout()
1554 if wallet.use_encryption:
1555 grid.addWidget(QLabel(_('Password')), 1, 0)
1556 grid.addWidget(pw, 1, 1)
1558 grid.addWidget(QLabel(_('New Password')), 2, 0)
1559 grid.addWidget(new_pw, 2, 1)
1561 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1562 grid.addWidget(conf_pw, 3, 1)
1563 vbox.addLayout(grid)
1565 vbox.addLayout(ok_cancel_buttons(d))
1568 if not d.exec_(): return
1570 password = unicode(pw.text()) if wallet.use_encryption else None
1571 new_password = unicode(new_pw.text())
1572 new_password2 = unicode(conf_pw.text())
1575 seed = wallet.decode_seed(password)
1577 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1580 if new_password != new_password2:
1581 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1582 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1584 wallet.update_password(seed, password, new_password)
1587 def seed_dialog(wallet, parent=None):
1591 vbox = QVBoxLayout()
1592 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1593 vbox.addWidget(QLabel(msg))
1595 grid = QGridLayout()
1598 seed_e = QLineEdit()
1599 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1600 grid.addWidget(seed_e, 1, 1)
1604 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1605 grid.addWidget(gap_e, 2, 1)
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 dialog = QDialog(self)
1731 dialog.setMinimumWidth(500)
1732 dialog.setWindowTitle(_('Input raw transaction'))
1736 l.addWidget(QLabel(_("Transaction:")))
1740 ok_button = QPushButton(_("Load transaction"))
1741 ok_button.setDefault(True)
1742 ok_button.clicked.connect(dialog.accept)
1743 l.addWidget(ok_button)
1746 tx_dict = self.tx_dict_from_text(unicode(txt.toPlainText()))
1748 self.create_process_transaction_window(tx_dict)
1750 def do_process_from_file(self):
1751 tx_dict = self.read_tx_from_file()
1753 self.create_process_transaction_window(tx_dict)
1755 def create_process_transaction_window(self, tx_dict):
1756 tx = Transaction(tx_dict["hex"])
1758 dialog = QDialog(self)
1759 dialog.setMinimumWidth(500)
1760 dialog.setWindowTitle(_('Process raw transaction'))
1766 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1767 l.addWidget(QLabel(_("Actions")), 4,0)
1769 if tx_dict["complete"] == False:
1770 l.addWidget(QLabel(_("Unsigned")), 3,1)
1771 if self.wallet.seed :
1772 b = QPushButton("Sign transaction")
1773 input_info = json.loads(tx_dict["input_info"])
1774 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1775 l.addWidget(b, 4, 1)
1777 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1779 l.addWidget(QLabel(_("Signed")), 3,1)
1780 b = QPushButton("Broadcast transaction")
1781 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1784 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1785 cancelButton = QPushButton(_("Cancel"))
1786 cancelButton.clicked.connect(lambda: dialog.done(0))
1787 l.addWidget(cancelButton, 4,2)
1793 def do_export_privkeys(self, password):
1794 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.")))
1797 select_export = _('Select file to export your private keys to')
1798 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1800 with open(fileName, "w+") as csvfile:
1801 transaction = csv.writer(csvfile)
1802 transaction.writerow(["address", "private_key"])
1805 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1806 transaction.writerow(["%34s"%addr,pk])
1808 self.show_message(_("Private keys exported."))
1810 except (IOError, os.error), reason:
1811 export_error_label = _("Electrum was unable to produce a private key-export.")
1812 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1814 except BaseException, e:
1815 self.show_message(str(e))
1819 def do_import_labels(self):
1820 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1821 if not labelsFile: return
1823 f = open(labelsFile, 'r')
1826 for key, value in json.loads(data).items():
1827 self.wallet.labels[key] = value
1829 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1830 except (IOError, os.error), reason:
1831 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1834 def do_export_labels(self):
1835 labels = self.wallet.labels
1837 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1839 with open(fileName, 'w+') as f:
1840 json.dump(labels, f)
1841 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1842 except (IOError, os.error), reason:
1843 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1846 def do_export_history(self):
1847 from gui_lite import csv_transaction
1848 csv_transaction(self.wallet)
1852 def do_import_privkey(self, password):
1853 if not self.wallet.imported_keys:
1854 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1855 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1856 + _('Are you sure you understand what you are doing?'), 3, 4)
1859 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1861 sec = str(text).strip()
1863 addr = self.wallet.import_key(sec, password)
1865 QMessageBox.critical(None, _("Unable to import key"), "error")
1867 QMessageBox.information(None, _("Key imported"), addr)
1868 self.update_receive_tab()
1869 self.update_history_tab()
1870 except BaseException as e:
1871 QMessageBox.critical(None, _("Unable to import key"), str(e))
1874 def settings_dialog(self):
1876 d.setWindowTitle(_('Electrum Settings'))
1878 vbox = QVBoxLayout()
1880 tabs = QTabWidget(self)
1881 self.settings_tab = tabs
1882 vbox.addWidget(tabs)
1885 grid_ui = QGridLayout(tab1)
1886 grid_ui.setColumnStretch(0,1)
1887 tabs.addTab(tab1, _('Display') )
1889 nz_label = QLabel(_('Display zeros'))
1890 grid_ui.addWidget(nz_label, 0, 0)
1892 nz_e.setText("%d"% self.wallet.num_zeros)
1893 grid_ui.addWidget(nz_e, 0, 1)
1894 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1895 grid_ui.addWidget(HelpButton(msg), 0, 2)
1896 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1897 if not self.config.is_modifiable('num_zeros'):
1898 for w in [nz_e, nz_label]: w.setEnabled(False)
1900 lang_label=QLabel(_('Language') + ':')
1901 grid_ui.addWidget(lang_label, 1, 0)
1902 lang_combo = QComboBox()
1903 from i18n import languages
1904 lang_combo.addItems(languages.values())
1906 index = languages.keys().index(self.config.get("language",''))
1909 lang_combo.setCurrentIndex(index)
1910 grid_ui.addWidget(lang_combo, 1, 1)
1911 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1912 if not self.config.is_modifiable('language'):
1913 for w in [lang_combo, lang_label]: w.setEnabled(False)
1915 currencies = self.exchanger.get_currencies()
1916 currencies.insert(0, "None")
1918 cur_label=QLabel(_('Currency') + ':')
1919 grid_ui.addWidget(cur_label , 2, 0)
1920 cur_combo = QComboBox()
1921 cur_combo.addItems(currencies)
1923 index = currencies.index(self.config.get('currency', "None"))
1926 cur_combo.setCurrentIndex(index)
1927 grid_ui.addWidget(cur_combo, 2, 1)
1928 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1930 view_label=QLabel(_('Receive Tab') + ':')
1931 grid_ui.addWidget(view_label , 3, 0)
1932 view_combo = QComboBox()
1933 view_combo.addItems([_('Simple'), _('Advanced')])
1934 view_combo.setCurrentIndex(self.expert_mode)
1935 grid_ui.addWidget(view_combo, 3, 1)
1936 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1937 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1938 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1940 grid_ui.addWidget(HelpButton(hh), 3, 2)
1941 grid_ui.setRowStretch(4,1)
1945 grid_wallet = QGridLayout(tab2)
1946 grid_wallet.setColumnStretch(0,1)
1947 tabs.addTab(tab2, _('Wallet') )
1949 fee_label = QLabel(_('Transaction fee'))
1950 grid_wallet.addWidget(fee_label, 0, 0)
1952 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1953 grid_wallet.addWidget(fee_e, 0, 2)
1954 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1955 + _('Recommended value') + ': 0.001'
1956 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1957 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1958 if not self.config.is_modifiable('fee'):
1959 for w in [fee_e, fee_label]: w.setEnabled(False)
1961 usechange_label = QLabel(_('Use change addresses'))
1962 grid_wallet.addWidget(usechange_label, 1, 0)
1963 usechange_combo = QComboBox()
1964 usechange_combo.addItems([_('Yes'), _('No')])
1965 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1966 grid_wallet.addWidget(usechange_combo, 1, 2)
1967 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1968 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1970 gap_label = QLabel(_('Gap limit'))
1971 grid_wallet.addWidget(gap_label, 2, 0)
1973 gap_e.setText("%d"% self.wallet.gap_limit)
1974 grid_wallet.addWidget(gap_e, 2, 2)
1975 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1976 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1977 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1978 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1979 + _('Warning') + ': ' \
1980 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1981 + _('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'
1982 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1983 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1984 if not self.config.is_modifiable('gap_limit'):
1985 for w in [gap_e, gap_label]: w.setEnabled(False)
1987 grid_wallet.setRowStretch(3,1)
1992 grid_io = QGridLayout(tab3)
1993 grid_io.setColumnStretch(0,1)
1994 tabs.addTab(tab3, _('Import/Export') )
1996 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1997 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1998 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1999 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2001 grid_io.addWidget(QLabel(_('History')), 2, 0)
2002 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2003 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2005 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2007 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2008 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2009 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2011 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2012 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2013 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2014 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2015 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2018 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2019 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2020 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2021 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2023 grid_io.setRowStretch(5,1)
2028 tab5 = QScrollArea()
2029 grid_plugins = QGridLayout(tab5)
2030 grid_plugins.setColumnStretch(0,1)
2031 tabs.addTab(tab5, _('Plugins') )
2032 def mk_toggle(cb, p):
2033 return lambda: cb.setChecked(p.toggle(self))
2034 for i, p in enumerate(self.plugins):
2036 name, description = p.get_info()
2037 cb = QCheckBox(name)
2038 cb.setDisabled(not p.is_available())
2039 cb.setChecked(p.is_enabled())
2040 cb.clicked.connect(mk_toggle(cb,p))
2041 grid_plugins.addWidget(cb, i, 0)
2042 grid_plugins.addWidget(HelpButton(description), i, 2)
2044 print_msg("Error: cannot display plugin", p)
2045 traceback.print_exc(file=sys.stdout)
2046 grid_plugins.setRowStretch(i+1,1)
2048 self.run_hook('create_settings_tab', (self,tabs,))
2050 vbox.addLayout(ok_cancel_buttons(d))
2054 if not d.exec_(): return
2056 fee = unicode(fee_e.text())
2058 fee = int( 100000000 * Decimal(fee) )
2060 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2063 if self.wallet.fee != fee:
2064 self.wallet.fee = fee
2067 nz = unicode(nz_e.text())
2072 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2075 if self.wallet.num_zeros != nz:
2076 self.wallet.num_zeros = nz
2077 self.config.set_key('num_zeros', nz, True)
2078 self.update_history_tab()
2079 self.update_receive_tab()
2081 usechange_result = usechange_combo.currentIndex() == 0
2082 if self.wallet.use_change != usechange_result:
2083 self.wallet.use_change = usechange_result
2084 self.config.set_key('use_change', self.wallet.use_change, True)
2087 n = int(gap_e.text())
2089 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2092 if self.wallet.gap_limit != n:
2093 r = self.wallet.change_gap_limit(n)
2095 self.update_receive_tab()
2096 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2098 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2100 need_restart = False
2102 lang_request = languages.keys()[lang_combo.currentIndex()]
2103 if lang_request != self.config.get('language'):
2104 self.config.set_key("language", lang_request, True)
2107 cur_request = str(currencies[cur_combo.currentIndex()])
2108 if cur_request != self.config.get('currency', "None"):
2109 self.config.set_key('currency', cur_request, True)
2110 self.update_wallet()
2112 self.run_hook('close_setting_dialog', (self,))
2115 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2117 self.receive_tab_set_mode(view_combo.currentIndex())
2121 def network_dialog(wallet, parent=None):
2122 interface = wallet.interface
2124 if interface.is_connected:
2125 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2127 status = _("Not connected")
2128 server = interface.server
2131 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2132 server = interface.server
2134 plist, servers_list = interface.get_servers_list()
2138 d.setWindowTitle(_('Server'))
2139 d.setMinimumSize(375, 20)
2141 vbox = QVBoxLayout()
2144 hbox = QHBoxLayout()
2146 l.setPixmap(QPixmap(":icons/network.png"))
2149 hbox.addWidget(QLabel(status))
2151 vbox.addLayout(hbox)
2155 grid = QGridLayout()
2157 vbox.addLayout(grid)
2160 server_protocol = QComboBox()
2161 server_host = QLineEdit()
2162 server_host.setFixedWidth(200)
2163 server_port = QLineEdit()
2164 server_port.setFixedWidth(60)
2166 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2167 protocol_letters = 'thsg'
2168 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2169 server_protocol.addItems(protocol_names)
2171 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2172 grid.addWidget(server_protocol, 0, 1)
2173 grid.addWidget(server_host, 0, 2)
2174 grid.addWidget(server_port, 0, 3)
2176 def change_protocol(p):
2177 protocol = protocol_letters[p]
2178 host = unicode(server_host.text())
2179 pp = plist.get(host,DEFAULT_PORTS)
2180 if protocol not in pp.keys():
2181 protocol = pp.keys()[0]
2183 server_host.setText( host )
2184 server_port.setText( port )
2186 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2188 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2189 servers_list_widget = QTreeWidget(parent)
2190 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2191 servers_list_widget.setMaximumHeight(150)
2192 servers_list_widget.setColumnWidth(0, 240)
2193 for _host in servers_list.keys():
2194 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2195 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2197 def change_server(host, protocol=None):
2198 pp = plist.get(host,DEFAULT_PORTS)
2200 port = pp.get(protocol)
2201 if not port: protocol = None
2204 if 't' in pp.keys():
2206 port = pp.get(protocol)
2208 protocol = pp.keys()[0]
2209 port = pp.get(protocol)
2211 server_host.setText( host )
2212 server_port.setText( port )
2213 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2215 if not plist: return
2216 for p in protocol_letters:
2217 i = protocol_letters.index(p)
2218 j = server_protocol.model().index(i,0)
2219 if p not in pp.keys():
2220 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2222 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2226 host, port, protocol = server.split(':')
2227 change_server(host,protocol)
2229 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2230 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2232 if not wallet.config.is_modifiable('server'):
2233 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2236 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2237 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2238 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2239 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2242 proxy_mode = QComboBox()
2243 proxy_host = QLineEdit()
2244 proxy_host.setFixedWidth(200)
2245 proxy_port = QLineEdit()
2246 proxy_port.setFixedWidth(60)
2247 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2249 def check_for_disable(index = False):
2250 if proxy_mode.currentText() != 'NONE':
2251 proxy_host.setEnabled(True)
2252 proxy_port.setEnabled(True)
2254 proxy_host.setEnabled(False)
2255 proxy_port.setEnabled(False)
2258 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2260 if not wallet.config.is_modifiable('proxy'):
2261 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2263 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2264 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2265 proxy_host.setText(proxy_config.get("host"))
2266 proxy_port.setText(proxy_config.get("port"))
2268 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2269 grid.addWidget(proxy_mode, 2, 1)
2270 grid.addWidget(proxy_host, 2, 2)
2271 grid.addWidget(proxy_port, 2, 3)
2274 vbox.addLayout(ok_cancel_buttons(d))
2277 if not d.exec_(): return
2279 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2280 if proxy_mode.currentText() != 'NONE':
2281 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2285 wallet.config.set_key("proxy", proxy, True)
2286 wallet.config.set_key("server", server, True)
2287 interface.set_server(server, proxy)
2288 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2291 def closeEvent(self, event):
2293 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2294 self.save_column_widths()
2295 self.config.set_key("column-widths", self.column_widths, True)
2296 self.config.set_key("console-history",self.console.history[-50:])
2302 def __init__(self, wallet, config, app=None):
2303 self.wallet = wallet
2304 self.config = config
2306 self.app = QApplication(sys.argv)
2309 def restore_or_create(self):
2310 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2311 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2312 if r==2: return None
2313 return 'restore' if r==1 else 'create'
2315 def seed_dialog(self):
2316 return ElectrumWindow.seed_dialog( self.wallet )
2318 def network_dialog(self):
2319 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2322 def show_seed(self):
2323 ElectrumWindow.show_seed(self.wallet.seed)
2326 def password_dialog(self):
2327 if self.wallet.seed:
2328 ElectrumWindow.change_password_dialog(self.wallet)
2331 def restore_wallet(self):
2332 wallet = self.wallet
2333 # wait until we are connected, because the user might have selected another server
2334 if not wallet.interface.is_connected:
2335 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2336 waiting_dialog(waiting)
2338 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2339 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2341 wallet.set_up_to_date(False)
2342 wallet.interface.poke('synchronizer')
2343 waiting_dialog(waiting)
2344 if wallet.is_found():
2345 print_error( "Recovery successful" )
2347 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2354 w = ElectrumWindow(self.wallet, self.config)
2355 if url: w.set_url(url)