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)
378 self.run_hook('set_label', (name, text, changed,))
382 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
383 def getOpenFileName(self, title, filter = None):
384 directory = self.config.get('io_dir', os.path.expanduser('~'))
385 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
386 if fileName and directory != os.path.dirname(fileName):
387 self.config.set_key('io_dir', os.path.dirname(fileName), True)
390 def getSaveFileName(self, title, filename, filter = None):
391 directory = self.config.get('io_dir', os.path.expanduser('~'))
392 path = os.path.join( directory, filename )
393 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
394 if fileName and directory != os.path.dirname(fileName):
395 self.config.set_key('io_dir', os.path.dirname(fileName), True)
401 QMainWindow.close(self)
402 self.run_hook('close_main_window', (self,))
404 def connect_slots(self, sender):
405 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
406 self.previous_payto_e=''
408 def timer_actions(self):
409 self.run_hook('timer_actions', (self,))
411 if self.payto_e.hasFocus():
413 r = unicode( self.payto_e.text() )
414 if r != self.previous_payto_e:
415 self.previous_payto_e = r
417 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
419 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
423 s = r + ' <' + to_address + '>'
424 self.payto_e.setText(s)
428 def update_status(self):
429 if self.wallet.interface and self.wallet.interface.is_connected:
430 if not self.wallet.up_to_date:
431 text = _("Synchronizing...")
432 icon = QIcon(":icons/status_waiting.png")
434 c, u = self.wallet.get_balance()
435 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
436 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
437 text += self.create_quote_text(Decimal(c+u)/100000000)
438 icon = QIcon(":icons/status_connected.png")
440 text = _("Not connected")
441 icon = QIcon(":icons/status_disconnected.png")
443 self.status_text = text
444 self.statusBar().showMessage(text)
445 self.status_button.setIcon( icon )
447 def update_wallet(self):
449 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
450 self.update_history_tab()
451 self.update_receive_tab()
452 self.update_contacts_tab()
453 self.update_completions()
456 def create_quote_text(self, btc_balance):
457 quote_currency = self.config.get("currency", "None")
458 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
459 if quote_balance is None:
462 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
465 def create_history_tab(self):
466 self.history_list = l = MyTreeWidget(self)
468 for i,width in enumerate(self.column_widths['history']):
469 l.setColumnWidth(i, width)
470 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
471 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
472 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
474 l.setContextMenuPolicy(Qt.CustomContextMenu)
475 l.customContextMenuRequested.connect(self.create_history_menu)
479 def create_history_menu(self, position):
480 self.history_list.selectedIndexes()
481 item = self.history_list.currentItem()
483 tx_hash = str(item.data(0, Qt.UserRole).toString())
484 if not tx_hash: return
486 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
487 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
488 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
489 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
492 def show_tx_details(self, tx):
493 dialog = QDialog(self)
495 dialog.setWindowTitle(_("Transaction Details"))
497 dialog.setLayout(vbox)
498 dialog.setMinimumSize(600,300)
501 if tx_hash in self.wallet.transactions.keys():
502 is_mine, v, fee = self.wallet.get_tx_value(tx)
503 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
505 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
511 vbox.addWidget(QLabel("Transaction ID:"))
512 e = QLineEdit(tx_hash)
516 vbox.addWidget(QLabel("Date: %s"%time_str))
517 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
520 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
521 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
523 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
524 vbox.addWidget(QLabel("Transaction fee: unknown"))
526 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
528 vbox.addWidget( self.generate_transaction_information_widget(tx) )
530 ok_button = QPushButton(_("Close"))
531 ok_button.setDefault(True)
532 ok_button.clicked.connect(dialog.accept)
536 hbox.addWidget(ok_button)
540 def tx_label_clicked(self, item, column):
541 if column==2 and item.isSelected():
543 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
544 self.history_list.editItem( item, column )
545 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
548 def tx_label_changed(self, item, column):
552 tx_hash = str(item.data(0, Qt.UserRole).toString())
553 tx = self.wallet.transactions.get(tx_hash)
554 text = unicode( item.text(2) )
555 self.set_label(tx_hash, text)
557 item.setForeground(2, QBrush(QColor('black')))
559 text = self.wallet.get_default_label(tx_hash)
560 item.setText(2, text)
561 item.setForeground(2, QBrush(QColor('gray')))
565 def edit_label(self, is_recv):
566 l = self.receive_list if is_recv else self.contacts_list
567 item = l.currentItem()
568 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
569 l.editItem( item, 1 )
570 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
574 def address_label_clicked(self, item, column, l, column_addr, column_label):
575 if column == column_label and item.isSelected():
576 addr = unicode( item.text(column_addr) )
577 label = unicode( item.text(column_label) )
578 if label in self.wallet.aliases.keys():
580 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
581 l.editItem( item, column )
582 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
585 def address_label_changed(self, item, column, l, column_addr, column_label):
586 if column == column_label:
587 addr = unicode( item.text(column_addr) )
588 text = unicode( item.text(column_label) )
591 if text in self.wallet.aliases.keys():
592 print_error("Error: This is one of your aliases")
593 label = self.wallet.labels.get(addr,'')
594 item.setText(column_label, QString(label))
597 changed = self.set_label(addr, text)
599 self.update_history_tab()
600 self.update_completions()
602 self.current_item_changed(item)
604 self.run_hook('item_changed',(self, item, column))
607 def current_item_changed(self, a):
608 self.run_hook('current_item_changed',(self, a))
612 def update_history_tab(self):
614 self.history_list.clear()
615 for item in self.wallet.get_tx_history():
616 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
619 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
625 icon = QIcon(":icons/unconfirmed.png")
627 icon = QIcon(":icons/clock%d.png"%conf)
629 icon = QIcon(":icons/confirmed.png")
632 icon = QIcon(":icons/unconfirmed.png")
634 if value is not None:
635 v_str = format_satoshis(value, True, self.wallet.num_zeros)
639 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
642 label, is_default_label = self.wallet.get_label(tx_hash)
644 label = _('Pruned transaction outputs')
645 is_default_label = False
647 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
648 item.setFont(2, QFont(MONOSPACE_FONT))
649 item.setFont(3, QFont(MONOSPACE_FONT))
650 item.setFont(4, QFont(MONOSPACE_FONT))
652 item.setForeground(3, QBrush(QColor("#BC1E1E")))
654 item.setData(0, Qt.UserRole, tx_hash)
655 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
657 item.setForeground(2, QBrush(QColor('grey')))
659 item.setIcon(0, icon)
660 self.history_list.insertTopLevelItem(0,item)
663 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
666 def create_send_tab(self):
671 grid.setColumnMinimumWidth(3,300)
672 grid.setColumnStretch(5,1)
674 self.payto_e = QLineEdit()
675 grid.addWidget(QLabel(_('Pay to')), 1, 0)
676 grid.addWidget(self.payto_e, 1, 1, 1, 3)
678 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)
680 completer = QCompleter()
681 completer.setCaseSensitivity(False)
682 self.payto_e.setCompleter(completer)
683 completer.setModel(self.completions)
685 self.message_e = QLineEdit()
686 grid.addWidget(QLabel(_('Description')), 2, 0)
687 grid.addWidget(self.message_e, 2, 1, 1, 3)
688 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)
690 self.amount_e = QLineEdit()
691 grid.addWidget(QLabel(_('Amount')), 3, 0)
692 grid.addWidget(self.amount_e, 3, 1, 1, 2)
693 grid.addWidget(HelpButton(
694 _('Amount to be sent.') + '\n\n' \
695 + _('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)
697 self.fee_e = QLineEdit()
698 grid.addWidget(QLabel(_('Fee')), 4, 0)
699 grid.addWidget(self.fee_e, 4, 1, 1, 2)
700 grid.addWidget(HelpButton(
701 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
702 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
703 + _('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)
706 b = EnterButton(_("Send"), self.do_send)
708 b = EnterButton(_("Create unsigned transaction"), self.do_send)
709 grid.addWidget(b, 6, 1)
711 b = EnterButton(_("Clear"),self.do_clear)
712 grid.addWidget(b, 6, 2)
714 self.payto_sig = QLabel('')
715 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
717 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
718 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
727 def entry_changed( is_fee ):
728 self.funds_error = False
729 amount = numbify(self.amount_e)
730 fee = numbify(self.fee_e)
731 if not is_fee: fee = None
734 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
736 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
739 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
740 text = self.status_text
743 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
744 self.funds_error = True
745 text = _( "Not enough funds" )
747 self.statusBar().showMessage(text)
748 self.amount_e.setPalette(palette)
749 self.fee_e.setPalette(palette)
751 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
752 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
754 self.run_hook('create_send_tab',(self,grid))
758 def update_completions(self):
760 for addr,label in self.wallet.labels.items():
761 if addr in self.wallet.addressbook:
762 l.append( label + ' <' + addr + '>')
763 l = l + self.wallet.aliases.keys()
765 self.completions.setStringList(l)
769 return lambda s, *args: s.do_protect(func, args)
773 def do_send(self, password):
775 label = unicode( self.message_e.text() )
776 r = unicode( self.payto_e.text() )
780 m1 = re.match(ALIAS_REGEXP, r)
781 # label or alias, with address in brackets
782 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
785 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
789 to_address = m2.group(2)
793 if not is_valid(to_address):
794 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
798 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
800 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
803 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
805 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
809 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
810 except BaseException, e:
811 self.show_message(str(e))
814 self.run_hook('send_tx', (self.wallet, self, tx))
817 self.set_label(tx.hash(), label)
820 h = self.wallet.send_tx(tx)
821 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
822 status, msg = self.wallet.receive_tx( h )
824 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
826 self.update_contacts_tab()
828 QMessageBox.warning(self, _('Error'), msg, _('OK'))
830 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
832 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
833 with open(fileName,'w') as f:
834 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
835 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
837 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
842 def set_url(self, url):
843 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
844 self.tabs.setCurrentIndex(1)
845 label = self.wallet.labels.get(payto)
846 m_addr = label + ' <'+ payto+'>' if label else payto
847 self.payto_e.setText(m_addr)
849 self.message_e.setText(message)
850 self.amount_e.setText(amount)
852 self.set_frozen(self.payto_e,True)
853 self.set_frozen(self.amount_e,True)
854 self.set_frozen(self.message_e,True)
855 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
857 self.payto_sig.setVisible(False)
860 self.payto_sig.setVisible(False)
861 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
863 self.set_frozen(e,False)
865 def set_frozen(self,entry,frozen):
867 entry.setReadOnly(True)
868 entry.setFrame(False)
870 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
871 entry.setPalette(palette)
873 entry.setReadOnly(False)
876 palette.setColor(entry.backgroundRole(), QColor('white'))
877 entry.setPalette(palette)
880 def toggle_freeze(self,addr):
882 if addr in self.wallet.frozen_addresses:
883 self.wallet.unfreeze(addr)
885 self.wallet.freeze(addr)
886 self.update_receive_tab()
888 def toggle_priority(self,addr):
890 if addr in self.wallet.prioritized_addresses:
891 self.wallet.unprioritize(addr)
893 self.wallet.prioritize(addr)
894 self.update_receive_tab()
897 def create_list_tab(self, headers):
898 "generic tab creation method"
899 l = MyTreeWidget(self)
900 l.setColumnCount( len(headers) )
901 l.setHeaderLabels( headers )
911 vbox.addWidget(buttons)
916 buttons.setLayout(hbox)
921 def create_receive_tab(self):
922 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
923 l.setContextMenuPolicy(Qt.CustomContextMenu)
924 l.customContextMenuRequested.connect(self.create_receive_menu)
925 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
926 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
927 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
928 self.receive_list = l
929 self.receive_buttons_hbox = hbox
934 def receive_tab_set_mode(self, i):
935 self.save_column_widths()
936 self.expert_mode = (i == 1)
937 self.config.set_key('classic_expert_mode', self.expert_mode, True)
939 self.update_receive_tab()
942 def save_column_widths(self):
943 if not self.expert_mode:
944 widths = [ self.receive_list.columnWidth(0) ]
947 for i in range(self.receive_list.columnCount() -1):
948 widths.append(self.receive_list.columnWidth(i))
949 self.column_widths["receive"][self.expert_mode] = widths
951 self.column_widths["history"] = []
952 for i in range(self.history_list.columnCount() - 1):
953 self.column_widths["history"].append(self.history_list.columnWidth(i))
955 self.column_widths["contacts"] = []
956 for i in range(self.contacts_list.columnCount() - 1):
957 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
960 def create_contacts_tab(self):
961 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
962 l.setContextMenuPolicy(Qt.CustomContextMenu)
963 l.customContextMenuRequested.connect(self.create_contact_menu)
964 for i,width in enumerate(self.column_widths['contacts']):
965 l.setColumnWidth(i, width)
967 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
968 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
969 self.contacts_list = l
970 self.contacts_buttons_hbox = hbox
971 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
976 def delete_imported_key(self, addr):
977 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
978 self.wallet.imported_keys.pop(addr)
979 self.update_receive_tab()
980 self.update_history_tab()
984 def create_receive_menu(self, position):
985 # fixme: this function apparently has a side effect.
986 # if it is not called the menu pops up several times
987 #self.receive_list.selectedIndexes()
989 item = self.receive_list.itemAt(position)
991 addr = unicode(item.text(0))
992 if not is_valid(addr):
993 item.setExpanded(not item.isExpanded())
996 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
997 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
998 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
999 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1000 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1001 if addr in self.wallet.imported_keys:
1002 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1004 if self.expert_mode:
1005 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1006 menu.addAction(t, lambda: self.toggle_freeze(addr))
1007 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1008 menu.addAction(t, lambda: self.toggle_priority(addr))
1010 self.run_hook('receive_menu', (self, menu,))
1011 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1014 def payto(self, x, is_alias):
1021 label = self.wallet.labels.get(addr)
1022 m_addr = label + ' <' + addr + '>' if label else addr
1023 self.tabs.setCurrentIndex(1)
1024 self.payto_e.setText(m_addr)
1025 self.amount_e.setFocus()
1027 def delete_contact(self, x, is_alias):
1028 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1029 if not is_alias and x in self.wallet.addressbook:
1030 self.wallet.addressbook.remove(x)
1031 self.set_label(x, None)
1032 elif is_alias and x in self.wallet.aliases:
1033 self.wallet.aliases.pop(x)
1034 self.update_history_tab()
1035 self.update_contacts_tab()
1036 self.update_completions()
1038 def create_contact_menu(self, position):
1039 # fixme: this function apparently has a side effect.
1040 # if it is not called the menu pops up several times
1041 #self.contacts_list.selectedIndexes()
1043 item = self.contacts_list.itemAt(position)
1045 addr = unicode(item.text(0))
1046 label = unicode(item.text(1))
1047 is_alias = label in self.wallet.aliases.keys()
1048 x = label if is_alias else addr
1050 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1051 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1052 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1054 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1056 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1057 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1058 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1061 def update_receive_item(self, item):
1062 item.setFont(0, QFont(MONOSPACE_FONT))
1063 address = str(item.data(0,0).toString())
1064 label = self.wallet.labels.get(address,'')
1065 item.setData(1,0,label)
1067 self.run_hook('update_receive_item', (self, address, item))
1069 c, u = self.wallet.get_addr_balance(address)
1070 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1071 item.setData(2,0,balance)
1073 if self.expert_mode:
1074 if address in self.wallet.frozen_addresses:
1075 item.setBackgroundColor(0, QColor('lightblue'))
1076 elif address in self.wallet.prioritized_addresses:
1077 item.setBackgroundColor(0, QColor('lightgreen'))
1080 def update_receive_tab(self):
1081 l = self.receive_list
1084 l.setColumnHidden(2, not self.expert_mode)
1085 l.setColumnHidden(3, not self.expert_mode)
1086 if not self.expert_mode:
1087 width = self.column_widths['receive'][0][0]
1088 l.setColumnWidth(0, width)
1090 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1091 l.setColumnWidth(i, width)
1094 for k, account in self.wallet.accounts.items():
1095 name = account.get('name',str(k))
1096 c,u = self.wallet.get_account_balance(k)
1097 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1098 l.addTopLevelItem(account_item)
1099 account_item.setExpanded(True)
1102 for is_change in [0,1]:
1103 name = "Receiving" if not is_change else "Change"
1104 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1105 account_item.addChild(seq_item)
1106 if not is_change: seq_item.setExpanded(True)
1110 for address in account[is_change]:
1111 h = self.wallet.history.get(address,[])
1116 if gap > self.wallet.gap_limit:
1121 num_tx = '*' if h == ['*'] else "%d"%len(h)
1122 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1123 self.update_receive_item(item)
1125 item.setBackgroundColor(1, QColor('red'))
1126 seq_item.addChild(item)
1128 if self.wallet.imported_keys:
1129 c,u = self.wallet.get_imported_balance()
1130 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1131 l.addTopLevelItem(account_item)
1132 account_item.setExpanded(True)
1133 for address in self.wallet.imported_keys.keys():
1134 item = QTreeWidgetItem( [ address, '', '', ''] )
1135 self.update_receive_item(item)
1136 account_item.addChild(item)
1139 # we use column 1 because column 0 may be hidden
1140 l.setCurrentItem(l.topLevelItem(0),1)
1142 def show_contact_details(self, m):
1143 a = self.wallet.aliases.get(m)
1145 if a[0] in self.wallet.authorities.keys():
1146 s = self.wallet.authorities.get(a[0])
1149 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1150 QMessageBox.information(self, 'Alias', msg, 'OK')
1152 def update_contacts_tab(self):
1154 l = self.contacts_list
1158 for alias, v in self.wallet.aliases.items():
1160 alias_targets.append(target)
1161 item = QTreeWidgetItem( [ target, alias, '-'] )
1162 item.setBackgroundColor(0, QColor('lightgray'))
1163 l.addTopLevelItem(item)
1165 for address in self.wallet.addressbook:
1166 if address in alias_targets: continue
1167 label = self.wallet.labels.get(address,'')
1169 for tx in self.wallet.transactions.values():
1170 if address in map(lambda x: x[0], tx.outputs): n += 1
1172 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1173 item.setFont(0, QFont(MONOSPACE_FONT))
1174 l.addTopLevelItem(item)
1176 l.setCurrentItem(l.topLevelItem(0))
1179 def create_console_tab(self):
1180 from qt_console import Console
1181 self.console = console = Console()
1182 self.console.history = self.config.get("console-history",[])
1183 self.console.history_index = len(self.console.history)
1185 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1186 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1188 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1190 def mkfunc(f, method):
1191 return lambda *args: apply( f, (method, args, self.password_dialog ))
1193 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1194 methods[m] = mkfunc(c._run, m)
1196 console.updateNamespace(methods)
1200 def create_status_bar(self):
1201 self.status_text = ""
1203 sb.setFixedHeight(35)
1204 qtVersion = qVersion()
1206 update_notification = UpdateLabel(self.config)
1207 if(update_notification.new_version):
1208 sb.addPermanentWidget(update_notification)
1210 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1211 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1212 if self.wallet.seed:
1213 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1214 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1215 if self.wallet.seed:
1216 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1217 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1218 sb.addPermanentWidget( self.status_button )
1220 self.run_hook('create_status_bar', (sb,))
1222 self.setStatusBar(sb)
1226 self.config.set_key('gui', 'lite', True)
1229 self.lite.mini.show()
1231 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1232 self.lite.main(None)
1234 def new_contact_dialog(self):
1235 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1236 address = unicode(text)
1238 if is_valid(address):
1239 self.wallet.addressbook.append(address)
1241 self.update_contacts_tab()
1242 self.update_history_tab()
1243 self.update_completions()
1245 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1247 def show_master_public_key(self):
1248 dialog = QDialog(self)
1250 dialog.setWindowTitle(_("Master Public Key"))
1252 main_text = QTextEdit()
1253 main_text.setText(self.wallet.get_master_public_key())
1254 main_text.setReadOnly(True)
1255 main_text.setMaximumHeight(170)
1256 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1258 ok_button = QPushButton(_("OK"))
1259 ok_button.setDefault(True)
1260 ok_button.clicked.connect(dialog.accept)
1262 main_layout = QGridLayout()
1263 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1265 main_layout.addWidget(main_text, 1, 0)
1266 main_layout.addWidget(qrw, 1, 1 )
1268 vbox = QVBoxLayout()
1269 vbox.addLayout(main_layout)
1270 hbox = QHBoxLayout()
1272 hbox.addWidget(ok_button)
1273 vbox.addLayout(hbox)
1275 dialog.setLayout(vbox)
1280 def show_seed_dialog(self, password):
1281 if not self.wallet.seed:
1282 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1285 seed = self.wallet.decode_seed(password)
1287 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1289 self.show_seed(seed, self)
1293 def show_seed(self, seed, parent=None):
1294 dialog = QDialog(parent)
1296 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1298 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1300 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1302 seed_text = QTextEdit(brainwallet)
1303 seed_text.setReadOnly(True)
1304 seed_text.setMaximumHeight(130)
1306 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1307 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1308 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1309 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1310 label2 = QLabel(msg2)
1311 label2.setWordWrap(True)
1314 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1315 logo.setMaximumWidth(60)
1317 qrw = QRCodeWidget(seed)
1319 ok_button = QPushButton(_("OK"))
1320 ok_button.setDefault(True)
1321 ok_button.clicked.connect(dialog.accept)
1323 grid = QGridLayout()
1324 #main_layout.addWidget(logo, 0, 0)
1326 grid.addWidget(logo, 0, 0)
1327 grid.addWidget(label1, 0, 1)
1329 grid.addWidget(seed_text, 1, 0, 1, 2)
1331 grid.addWidget(qrw, 0, 2, 2, 1)
1333 vbox = QVBoxLayout()
1334 vbox.addLayout(grid)
1335 vbox.addWidget(label2)
1337 hbox = QHBoxLayout()
1339 hbox.addWidget(ok_button)
1340 vbox.addLayout(hbox)
1342 dialog.setLayout(vbox)
1345 def show_qrcode(self, data, title = "QR code"):
1349 d.setWindowTitle(title)
1350 d.setMinimumSize(270, 300)
1351 vbox = QVBoxLayout()
1352 qrw = QRCodeWidget(data)
1353 vbox.addWidget(qrw, 1)
1354 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1355 hbox = QHBoxLayout()
1359 filename = "qrcode.bmp"
1360 bmp.save_qrcode(qrw.qr, filename)
1361 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1363 b = QPushButton(_("Save"))
1365 b.clicked.connect(print_qr)
1367 b = QPushButton(_("Close"))
1369 b.clicked.connect(d.accept)
1372 vbox.addLayout(hbox)
1377 def do_protect(self, func, args):
1378 if self.wallet.use_encryption:
1379 password = self.password_dialog()
1385 if args != (False,):
1386 args = (self,) + args + (password,)
1388 args = (self,password)
1393 def show_private_key(self, address, password):
1394 if not address: return
1396 pk = self.wallet.get_private_key(address, password)
1397 except BaseException, e:
1398 self.show_message(str(e))
1400 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1404 def do_sign(self, address, message, signature, password):
1406 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1407 signature.setText(sig)
1408 except BaseException, e:
1409 self.show_message(str(e))
1411 def sign_message(self, address):
1412 if not address: return
1415 d.setWindowTitle(_('Sign Message'))
1416 d.setMinimumSize(410, 290)
1418 tab_widget = QTabWidget()
1420 layout = QGridLayout(tab)
1422 sign_address = QLineEdit()
1424 sign_address.setText(address)
1425 layout.addWidget(QLabel(_('Address')), 1, 0)
1426 layout.addWidget(sign_address, 1, 1)
1428 sign_message = QTextEdit()
1429 layout.addWidget(QLabel(_('Message')), 2, 0)
1430 layout.addWidget(sign_message, 2, 1)
1431 layout.setRowStretch(2,3)
1433 sign_signature = QTextEdit()
1434 layout.addWidget(QLabel(_('Signature')), 3, 0)
1435 layout.addWidget(sign_signature, 3, 1)
1436 layout.setRowStretch(3,1)
1439 hbox = QHBoxLayout()
1440 b = QPushButton(_("Sign"))
1442 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1443 b = QPushButton(_("Close"))
1444 b.clicked.connect(d.accept)
1446 layout.addLayout(hbox, 4, 1)
1447 tab_widget.addTab(tab, _("Sign"))
1451 layout = QGridLayout(tab)
1453 verify_address = QLineEdit()
1454 layout.addWidget(QLabel(_('Address')), 1, 0)
1455 layout.addWidget(verify_address, 1, 1)
1457 verify_message = QTextEdit()
1458 layout.addWidget(QLabel(_('Message')), 2, 0)
1459 layout.addWidget(verify_message, 2, 1)
1460 layout.setRowStretch(2,3)
1462 verify_signature = QTextEdit()
1463 layout.addWidget(QLabel(_('Signature')), 3, 0)
1464 layout.addWidget(verify_signature, 3, 1)
1465 layout.setRowStretch(3,1)
1469 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1470 self.show_message(_("Signature verified"))
1471 except BaseException, e:
1472 self.show_message(str(e))
1475 hbox = QHBoxLayout()
1476 b = QPushButton(_("Verify"))
1477 b.clicked.connect(do_verify)
1479 b = QPushButton(_("Close"))
1480 b.clicked.connect(d.accept)
1482 layout.addLayout(hbox, 4, 1)
1483 tab_widget.addTab(tab, _("Verify"))
1485 vbox = QVBoxLayout()
1486 vbox.addWidget(tab_widget)
1493 def question(self, msg):
1494 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1496 def show_message(self, msg):
1497 QMessageBox.information(self, _('Message'), msg, _('OK'))
1499 def password_dialog(self ):
1506 vbox = QVBoxLayout()
1507 msg = _('Please enter your password')
1508 vbox.addWidget(QLabel(msg))
1510 grid = QGridLayout()
1512 grid.addWidget(QLabel(_('Password')), 1, 0)
1513 grid.addWidget(pw, 1, 1)
1514 vbox.addLayout(grid)
1516 vbox.addLayout(ok_cancel_buttons(d))
1519 if not d.exec_(): return
1520 return unicode(pw.text())
1527 def change_password_dialog( wallet, parent=None ):
1530 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1538 new_pw = QLineEdit()
1539 new_pw.setEchoMode(2)
1540 conf_pw = QLineEdit()
1541 conf_pw.setEchoMode(2)
1543 vbox = QVBoxLayout()
1545 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1546 +_('To disable wallet encryption, enter an empty new password.')) \
1547 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1549 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1550 +_("Leave these fields empty if you want to disable encryption.")
1551 vbox.addWidget(QLabel(msg))
1553 grid = QGridLayout()
1556 if wallet.use_encryption:
1557 grid.addWidget(QLabel(_('Password')), 1, 0)
1558 grid.addWidget(pw, 1, 1)
1560 grid.addWidget(QLabel(_('New Password')), 2, 0)
1561 grid.addWidget(new_pw, 2, 1)
1563 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1564 grid.addWidget(conf_pw, 3, 1)
1565 vbox.addLayout(grid)
1567 vbox.addLayout(ok_cancel_buttons(d))
1570 if not d.exec_(): return
1572 password = unicode(pw.text()) if wallet.use_encryption else None
1573 new_password = unicode(new_pw.text())
1574 new_password2 = unicode(conf_pw.text())
1577 seed = wallet.decode_seed(password)
1579 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1582 if new_password != new_password2:
1583 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1584 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1586 wallet.update_password(seed, password, new_password)
1589 def seed_dialog(wallet, parent=None):
1593 vbox = QVBoxLayout()
1594 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1595 vbox.addWidget(QLabel(msg))
1597 grid = QGridLayout()
1600 seed_e = QLineEdit()
1601 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1602 grid.addWidget(seed_e, 1, 1)
1606 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1607 grid.addWidget(gap_e, 2, 1)
1608 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1609 vbox.addLayout(grid)
1611 vbox.addLayout(ok_cancel_buttons(d))
1614 if not d.exec_(): return
1617 gap = int(unicode(gap_e.text()))
1619 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1623 seed = str(seed_e.text())
1626 print_error("Warning: Not hex, trying decode")
1628 seed = mnemonic.mn_decode( seed.split(' ') )
1630 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1634 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1639 def generate_transaction_information_widget(self, tx):
1640 tabs = QTabWidget(self)
1643 grid_ui = QGridLayout(tab1)
1644 grid_ui.setColumnStretch(0,1)
1645 tabs.addTab(tab1, _('Outputs') )
1647 tree_widget = MyTreeWidget(self)
1648 tree_widget.setColumnCount(2)
1649 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1650 tree_widget.setColumnWidth(0, 300)
1651 tree_widget.setColumnWidth(1, 50)
1653 for address, value in tx.outputs:
1654 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1655 tree_widget.addTopLevelItem(item)
1657 tree_widget.setMaximumHeight(100)
1659 grid_ui.addWidget(tree_widget)
1662 grid_ui = QGridLayout(tab2)
1663 grid_ui.setColumnStretch(0,1)
1664 tabs.addTab(tab2, _('Inputs') )
1666 tree_widget = MyTreeWidget(self)
1667 tree_widget.setColumnCount(2)
1668 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1670 for input_line in tx.inputs:
1671 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1672 tree_widget.addTopLevelItem(item)
1674 tree_widget.setMaximumHeight(100)
1676 grid_ui.addWidget(tree_widget)
1680 def tx_dict_from_text(self, txt):
1682 tx_dict = json.loads(str(txt))
1683 assert "hex" in tx_dict.keys()
1684 assert "complete" in tx_dict.keys()
1685 if not tx_dict["complete"]:
1686 assert "input_info" in tx_dict.keys()
1688 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1693 def read_tx_from_file(self):
1694 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1698 with open(fileName, "r") as f:
1699 file_content = f.read()
1700 except (ValueError, IOError, os.error), reason:
1701 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1703 return self.tx_dict_from_text(file_content)
1707 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1709 self.wallet.signrawtransaction(tx, input_info, [], password)
1711 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1713 with open(fileName, "w+") as f:
1714 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1715 self.show_message(_("Transaction saved successfully"))
1718 except BaseException, e:
1719 self.show_message(str(e))
1722 def send_raw_transaction(self, raw_tx, dialog = ""):
1723 result, result_message = self.wallet.sendtx( raw_tx )
1725 self.show_message("Transaction successfully sent: %s" % (result_message))
1729 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1731 def do_process_from_text(self):
1732 dialog = QDialog(self)
1733 dialog.setMinimumWidth(500)
1734 dialog.setWindowTitle(_('Input raw transaction'))
1738 l.addWidget(QLabel(_("Transaction:")))
1742 ok_button = QPushButton(_("Load transaction"))
1743 ok_button.setDefault(True)
1744 ok_button.clicked.connect(dialog.accept)
1745 l.addWidget(ok_button)
1748 tx_dict = self.tx_dict_from_text(unicode(txt.toPlainText()))
1750 self.create_process_transaction_window(tx_dict)
1752 def do_process_from_file(self):
1753 tx_dict = self.read_tx_from_file()
1755 self.create_process_transaction_window(tx_dict)
1757 def create_process_transaction_window(self, tx_dict):
1758 tx = Transaction(tx_dict["hex"])
1760 dialog = QDialog(self)
1761 dialog.setMinimumWidth(500)
1762 dialog.setWindowTitle(_('Process raw transaction'))
1768 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1769 l.addWidget(QLabel(_("Actions")), 4,0)
1771 if tx_dict["complete"] == False:
1772 l.addWidget(QLabel(_("Unsigned")), 3,1)
1773 if self.wallet.seed :
1774 b = QPushButton("Sign transaction")
1775 input_info = json.loads(tx_dict["input_info"])
1776 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1777 l.addWidget(b, 4, 1)
1779 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1781 l.addWidget(QLabel(_("Signed")), 3,1)
1782 b = QPushButton("Broadcast transaction")
1783 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1786 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1787 cancelButton = QPushButton(_("Cancel"))
1788 cancelButton.clicked.connect(lambda: dialog.done(0))
1789 l.addWidget(cancelButton, 4,2)
1795 def do_export_privkeys(self, password):
1796 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.")))
1799 select_export = _('Select file to export your private keys to')
1800 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1802 with open(fileName, "w+") as csvfile:
1803 transaction = csv.writer(csvfile)
1804 transaction.writerow(["address", "private_key"])
1807 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1808 transaction.writerow(["%34s"%addr,pk])
1810 self.show_message(_("Private keys exported."))
1812 except (IOError, os.error), reason:
1813 export_error_label = _("Electrum was unable to produce a private key-export.")
1814 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1816 except BaseException, e:
1817 self.show_message(str(e))
1821 def do_import_labels(self):
1822 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1823 if not labelsFile: return
1825 f = open(labelsFile, 'r')
1828 for key, value in json.loads(data).items():
1829 self.wallet.labels[key] = value
1831 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1832 except (IOError, os.error), reason:
1833 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1836 def do_export_labels(self):
1837 labels = self.wallet.labels
1839 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1841 with open(fileName, 'w+') as f:
1842 json.dump(labels, f)
1843 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1844 except (IOError, os.error), reason:
1845 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1848 def do_export_history(self):
1849 from gui_lite import csv_transaction
1850 csv_transaction(self.wallet)
1854 def do_import_privkey(self, password):
1855 if not self.wallet.imported_keys:
1856 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1857 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1858 + _('Are you sure you understand what you are doing?'), 3, 4)
1861 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1863 sec = str(text).strip()
1865 addr = self.wallet.import_key(sec, password)
1867 QMessageBox.critical(None, _("Unable to import key"), "error")
1869 QMessageBox.information(None, _("Key imported"), addr)
1870 self.update_receive_tab()
1871 self.update_history_tab()
1872 except BaseException as e:
1873 QMessageBox.critical(None, _("Unable to import key"), str(e))
1876 def settings_dialog(self):
1878 d.setWindowTitle(_('Electrum Settings'))
1880 vbox = QVBoxLayout()
1882 tabs = QTabWidget(self)
1883 self.settings_tab = tabs
1884 vbox.addWidget(tabs)
1887 grid_ui = QGridLayout(tab1)
1888 grid_ui.setColumnStretch(0,1)
1889 tabs.addTab(tab1, _('Display') )
1891 nz_label = QLabel(_('Display zeros'))
1892 grid_ui.addWidget(nz_label, 0, 0)
1894 nz_e.setText("%d"% self.wallet.num_zeros)
1895 grid_ui.addWidget(nz_e, 0, 1)
1896 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1897 grid_ui.addWidget(HelpButton(msg), 0, 2)
1898 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1899 if not self.config.is_modifiable('num_zeros'):
1900 for w in [nz_e, nz_label]: w.setEnabled(False)
1902 lang_label=QLabel(_('Language') + ':')
1903 grid_ui.addWidget(lang_label, 1, 0)
1904 lang_combo = QComboBox()
1905 from i18n import languages
1906 lang_combo.addItems(languages.values())
1908 index = languages.keys().index(self.config.get("language",''))
1911 lang_combo.setCurrentIndex(index)
1912 grid_ui.addWidget(lang_combo, 1, 1)
1913 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1914 if not self.config.is_modifiable('language'):
1915 for w in [lang_combo, lang_label]: w.setEnabled(False)
1917 currencies = self.exchanger.get_currencies()
1918 currencies.insert(0, "None")
1920 cur_label=QLabel(_('Currency') + ':')
1921 grid_ui.addWidget(cur_label , 2, 0)
1922 cur_combo = QComboBox()
1923 cur_combo.addItems(currencies)
1925 index = currencies.index(self.config.get('currency', "None"))
1928 cur_combo.setCurrentIndex(index)
1929 grid_ui.addWidget(cur_combo, 2, 1)
1930 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1932 view_label=QLabel(_('Receive Tab') + ':')
1933 grid_ui.addWidget(view_label , 3, 0)
1934 view_combo = QComboBox()
1935 view_combo.addItems([_('Simple'), _('Advanced')])
1936 view_combo.setCurrentIndex(self.expert_mode)
1937 grid_ui.addWidget(view_combo, 3, 1)
1938 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1939 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1940 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1942 grid_ui.addWidget(HelpButton(hh), 3, 2)
1943 grid_ui.setRowStretch(4,1)
1947 grid_wallet = QGridLayout(tab2)
1948 grid_wallet.setColumnStretch(0,1)
1949 tabs.addTab(tab2, _('Wallet') )
1951 fee_label = QLabel(_('Transaction fee'))
1952 grid_wallet.addWidget(fee_label, 0, 0)
1954 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1955 grid_wallet.addWidget(fee_e, 0, 2)
1956 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1957 + _('Recommended value') + ': 0.001'
1958 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1959 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1960 if not self.config.is_modifiable('fee'):
1961 for w in [fee_e, fee_label]: w.setEnabled(False)
1963 usechange_label = QLabel(_('Use change addresses'))
1964 grid_wallet.addWidget(usechange_label, 1, 0)
1965 usechange_combo = QComboBox()
1966 usechange_combo.addItems([_('Yes'), _('No')])
1967 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1968 grid_wallet.addWidget(usechange_combo, 1, 2)
1969 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1970 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1972 gap_label = QLabel(_('Gap limit'))
1973 grid_wallet.addWidget(gap_label, 2, 0)
1975 gap_e.setText("%d"% self.wallet.gap_limit)
1976 grid_wallet.addWidget(gap_e, 2, 2)
1977 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1978 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1979 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1980 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1981 + _('Warning') + ': ' \
1982 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1983 + _('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'
1984 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1985 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1986 if not self.config.is_modifiable('gap_limit'):
1987 for w in [gap_e, gap_label]: w.setEnabled(False)
1989 grid_wallet.setRowStretch(3,1)
1994 grid_io = QGridLayout(tab3)
1995 grid_io.setColumnStretch(0,1)
1996 tabs.addTab(tab3, _('Import/Export') )
1998 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1999 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2000 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2001 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2003 grid_io.addWidget(QLabel(_('History')), 2, 0)
2004 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2005 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2007 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2009 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2010 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2011 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2013 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2014 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2015 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2016 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2017 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2020 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2021 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2022 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2023 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2025 grid_io.setRowStretch(5,1)
2030 tab5 = QScrollArea()
2031 grid_plugins = QGridLayout(tab5)
2032 grid_plugins.setColumnStretch(0,1)
2033 tabs.addTab(tab5, _('Plugins') )
2034 def mk_toggle(cb, p):
2035 return lambda: cb.setChecked(p.toggle(self))
2036 for i, p in enumerate(self.plugins):
2038 name, description = p.get_info()
2039 cb = QCheckBox(name)
2040 cb.setDisabled(not p.is_available())
2041 cb.setChecked(p.is_enabled())
2042 cb.clicked.connect(mk_toggle(cb,p))
2043 grid_plugins.addWidget(cb, i, 0)
2044 grid_plugins.addWidget(HelpButton(description), i, 2)
2046 print_msg("Error: cannot display plugin", p)
2047 traceback.print_exc(file=sys.stdout)
2048 grid_plugins.setRowStretch(i+1,1)
2050 self.run_hook('create_settings_tab', (self,tabs,))
2052 vbox.addLayout(ok_cancel_buttons(d))
2056 if not d.exec_(): return
2058 fee = unicode(fee_e.text())
2060 fee = int( 100000000 * Decimal(fee) )
2062 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2065 if self.wallet.fee != fee:
2066 self.wallet.fee = fee
2069 nz = unicode(nz_e.text())
2074 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2077 if self.wallet.num_zeros != nz:
2078 self.wallet.num_zeros = nz
2079 self.config.set_key('num_zeros', nz, True)
2080 self.update_history_tab()
2081 self.update_receive_tab()
2083 usechange_result = usechange_combo.currentIndex() == 0
2084 if self.wallet.use_change != usechange_result:
2085 self.wallet.use_change = usechange_result
2086 self.config.set_key('use_change', self.wallet.use_change, True)
2089 n = int(gap_e.text())
2091 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2094 if self.wallet.gap_limit != n:
2095 r = self.wallet.change_gap_limit(n)
2097 self.update_receive_tab()
2098 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2100 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2102 need_restart = False
2104 lang_request = languages.keys()[lang_combo.currentIndex()]
2105 if lang_request != self.config.get('language'):
2106 self.config.set_key("language", lang_request, True)
2109 cur_request = str(currencies[cur_combo.currentIndex()])
2110 if cur_request != self.config.get('currency', "None"):
2111 self.config.set_key('currency', cur_request, True)
2112 self.update_wallet()
2114 self.run_hook('close_settings_dialog', (self,))
2117 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2119 self.receive_tab_set_mode(view_combo.currentIndex())
2123 def network_dialog(wallet, parent=None):
2124 interface = wallet.interface
2126 if interface.is_connected:
2127 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2129 status = _("Not connected")
2130 server = interface.server
2133 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2134 server = interface.server
2136 plist, servers_list = interface.get_servers_list()
2140 d.setWindowTitle(_('Server'))
2141 d.setMinimumSize(375, 20)
2143 vbox = QVBoxLayout()
2146 hbox = QHBoxLayout()
2148 l.setPixmap(QPixmap(":icons/network.png"))
2151 hbox.addWidget(QLabel(status))
2153 vbox.addLayout(hbox)
2157 grid = QGridLayout()
2159 vbox.addLayout(grid)
2162 server_protocol = QComboBox()
2163 server_host = QLineEdit()
2164 server_host.setFixedWidth(200)
2165 server_port = QLineEdit()
2166 server_port.setFixedWidth(60)
2168 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2169 protocol_letters = 'thsg'
2170 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2171 server_protocol.addItems(protocol_names)
2173 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2174 grid.addWidget(server_protocol, 0, 1)
2175 grid.addWidget(server_host, 0, 2)
2176 grid.addWidget(server_port, 0, 3)
2178 def change_protocol(p):
2179 protocol = protocol_letters[p]
2180 host = unicode(server_host.text())
2181 pp = plist.get(host,DEFAULT_PORTS)
2182 if protocol not in pp.keys():
2183 protocol = pp.keys()[0]
2185 server_host.setText( host )
2186 server_port.setText( port )
2188 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2190 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2191 servers_list_widget = QTreeWidget(parent)
2192 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2193 servers_list_widget.setMaximumHeight(150)
2194 servers_list_widget.setColumnWidth(0, 240)
2195 for _host in servers_list.keys():
2196 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2197 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2199 def change_server(host, protocol=None):
2200 pp = plist.get(host,DEFAULT_PORTS)
2202 port = pp.get(protocol)
2203 if not port: protocol = None
2206 if 't' in pp.keys():
2208 port = pp.get(protocol)
2210 protocol = pp.keys()[0]
2211 port = pp.get(protocol)
2213 server_host.setText( host )
2214 server_port.setText( port )
2215 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2217 if not plist: return
2218 for p in protocol_letters:
2219 i = protocol_letters.index(p)
2220 j = server_protocol.model().index(i,0)
2221 if p not in pp.keys():
2222 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2224 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2228 host, port, protocol = server.split(':')
2229 change_server(host,protocol)
2231 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2232 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2234 if not wallet.config.is_modifiable('server'):
2235 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2238 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2239 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2240 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2241 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2244 proxy_mode = QComboBox()
2245 proxy_host = QLineEdit()
2246 proxy_host.setFixedWidth(200)
2247 proxy_port = QLineEdit()
2248 proxy_port.setFixedWidth(60)
2249 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2251 def check_for_disable(index = False):
2252 if proxy_mode.currentText() != 'NONE':
2253 proxy_host.setEnabled(True)
2254 proxy_port.setEnabled(True)
2256 proxy_host.setEnabled(False)
2257 proxy_port.setEnabled(False)
2260 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2262 if not wallet.config.is_modifiable('proxy'):
2263 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2265 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2266 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2267 proxy_host.setText(proxy_config.get("host"))
2268 proxy_port.setText(proxy_config.get("port"))
2270 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2271 grid.addWidget(proxy_mode, 2, 1)
2272 grid.addWidget(proxy_host, 2, 2)
2273 grid.addWidget(proxy_port, 2, 3)
2276 vbox.addLayout(ok_cancel_buttons(d))
2279 if not d.exec_(): return
2281 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2282 if proxy_mode.currentText() != 'NONE':
2283 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2287 wallet.config.set_key("proxy", proxy, True)
2288 wallet.config.set_key("server", server, True)
2289 interface.set_server(server, proxy)
2290 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2293 def closeEvent(self, event):
2295 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2296 self.save_column_widths()
2297 self.config.set_key("column-widths", self.column_widths, True)
2298 self.config.set_key("console-history",self.console.history[-50:])
2304 def __init__(self, wallet, config, app=None):
2305 self.wallet = wallet
2306 self.config = config
2308 self.app = QApplication(sys.argv)
2311 def restore_or_create(self):
2312 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2313 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2314 if r==2: return None
2315 return 'restore' if r==1 else 'create'
2317 def seed_dialog(self):
2318 return ElectrumWindow.seed_dialog( self.wallet )
2320 def network_dialog(self):
2321 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2324 def show_seed(self):
2325 ElectrumWindow.show_seed(self.wallet.seed)
2328 def password_dialog(self):
2329 if self.wallet.seed:
2330 ElectrumWindow.change_password_dialog(self.wallet)
2333 def restore_wallet(self):
2334 wallet = self.wallet
2335 # wait until we are connected, because the user might have selected another server
2336 if not wallet.interface.is_connected:
2337 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2338 waiting_dialog(waiting)
2340 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2341 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2343 wallet.set_up_to_date(False)
2344 wallet.interface.poke('synchronizer')
2345 waiting_dialog(waiting)
2346 if wallet.is_found():
2347 print_error( "Recovery successful" )
2349 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2356 w = ElectrumWindow(self.wallet, self.config)
2357 if url: w.set_url(url)