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
45 import bmp, pyqrnative
48 from decimal import Decimal
56 if platform.system() == 'Windows':
57 MONOSPACE_FONT = 'Lucida Console'
58 elif platform.system() == 'Darwin':
59 MONOSPACE_FONT = 'Monaco'
61 MONOSPACE_FONT = 'monospace'
63 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
65 from electrum import ELECTRUM_VERSION
68 class UpdateLabel(QtGui.QLabel):
69 def __init__(self, config, parent=None):
70 QtGui.QLabel.__init__(self, parent)
71 self.new_version = False
74 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
75 con.request("GET", "/version")
76 res = con.getresponse()
77 except socket.error as msg:
78 print_error("Could not retrieve version information")
82 self.latest_version = res.read()
83 self.latest_version = self.latest_version.replace("\n","")
84 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
86 self.current_version = ELECTRUM_VERSION
87 if(self.compare_versions(self.latest_version, self.current_version) == 1):
88 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
89 if(self.compare_versions(self.latest_version, latest_seen) == 1):
90 self.new_version = True
91 self.setText(_("New version available") + ": " + self.latest_version)
94 def compare_versions(self, version1, version2):
96 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
97 return cmp(normalize(version1), normalize(version2))
99 def ignore_this_version(self):
101 self.config.set_key("last_seen_version", self.latest_version, True)
102 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
105 def ignore_all_version(self):
107 self.config.set_key("last_seen_version", "9.9.9", True)
108 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
111 def open_website(self):
112 webbrowser.open("http://electrum.org/download.html")
115 def mouseReleaseEvent(self, event):
116 dialog = QDialog(self)
117 dialog.setWindowTitle(_('Electrum update'))
120 main_layout = QGridLayout()
121 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
123 ignore_version = QPushButton(_("Ignore this version"))
124 ignore_version.clicked.connect(self.ignore_this_version)
126 ignore_all_versions = QPushButton(_("Ignore all versions"))
127 ignore_all_versions.clicked.connect(self.ignore_all_version)
129 open_website = QPushButton(_("Goto download page"))
130 open_website.clicked.connect(self.open_website)
132 main_layout.addWidget(ignore_version, 1, 0)
133 main_layout.addWidget(ignore_all_versions, 1, 1)
134 main_layout.addWidget(open_website, 1, 2)
136 dialog.setLayout(main_layout)
140 if not dialog.exec_(): return
142 def numbify(entry, is_int = False):
143 text = unicode(entry.text()).strip()
144 pos = entry.cursorPosition()
146 if not is_int: chars +='.'
147 s = ''.join([i for i in text if i in chars])
151 s = s.replace('.','')
152 s = s[:p] + '.' + s[p:p+8]
154 amount = int( Decimal(s) * 100000000 )
163 entry.setCursorPosition(pos)
167 class Timer(QtCore.QThread):
170 self.emit(QtCore.SIGNAL('timersignal'))
173 class HelpButton(QPushButton):
174 def __init__(self, text):
175 QPushButton.__init__(self, '?')
176 self.setFocusPolicy(Qt.NoFocus)
177 self.setFixedWidth(20)
178 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
181 class EnterButton(QPushButton):
182 def __init__(self, text, func):
183 QPushButton.__init__(self, text)
185 self.clicked.connect(func)
187 def keyPressEvent(self, e):
188 if e.key() == QtCore.Qt.Key_Return:
191 class MyTreeWidget(QTreeWidget):
192 def __init__(self, parent):
193 QTreeWidget.__init__(self, parent)
196 for i in range(0,self.viewport().height()/5):
197 if self.itemAt(QPoint(0,i*5)) == item:
201 for j in range(0,30):
202 if self.itemAt(QPoint(0,i*5 + j)) != item:
204 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
206 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
211 class StatusBarButton(QPushButton):
212 def __init__(self, icon, tooltip, func):
213 QPushButton.__init__(self, icon, '')
214 self.setToolTip(tooltip)
216 self.setMaximumWidth(25)
217 self.clicked.connect(func)
220 def keyPressEvent(self, e):
221 if e.key() == QtCore.Qt.Key_Return:
229 def waiting_dialog(f):
235 w.setWindowTitle('Electrum')
245 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
250 def ok_cancel_buttons(dialog):
253 b = QPushButton("OK")
255 b.clicked.connect(dialog.accept)
256 b = QPushButton("Cancel")
258 b.clicked.connect(dialog.reject)
262 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
263 "receive":[[370],[370,200,130,130],[370,200,130,130]] }
265 class ElectrumWindow(QMainWindow):
267 def __init__(self, wallet, config):
268 QMainWindow.__init__(self)
272 self.wallet.interface.register_callback('updated', self.update_callback)
273 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
274 self.wallet.interface.register_callback('disconnected', self.update_callback)
275 self.wallet.interface.register_callback('disconnecting', self.update_callback)
277 self.expert_mode = config.get('classic_expert_mode', False)
278 self.merchant_name = config.get('merchant_name', 'Invoice')
280 set_language(config.get('language'))
282 self.funds_error = False
283 self.completions = QStringListModel()
285 self.tabs = tabs = QTabWidget(self)
286 self.column_widths = self.config.get("column-widths", default_column_widths )
287 tabs.addTab(self.create_history_tab(), _('History') )
288 tabs.addTab(self.create_send_tab(), _('Send') )
289 tabs.addTab(self.create_receive_tab(), _('Receive') )
290 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
291 tabs.addTab(self.create_console_tab(), _('Console') )
292 tabs.setMinimumSize(600, 400)
293 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
294 self.setCentralWidget(tabs)
295 self.create_status_bar()
297 g = self.config.get("winpos-qt",[100, 100, 840, 400])
298 self.setGeometry(g[0], g[1], g[2], g[3])
299 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
300 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
301 self.setWindowTitle( title )
303 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
304 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
305 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
306 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
308 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
309 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
310 self.history_list.setFocus(True)
312 self.exchanger = exchange_rate.Exchanger(self)
313 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
315 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
316 if platform.system() == 'Windows':
317 n = 3 if self.wallet.seed else 2
318 tabs.setCurrentIndex (n)
319 tabs.setCurrentIndex (0)
321 # set initial message
322 self.console.showMessage(self.wallet.banner)
325 for p in self.wallet.plugins:
329 print_msg("Error:cannot initialize plugin",p)
330 traceback.print_exc(file=sys.stdout)
334 QMainWindow.close(self)
335 self.wallet.run_hook('close_main_window', (self,))
337 def connect_slots(self, sender):
338 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
339 self.previous_payto_e=''
341 def timer_actions(self):
342 self.wallet.run_hook('timer_actions', (self,))
344 if self.payto_e.hasFocus():
346 r = unicode( self.payto_e.text() )
347 if r != self.previous_payto_e:
348 self.previous_payto_e = r
350 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
352 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
356 s = r + ' <' + to_address + '>'
357 self.payto_e.setText(s)
360 def update_callback(self):
361 self.emit(QtCore.SIGNAL('updatesignal'))
363 def update_wallet(self):
364 if self.wallet.interface and self.wallet.interface.is_connected:
365 if not self.wallet.up_to_date:
366 text = _("Synchronizing...")
367 icon = QIcon(":icons/status_waiting.png")
369 c, u = self.wallet.get_balance()
370 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
371 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
372 text += self.create_quote_text(Decimal(c+u)/100000000)
373 icon = QIcon(":icons/status_connected.png")
375 text = _("Not connected")
376 icon = QIcon(":icons/status_disconnected.png")
378 self.status_text = text
379 self.statusBar().showMessage(text)
380 self.status_button.setIcon( icon )
382 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
383 self.update_history_tab()
384 self.update_receive_tab()
385 self.update_contacts_tab()
386 self.update_completions()
389 def create_quote_text(self, btc_balance):
390 quote_currency = self.config.get("currency", "None")
391 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
392 if quote_balance is None:
395 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
398 def create_history_tab(self):
399 self.history_list = l = MyTreeWidget(self)
401 for i,width in enumerate(self.column_widths['history']):
402 l.setColumnWidth(i, width)
403 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
404 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
405 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
407 l.setContextMenuPolicy(Qt.CustomContextMenu)
408 l.customContextMenuRequested.connect(self.create_history_menu)
412 def create_history_menu(self, position):
413 self.history_list.selectedIndexes()
414 item = self.history_list.currentItem()
416 tx_hash = str(item.data(0, Qt.UserRole).toString())
417 if not tx_hash: return
419 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
420 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
421 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
422 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
425 def show_tx_details(self, tx):
426 dialog = QDialog(None)
428 dialog.setWindowTitle(_("Transaction Details"))
430 dialog.setLayout(vbox)
431 dialog.setMinimumSize(600,300)
434 if tx_hash in self.wallet.transactions.keys():
435 is_mine, v, fee = self.wallet.get_tx_value(tx)
436 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
438 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
444 vbox.addWidget(QLabel("Transaction ID:"))
445 e = QLineEdit(tx_hash)
449 vbox.addWidget(QLabel("Date: %s"%time_str))
450 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
453 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
454 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
456 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
457 vbox.addWidget(QLabel("Transaction fee: unknown"))
459 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
461 vbox.addWidget( self.generate_transaction_information_widget(tx) )
463 ok_button = QPushButton(_("Close"))
464 ok_button.setDefault(True)
465 ok_button.clicked.connect(dialog.accept)
469 hbox.addWidget(ok_button)
473 def tx_label_clicked(self, item, column):
474 if column==2 and item.isSelected():
476 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
477 self.history_list.editItem( item, column )
478 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
481 def tx_label_changed(self, item, column):
485 tx_hash = str(item.data(0, Qt.UserRole).toString())
486 tx = self.wallet.transactions.get(tx_hash)
487 s = self.wallet.labels.get(tx_hash)
488 text = unicode( item.text(2) )
490 self.wallet.labels[tx_hash] = text
491 item.setForeground(2, QBrush(QColor('black')))
493 if s: self.wallet.labels.pop(tx_hash)
494 text = self.wallet.get_default_label(tx_hash)
495 item.setText(2, text)
496 item.setForeground(2, QBrush(QColor('gray')))
500 def edit_label(self, is_recv):
501 l = self.receive_list if is_recv else self.contacts_list
502 c = 2 if is_recv else 1
503 item = l.currentItem()
504 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
505 l.editItem( item, c )
506 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
510 def address_label_clicked(self, item, column, l, column_addr, column_label):
511 if column == column_label and item.isSelected():
512 addr = unicode( item.text(column_addr) )
513 label = unicode( item.text(column_label) )
514 if label in self.wallet.aliases.keys():
516 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
517 l.editItem( item, column )
518 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
521 def address_label_changed(self, item, column, l, column_addr, column_label):
523 if column == column_label:
524 addr = unicode( item.text(column_addr) )
525 text = unicode( item.text(column_label) )
529 if text not in self.wallet.aliases.keys():
530 old_addr = self.wallet.labels.get(text)
532 self.wallet.labels[addr] = text
535 print_error("Error: This is one of your aliases")
536 label = self.wallet.labels.get(addr,'')
537 item.setText(column_label, QString(label))
539 s = self.wallet.labels.get(addr)
541 self.wallet.labels.pop(addr)
545 self.update_history_tab()
546 self.update_completions()
548 self.current_item_changed(item)
550 self.wallet.run_hook('item_changed',(self, item, column))
553 def current_item_changed(self, a):
554 self.wallet.run_hook('current_item_changed',(self, a))
558 def update_history_tab(self):
560 self.history_list.clear()
561 for item in self.wallet.get_tx_history():
562 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
565 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
571 icon = QIcon(":icons/unconfirmed.png")
573 icon = QIcon(":icons/clock%d.png"%conf)
575 icon = QIcon(":icons/confirmed.png")
578 icon = QIcon(":icons/unconfirmed.png")
580 if value is not None:
581 v_str = format_satoshis(value, True, self.wallet.num_zeros)
585 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
588 label, is_default_label = self.wallet.get_label(tx_hash)
590 label = _('Pruned transaction outputs')
591 is_default_label = False
593 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
594 item.setFont(2, QFont(MONOSPACE_FONT))
595 item.setFont(3, QFont(MONOSPACE_FONT))
596 item.setFont(4, QFont(MONOSPACE_FONT))
598 item.setForeground(3, QBrush(QColor("#BC1E1E")))
600 item.setData(0, Qt.UserRole, tx_hash)
601 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
603 item.setForeground(2, QBrush(QColor('grey')))
605 item.setIcon(0, icon)
606 self.history_list.insertTopLevelItem(0,item)
609 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
612 def create_send_tab(self):
617 grid.setColumnMinimumWidth(3,300)
618 grid.setColumnStretch(5,1)
620 self.payto_e = QLineEdit()
621 grid.addWidget(QLabel(_('Pay to')), 1, 0)
622 grid.addWidget(self.payto_e, 1, 1, 1, 3)
624 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)
626 completer = QCompleter()
627 completer.setCaseSensitivity(False)
628 self.payto_e.setCompleter(completer)
629 completer.setModel(self.completions)
631 self.message_e = QLineEdit()
632 grid.addWidget(QLabel(_('Description')), 2, 0)
633 grid.addWidget(self.message_e, 2, 1, 1, 3)
634 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)
636 self.amount_e = QLineEdit()
637 grid.addWidget(QLabel(_('Amount')), 3, 0)
638 grid.addWidget(self.amount_e, 3, 1, 1, 2)
639 grid.addWidget(HelpButton(
640 _('Amount to be sent.') + '\n\n' \
641 + _('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)
643 self.fee_e = QLineEdit()
644 grid.addWidget(QLabel(_('Fee')), 4, 0)
645 grid.addWidget(self.fee_e, 4, 1, 1, 2)
646 grid.addWidget(HelpButton(
647 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
648 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
649 + _('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)
652 b = EnterButton(_("Send"), self.do_send)
654 b = EnterButton(_("Create unsigned transaction"), self.do_send)
655 grid.addWidget(b, 6, 1)
657 b = EnterButton(_("Clear"),self.do_clear)
658 grid.addWidget(b, 6, 2)
660 self.payto_sig = QLabel('')
661 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
663 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
664 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
673 def entry_changed( is_fee ):
674 self.funds_error = False
675 amount = numbify(self.amount_e)
676 fee = numbify(self.fee_e)
677 if not is_fee: fee = None
680 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
682 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
685 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
686 text = self.status_text
689 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
690 self.funds_error = True
691 text = _( "Not enough funds" )
693 self.statusBar().showMessage(text)
694 self.amount_e.setPalette(palette)
695 self.fee_e.setPalette(palette)
697 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
698 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
700 self.wallet.run_hook('create_send_tab',(self,grid))
704 def update_completions(self):
706 for addr,label in self.wallet.labels.items():
707 if addr in self.wallet.addressbook:
708 l.append( label + ' <' + addr + '>')
709 l = l + self.wallet.aliases.keys()
711 self.completions.setStringList(l)
717 label = unicode( self.message_e.text() )
718 r = unicode( self.payto_e.text() )
722 m1 = re.match(ALIAS_REGEXP, r)
723 # label or alias, with address in brackets
724 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
727 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
731 to_address = m2.group(2)
735 if not is_valid(to_address):
736 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
740 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
742 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
745 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
747 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
750 if self.wallet.use_encryption:
751 password = self.password_dialog()
758 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
759 except BaseException, e:
760 self.show_message(str(e))
763 self.wallet.run_hook('send_tx', (wallet, self, tx))
766 self.wallet.labels[tx.hash()] = label
769 h = self.wallet.send_tx(tx)
770 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
771 status, msg = self.wallet.receive_tx( h )
773 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
775 self.update_contacts_tab()
777 QMessageBox.warning(self, _('Error'), msg, _('OK'))
779 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
781 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
782 with open(fileName,'w') as f:
783 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
784 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
786 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
791 def set_url(self, url):
792 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
793 self.tabs.setCurrentIndex(1)
794 label = self.wallet.labels.get(payto)
795 m_addr = label + ' <'+ payto+'>' if label else payto
796 self.payto_e.setText(m_addr)
798 self.message_e.setText(message)
799 self.amount_e.setText(amount)
801 self.set_frozen(self.payto_e,True)
802 self.set_frozen(self.amount_e,True)
803 self.set_frozen(self.message_e,True)
804 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
806 self.payto_sig.setVisible(False)
809 self.payto_sig.setVisible(False)
810 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
812 self.set_frozen(e,False)
814 def set_frozen(self,entry,frozen):
816 entry.setReadOnly(True)
817 entry.setFrame(False)
819 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
820 entry.setPalette(palette)
822 entry.setReadOnly(False)
825 palette.setColor(entry.backgroundRole(), QColor('white'))
826 entry.setPalette(palette)
829 def toggle_freeze(self,addr):
831 if addr in self.wallet.frozen_addresses:
832 self.wallet.unfreeze(addr)
834 self.wallet.freeze(addr)
835 self.update_receive_tab()
837 def toggle_priority(self,addr):
839 if addr in self.wallet.prioritized_addresses:
840 self.wallet.unprioritize(addr)
842 self.wallet.prioritize(addr)
843 self.update_receive_tab()
846 def create_list_tab(self, headers):
847 "generic tab creation method"
848 l = MyTreeWidget(self)
849 l.setColumnCount( len(headers) )
850 l.setHeaderLabels( headers )
860 vbox.addWidget(buttons)
865 buttons.setLayout(hbox)
870 def create_receive_tab(self):
871 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _(''), _('Balance'), _('Tx')])
872 l.setContextMenuPolicy(Qt.CustomContextMenu)
873 l.customContextMenuRequested.connect(self.create_receive_menu)
874 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
875 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
876 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
877 self.receive_list = l
878 self.receive_buttons_hbox = hbox
883 def receive_tab_set_mode(self, i):
884 self.save_column_widths()
885 self.expert_mode = (i == 1)
886 self.config.set_key('classic_expert_mode', self.expert_mode, True)
888 self.update_receive_tab()
891 def save_column_widths(self):
892 if not self.expert_mode:
893 widths = [ self.receive_list.columnWidth(0) ]
896 for i in range(self.receive_list.columnCount() -1):
897 widths.append(self.receive_list.columnWidth(i))
898 self.column_widths["receive"][self.expert_mode] = widths
900 self.column_widths["history"] = []
901 for i in range(self.history_list.columnCount() - 1):
902 self.column_widths["history"].append(self.history_list.columnWidth(i))
904 self.column_widths["contacts"] = []
905 for i in range(self.contacts_list.columnCount() - 1):
906 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
909 def create_contacts_tab(self):
910 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
911 l.setContextMenuPolicy(Qt.CustomContextMenu)
912 l.customContextMenuRequested.connect(self.create_contact_menu)
913 for i,width in enumerate(self.column_widths['contacts']):
914 l.setColumnWidth(i, width)
916 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
917 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
918 self.contacts_list = l
919 self.contacts_buttons_hbox = hbox
920 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
925 def delete_imported_key(self, addr):
926 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
927 self.wallet.imported_keys.pop(addr)
928 self.update_receive_tab()
929 self.update_history_tab()
933 def create_receive_menu(self, position):
934 # fixme: this function apparently has a side effect.
935 # if it is not called the menu pops up several times
936 #self.receive_list.selectedIndexes()
938 item = self.receive_list.itemAt(position)
940 addr = unicode(item.text(0))
942 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
943 menu.addAction(_("QR code"), lambda: ElectrumWindow.show_qrcode("bitcoin:" + addr, _("Address")) )
944 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
945 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
946 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
947 if addr in self.wallet.imported_keys:
948 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
951 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
952 menu.addAction(t, lambda: self.toggle_freeze(addr))
953 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
954 menu.addAction(t, lambda: self.toggle_priority(addr))
956 self.wallet.run_hook('receive_menu', (self, menu,))
957 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
960 def payto(self, x, is_alias):
967 label = self.wallet.labels.get(addr)
968 m_addr = label + ' <' + addr + '>' if label else addr
969 self.tabs.setCurrentIndex(1)
970 self.payto_e.setText(m_addr)
971 self.amount_e.setFocus()
973 def delete_contact(self, x, is_alias):
974 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
975 if not is_alias and x in self.wallet.addressbook:
976 self.wallet.addressbook.remove(x)
977 if x in self.wallet.labels.keys():
978 self.wallet.labels.pop(x)
979 elif is_alias and x in self.wallet.aliases:
980 self.wallet.aliases.pop(x)
981 self.update_history_tab()
982 self.update_contacts_tab()
983 self.update_completions()
985 def create_contact_menu(self, position):
986 # fixme: this function apparently has a side effect.
987 # if it is not called the menu pops up several times
988 #self.contacts_list.selectedIndexes()
990 item = self.contacts_list.itemAt(position)
992 addr = unicode(item.text(0))
993 label = unicode(item.text(1))
994 is_alias = label in self.wallet.aliases.keys()
995 x = label if is_alias else addr
997 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
998 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
999 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1001 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1003 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1004 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1005 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1008 def update_receive_item(self, item):
1009 address = str(item.data(0,0).toString())
1010 label = self.wallet.labels.get(address,'')
1011 item.setData(1,0,label)
1013 self.wallet.run_hook('update_receive_item', (self, address, item))
1015 c, u = self.wallet.get_addr_balance(address)
1016 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1017 item.setData(3,0,balance)
1019 if self.expert_mode:
1020 if address in self.wallet.frozen_addresses:
1021 item.setBackgroundColor(0, QColor('lightblue'))
1022 elif address in self.wallet.prioritized_addresses:
1023 item.setBackgroundColor(0, QColor('lightgreen'))
1026 def update_receive_tab(self):
1027 l = self.receive_list
1030 l.setColumnHidden(3, not self.expert_mode)
1031 l.setColumnHidden(4, not self.expert_mode)
1032 if not self.expert_mode:
1033 width = self.column_widths['receive'][0][0]
1034 l.setColumnWidth(0, width)
1036 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1037 l.setColumnWidth(i, width)
1040 for k, account in self.wallet.accounts.items():
1041 name = account.get('name',str(k))
1042 c,u = self.wallet.get_account_balance(k)
1043 account_item = QTreeWidgetItem( [ name, '', '', format_satoshis(c+u), ''] )
1044 l.addTopLevelItem(account_item)
1045 account_item.setExpanded(True)
1048 for is_change in [0,1]:
1049 name = "Receiving" if not is_change else "Change"
1050 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1051 account_item.addChild(seq_item)
1052 if not is_change: seq_item.setExpanded(True)
1056 for address in account[is_change]:
1057 h = self.wallet.history.get(address,[])
1062 if gap > self.wallet.gap_limit:
1067 num_tx = '*' if h == ['*'] else "%d"%len(h)
1068 item = QTreeWidgetItem( [ address, '', '', '', num_tx] )
1069 item.setFont(0, QFont(MONOSPACE_FONT))
1070 item.setFont(2, QFont(MONOSPACE_FONT))
1071 self.update_receive_item(item)
1073 item.setBackgroundColor(1, QColor('red'))
1074 seq_item.addChild(item)
1076 if self.wallet.imported_keys:
1077 c,u = self.wallet.get_imported_balance()
1078 account_item = QTreeWidgetItem( [ _('Imported'), '', '', format_satoshis(c+u), ''] )
1079 l.addTopLevelItem(account_item)
1080 account_item.setExpanded(True)
1081 for address in self.wallet.imported_keys.keys():
1082 item = QTreeWidgetItem( [ address, '', '', '', ''] )
1083 item.setFont(0, QFont(MONOSPACE_FONT))
1084 item.setFont(2, QFont(MONOSPACE_FONT))
1085 self.update_receive_item(item)
1086 account_item.addChild(item)
1089 # we use column 1 because column 0 may be hidden
1090 l.setCurrentItem(l.topLevelItem(0),1)
1092 def show_contact_details(self, m):
1093 a = self.wallet.aliases.get(m)
1095 if a[0] in self.wallet.authorities.keys():
1096 s = self.wallet.authorities.get(a[0])
1099 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1100 QMessageBox.information(self, 'Alias', msg, 'OK')
1102 def update_contacts_tab(self):
1104 l = self.contacts_list
1108 for alias, v in self.wallet.aliases.items():
1110 alias_targets.append(target)
1111 item = QTreeWidgetItem( [ target, alias, '-'] )
1112 item.setBackgroundColor(0, QColor('lightgray'))
1113 l.addTopLevelItem(item)
1115 for address in self.wallet.addressbook:
1116 if address in alias_targets: continue
1117 label = self.wallet.labels.get(address,'')
1119 for tx in self.wallet.transactions.values():
1120 if address in map(lambda x: x[0], tx.outputs): n += 1
1122 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1123 item.setFont(0, QFont(MONOSPACE_FONT))
1124 l.addTopLevelItem(item)
1126 l.setCurrentItem(l.topLevelItem(0))
1129 def create_console_tab(self):
1130 from qt_console import Console
1131 from electrum import util, bitcoin, commands
1132 self.console = console = Console()
1133 self.console.history = self.config.get("console-history",[])
1134 self.console.history_index = len(self.console.history)
1136 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1137 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1139 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1141 def mkfunc(f, method):
1142 return lambda *args: apply( f, (method, args, self.password_dialog ))
1144 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1145 methods[m] = mkfunc(c._run, m)
1147 console.updateNamespace(methods)
1151 def create_status_bar(self):
1152 self.status_text = ""
1154 sb.setFixedHeight(35)
1155 qtVersion = qVersion()
1157 update_notification = UpdateLabel(self.config)
1158 if(update_notification.new_version):
1159 sb.addPermanentWidget(update_notification)
1161 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1162 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1163 if self.wallet.seed:
1164 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1165 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1166 if self.wallet.seed:
1167 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1168 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1169 sb.addPermanentWidget( self.status_button )
1171 self.setStatusBar(sb)
1175 self.config.set_key('gui', 'lite', True)
1178 self.lite.mini.show()
1180 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1181 self.lite.main(None)
1183 def new_contact_dialog(self):
1184 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1185 address = unicode(text)
1187 if is_valid(address):
1188 self.wallet.addressbook.append(address)
1190 self.update_contacts_tab()
1191 self.update_history_tab()
1192 self.update_completions()
1194 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1196 def show_master_public_key(self):
1197 dialog = QDialog(None)
1199 dialog.setWindowTitle(_("Master Public Key"))
1201 main_text = QTextEdit()
1202 main_text.setText(self.wallet.get_master_public_key())
1203 main_text.setReadOnly(True)
1204 main_text.setMaximumHeight(170)
1205 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1207 ok_button = QPushButton(_("OK"))
1208 ok_button.setDefault(True)
1209 ok_button.clicked.connect(dialog.accept)
1211 main_layout = QGridLayout()
1212 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1214 main_layout.addWidget(main_text, 1, 0)
1215 main_layout.addWidget(qrw, 1, 1 )
1217 vbox = QVBoxLayout()
1218 vbox.addLayout(main_layout)
1219 hbox = QHBoxLayout()
1221 hbox.addWidget(ok_button)
1222 vbox.addLayout(hbox)
1224 dialog.setLayout(vbox)
1229 def show_seed_dialog(self, wallet, parent=None):
1231 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1234 if wallet.use_encryption:
1235 password = parent.password_dialog()
1242 seed = wallet.decode_seed(password)
1244 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1247 self.show_seed(seed)
1250 def show_seed(self, seed):
1251 dialog = QDialog(None)
1253 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1255 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1257 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1259 seed_text = QTextEdit(brainwallet)
1260 seed_text.setReadOnly(True)
1261 seed_text.setMaximumHeight(130)
1263 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1264 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1265 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1266 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1267 label2 = QLabel(msg2)
1268 label2.setWordWrap(True)
1271 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1272 logo.setMaximumWidth(60)
1274 qrw = QRCodeWidget(seed, 4)
1276 ok_button = QPushButton(_("OK"))
1277 ok_button.setDefault(True)
1278 ok_button.clicked.connect(dialog.accept)
1280 grid = QGridLayout()
1281 #main_layout.addWidget(logo, 0, 0)
1283 grid.addWidget(logo, 0, 0)
1284 grid.addWidget(label1, 0, 1)
1286 grid.addWidget(seed_text, 1, 0, 1, 2)
1288 grid.addWidget(qrw, 0, 2, 2, 1)
1290 vbox = QVBoxLayout()
1291 vbox.addLayout(grid)
1292 vbox.addWidget(label2)
1294 hbox = QHBoxLayout()
1296 hbox.addWidget(ok_button)
1297 vbox.addLayout(hbox)
1299 dialog.setLayout(vbox)
1303 def show_qrcode(data, title = "QR code"):
1307 d.setWindowTitle(title)
1308 d.setMinimumSize(270, 300)
1309 vbox = QVBoxLayout()
1310 qrw = QRCodeWidget(data)
1311 vbox.addWidget(qrw, 1)
1312 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1313 hbox = QHBoxLayout()
1317 filename = "qrcode.bmp"
1318 bmp.save_qrcode(qrw.qr, filename)
1319 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1321 b = QPushButton(_("Save"))
1323 b.clicked.connect(print_qr)
1325 b = QPushButton(_("Close"))
1327 b.clicked.connect(d.accept)
1330 vbox.addLayout(hbox)
1334 def view_private_key(self,address):
1335 if not address: return
1336 if self.wallet.use_encryption:
1337 password = self.password_dialog()
1344 pk = self.wallet.get_private_key(address, password)
1345 except BaseException, e:
1346 self.show_message(str(e))
1349 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1352 def sign_message(self,address):
1353 if not address: return
1356 d.setWindowTitle(_('Sign Message'))
1357 d.setMinimumSize(410, 290)
1359 tab_widget = QTabWidget()
1361 layout = QGridLayout(tab)
1363 sign_address = QLineEdit()
1365 sign_address.setText(address)
1366 layout.addWidget(QLabel(_('Address')), 1, 0)
1367 layout.addWidget(sign_address, 1, 1)
1369 sign_message = QTextEdit()
1370 layout.addWidget(QLabel(_('Message')), 2, 0)
1371 layout.addWidget(sign_message, 2, 1)
1372 layout.setRowStretch(2,3)
1374 sign_signature = QTextEdit()
1375 layout.addWidget(QLabel(_('Signature')), 3, 0)
1376 layout.addWidget(sign_signature, 3, 1)
1377 layout.setRowStretch(3,1)
1380 if self.wallet.use_encryption:
1381 password = self.password_dialog()
1388 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1389 sign_signature.setText(signature)
1390 except BaseException, e:
1391 self.show_message(str(e))
1394 hbox = QHBoxLayout()
1395 b = QPushButton(_("Sign"))
1397 b.clicked.connect(do_sign)
1398 b = QPushButton(_("Close"))
1399 b.clicked.connect(d.accept)
1401 layout.addLayout(hbox, 4, 1)
1402 tab_widget.addTab(tab, _("Sign"))
1406 layout = QGridLayout(tab)
1408 verify_address = QLineEdit()
1409 layout.addWidget(QLabel(_('Address')), 1, 0)
1410 layout.addWidget(verify_address, 1, 1)
1412 verify_message = QTextEdit()
1413 layout.addWidget(QLabel(_('Message')), 2, 0)
1414 layout.addWidget(verify_message, 2, 1)
1415 layout.setRowStretch(2,3)
1417 verify_signature = QTextEdit()
1418 layout.addWidget(QLabel(_('Signature')), 3, 0)
1419 layout.addWidget(verify_signature, 3, 1)
1420 layout.setRowStretch(3,1)
1424 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1425 self.show_message(_("Signature verified"))
1426 except BaseException, e:
1427 self.show_message(str(e))
1430 hbox = QHBoxLayout()
1431 b = QPushButton(_("Verify"))
1432 b.clicked.connect(do_verify)
1434 b = QPushButton(_("Close"))
1435 b.clicked.connect(d.accept)
1437 layout.addLayout(hbox, 4, 1)
1438 tab_widget.addTab(tab, _("Verify"))
1440 vbox = QVBoxLayout()
1441 vbox.addWidget(tab_widget)
1448 def question(self, msg):
1449 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1451 def show_message(self, msg):
1452 QMessageBox.information(self, _('Message'), msg, _('OK'))
1454 def password_dialog(self ):
1461 vbox = QVBoxLayout()
1462 msg = _('Please enter your password')
1463 vbox.addWidget(QLabel(msg))
1465 grid = QGridLayout()
1467 grid.addWidget(QLabel(_('Password')), 1, 0)
1468 grid.addWidget(pw, 1, 1)
1469 vbox.addLayout(grid)
1471 vbox.addLayout(ok_cancel_buttons(d))
1474 if not d.exec_(): return
1475 return unicode(pw.text())
1482 def change_password_dialog( wallet, parent=None ):
1485 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1493 new_pw = QLineEdit()
1494 new_pw.setEchoMode(2)
1495 conf_pw = QLineEdit()
1496 conf_pw.setEchoMode(2)
1498 vbox = QVBoxLayout()
1500 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1501 +_('To disable wallet encryption, enter an empty new password.')) \
1502 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1504 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1505 +_("Leave these fields empty if you want to disable encryption.")
1506 vbox.addWidget(QLabel(msg))
1508 grid = QGridLayout()
1511 if wallet.use_encryption:
1512 grid.addWidget(QLabel(_('Password')), 1, 0)
1513 grid.addWidget(pw, 1, 1)
1515 grid.addWidget(QLabel(_('New Password')), 2, 0)
1516 grid.addWidget(new_pw, 2, 1)
1518 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1519 grid.addWidget(conf_pw, 3, 1)
1520 vbox.addLayout(grid)
1522 vbox.addLayout(ok_cancel_buttons(d))
1525 if not d.exec_(): return
1527 password = unicode(pw.text()) if wallet.use_encryption else None
1528 new_password = unicode(new_pw.text())
1529 new_password2 = unicode(conf_pw.text())
1532 seed = wallet.decode_seed(password)
1534 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1537 if new_password != new_password2:
1538 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1539 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1541 wallet.update_password(seed, password, new_password)
1544 def seed_dialog(wallet, parent=None):
1548 vbox = QVBoxLayout()
1549 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1550 vbox.addWidget(QLabel(msg))
1552 grid = QGridLayout()
1555 seed_e = QLineEdit()
1556 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1557 grid.addWidget(seed_e, 1, 1)
1561 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1562 grid.addWidget(gap_e, 2, 1)
1563 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1564 vbox.addLayout(grid)
1566 vbox.addLayout(ok_cancel_buttons(d))
1569 if not d.exec_(): return
1572 gap = int(unicode(gap_e.text()))
1574 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1578 seed = str(seed_e.text())
1581 print_error("Warning: Not hex, trying decode")
1583 seed = mnemonic.mn_decode( seed.split(' ') )
1585 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1589 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1594 def generate_transaction_information_widget(self, tx):
1595 tabs = QTabWidget(self)
1598 grid_ui = QGridLayout(tab1)
1599 grid_ui.setColumnStretch(0,1)
1600 tabs.addTab(tab1, _('Outputs') )
1602 tree_widget = MyTreeWidget(self)
1603 tree_widget.setColumnCount(2)
1604 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1605 tree_widget.setColumnWidth(0, 300)
1606 tree_widget.setColumnWidth(1, 50)
1608 for output in tx.d["outputs"]:
1609 item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
1610 tree_widget.addTopLevelItem(item)
1612 tree_widget.setMaximumHeight(100)
1614 grid_ui.addWidget(tree_widget)
1617 grid_ui = QGridLayout(tab2)
1618 grid_ui.setColumnStretch(0,1)
1619 tabs.addTab(tab2, _('Inputs') )
1621 tree_widget = MyTreeWidget(self)
1622 tree_widget.setColumnCount(2)
1623 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1625 for input_line in tx.inputs:
1626 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1627 tree_widget.addTopLevelItem(item)
1629 tree_widget.setMaximumHeight(100)
1631 grid_ui.addWidget(tree_widget)
1635 def tx_dict_from_text(self, txt):
1637 tx_dict = json.loads(str(txt))
1638 assert "hex" in tx_dict.keys()
1639 assert "complete" in tx_dict.keys()
1640 if not tx_dict["complete"]:
1641 assert "input_info" in tx_dict.keys()
1643 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1648 def read_tx_from_file(self):
1649 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1653 with open(fileName, "r") as f:
1654 file_content = f.read()
1655 except (ValueError, IOError, os.error), reason:
1656 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1658 return self.tx_dict_from_text(file_content)
1661 def sign_raw_transaction(self, tx, input_info):
1662 if self.wallet.use_encryption:
1663 password = self.password_dialog()
1670 self.wallet.signrawtransaction(tx, input_info, [], password)
1672 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1674 with open(fileName, "w+") as f:
1675 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1676 self.show_message(_("Transaction saved succesfully"))
1677 except BaseException, e:
1678 self.show_message(str(e))
1681 def create_sign_transaction_window(self, tx_dict):
1682 tx = Transaction(tx_dict["hex"])
1684 dialog = QDialog(self)
1685 dialog.setMinimumWidth(500)
1686 dialog.setWindowTitle(_('Sign unsigned transaction'))
1689 vbox = QVBoxLayout()
1690 dialog.setLayout(vbox)
1691 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1693 if tx_dict["complete"] == True:
1694 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1696 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1697 vbox.addLayout(ok_cancel_buttons(dialog))
1698 input_info = json.loads(tx_dict["input_info"])
1701 self.sign_raw_transaction(tx, input_info)
1705 def do_sign_from_text(self):
1706 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1709 tx_dict = self.tx_dict_from_text(unicode(txt))
1711 self.create_sign_transaction_window(tx_dict)
1714 def do_sign_from_file(self):
1715 tx_dict = self.read_tx_from_file()
1717 self.create_sign_transaction_window(tx_dict)
1720 def send_raw_transaction(self, raw_tx):
1721 result, result_message = self.wallet.sendtx( raw_tx )
1723 self.show_message("Transaction succesfully sent: %s" % (result_message))
1725 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1728 def create_send_transaction_window(self, tx_dict):
1729 tx = Transaction(tx_dict["hex"])
1731 dialog = QDialog(self)
1732 dialog.setMinimumWidth(500)
1733 dialog.setWindowTitle(_('Send raw transaction'))
1736 vbox = QVBoxLayout()
1737 dialog.setLayout(vbox)
1738 vbox.addWidget( self.generate_transaction_information_widget(tx))
1740 if tx_dict["complete"] == False:
1741 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1743 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1744 vbox.addLayout(ok_cancel_buttons(dialog))
1747 self.send_raw_transaction(tx_dict["hex"])
1750 def do_send_from_file(self):
1751 tx_dict = self.read_tx_from_file()
1753 self.create_send_transaction_window(tx_dict)
1756 def do_send_from_text(self):
1757 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1760 tx_dict = self.tx_dict_from_text(unicode(txt))
1762 self.create_send_transaction_window(tx_dict)
1765 def do_export_privkeys(self):
1766 self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
1768 if self.wallet.use_encryption:
1769 password = self.password_dialog()
1775 select_export = _('Select file to export your private keys to')
1776 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1778 with open(fileName, "w+") as csvfile:
1779 transaction = csv.writer(csvfile)
1780 transaction.writerow(["address", "private_key"])
1783 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1784 transaction.writerow(["%34s"%addr,pk])
1786 self.show_message(_("Private keys exported."))
1788 except (IOError, os.error), reason:
1789 export_error_label = _("Electrum was unable to produce a private key-export.")
1790 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1792 except BaseException, e:
1793 self.show_message(str(e))
1797 def do_import_labels(self):
1798 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1799 if not labelsFile: return
1801 f = open(labelsFile, 'r')
1804 for key, value in json.loads(data).items():
1805 self.wallet.labels[key] = value
1807 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1808 except (IOError, os.error), reason:
1809 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1813 def do_export_labels(self):
1814 labels = self.wallet.labels
1816 labelsFile = util.user_dir() + '/labels.dat'
1817 f = open(labelsFile, 'w+')
1818 json.dump(labels, f)
1820 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1821 except (IOError, os.error), reason:
1822 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1824 def do_export_history(self):
1825 from gui_lite import csv_transaction
1826 csv_transaction(self.wallet)
1828 def do_import_privkey(self):
1829 if not self.wallet.imported_keys:
1830 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1831 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1832 + _('Are you sure you understand what you are doing?'), 3, 4)
1835 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1837 sec = str(text).strip()
1838 if self.wallet.use_encryption:
1839 password = self.password_dialog()
1845 addr = self.wallet.import_key(sec, password)
1847 QMessageBox.critical(None, _("Unable to import key"), "error")
1849 QMessageBox.information(None, _("Key imported"), addr)
1850 self.update_receive_tab()
1851 self.update_history_tab()
1852 except BaseException as e:
1853 QMessageBox.critical(None, _("Unable to import key"), str(e))
1855 def settings_dialog(self):
1857 d.setWindowTitle(_('Electrum Settings'))
1859 vbox = QVBoxLayout()
1861 tabs = QTabWidget(self)
1862 vbox.addWidget(tabs)
1865 grid_ui = QGridLayout(tab1)
1866 grid_ui.setColumnStretch(0,1)
1867 tabs.addTab(tab1, _('Display') )
1869 nz_label = QLabel(_('Display zeros'))
1870 grid_ui.addWidget(nz_label, 3, 0)
1872 nz_e.setText("%d"% self.wallet.num_zeros)
1873 grid_ui.addWidget(nz_e, 3, 1)
1874 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1875 grid_ui.addWidget(HelpButton(msg), 3, 2)
1876 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1877 if not self.config.is_modifiable('num_zeros'):
1878 for w in [nz_e, nz_label]: w.setEnabled(False)
1880 lang_label=QLabel(_('Language') + ':')
1881 grid_ui.addWidget(lang_label , 8, 0)
1882 lang_combo = QComboBox()
1883 from i18n import languages
1884 lang_combo.addItems(languages.values())
1886 index = languages.keys().index(self.config.get("language",''))
1889 lang_combo.setCurrentIndex(index)
1890 grid_ui.addWidget(lang_combo, 8, 1)
1891 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1892 if not self.config.is_modifiable('language'):
1893 for w in [lang_combo, lang_label]: w.setEnabled(False)
1895 currencies = self.exchanger.get_currencies()
1896 currencies.insert(0, "None")
1898 cur_label=QLabel(_('Currency') + ':')
1899 grid_ui.addWidget(cur_label , 9, 0)
1900 cur_combo = QComboBox()
1901 cur_combo.addItems(currencies)
1903 index = currencies.index(self.config.get('currency', "None"))
1906 cur_combo.setCurrentIndex(index)
1907 grid_ui.addWidget(cur_combo, 9, 1)
1908 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1910 view_label=QLabel(_('Receive Tab') + ':')
1911 grid_ui.addWidget(view_label , 10, 0)
1912 view_combo = QComboBox()
1913 view_combo.addItems([_('Simple'), _('Advanced')])
1914 view_combo.setCurrentIndex(self.expert_mode)
1915 grid_ui.addWidget(view_combo, 10, 1)
1916 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1917 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1918 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1920 grid_ui.addWidget(HelpButton(hh), 10, 2)
1924 grid_wallet = QGridLayout(tab2)
1925 grid_wallet.setColumnStretch(0,1)
1926 tabs.addTab(tab2, _('Wallet') )
1928 fee_label = QLabel(_('Transaction fee'))
1929 grid_wallet.addWidget(fee_label, 0, 0)
1931 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1932 grid_wallet.addWidget(fee_e, 0, 1)
1933 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1934 + _('Recommended value') + ': 0.001'
1935 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1936 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1937 if not self.config.is_modifiable('fee'):
1938 for w in [fee_e, fee_label]: w.setEnabled(False)
1940 usechange_label = QLabel(_('Use change addresses'))
1941 grid_wallet.addWidget(usechange_label, 1, 0)
1942 usechange_combo = QComboBox()
1943 usechange_combo.addItems([_('Yes'), _('No')])
1944 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1945 grid_wallet.addWidget(usechange_combo, 1, 1)
1946 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1947 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1949 gap_label = QLabel(_('Gap limit'))
1950 grid_wallet.addWidget(gap_label, 2, 0)
1952 gap_e.setText("%d"% self.wallet.gap_limit)
1953 grid_wallet.addWidget(gap_e, 2, 1)
1954 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1955 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1956 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1957 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1958 + _('Warning') + ': ' \
1959 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1960 + _('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'
1961 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1962 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1963 if not self.config.is_modifiable('gap_limit'):
1964 for w in [gap_e, gap_label]: w.setEnabled(False)
1966 grid_wallet.setRowStretch(3,1)
1971 grid_io = QGridLayout(tab3)
1972 grid_io.setColumnStretch(0,1)
1973 tabs.addTab(tab3, _('Import/Export') )
1975 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1976 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1977 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1978 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1980 grid_io.addWidget(QLabel(_('History')), 2, 0)
1981 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1982 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1984 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1986 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1987 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1988 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1990 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1991 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1992 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1993 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1994 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1996 grid_io.setRowStretch(4,1)
1999 grid_raw = QGridLayout(tab4)
2000 grid_raw.setColumnStretch(0,1)
2001 tabs.addTab(tab4, _('Raw tx') ) # move this to wallet tab
2003 if self.wallet.seed:
2004 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2005 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2006 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2007 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2009 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2010 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2011 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2012 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2013 grid_raw.setRowStretch(3,1)
2017 grid_plugins = QGridLayout(tab5)
2018 grid_plugins.setColumnStretch(0,1)
2019 tabs.addTab(tab5, _('Plugins') )
2020 def mk_toggle(cb, p):
2021 return lambda: cb.setChecked(p.toggle(self))
2022 for i, p in enumerate(self.wallet.plugins):
2024 name, description = p.get_info()
2025 cb = QCheckBox(name)
2026 cb.setChecked(p.is_enabled())
2027 cb.stateChanged.connect(mk_toggle(cb,p))
2028 grid_plugins.addWidget(cb, i, 0)
2029 grid_plugins.addWidget(HelpButton(description), i, 2)
2031 print_msg("Error: cannot display plugin", p)
2032 traceback.print_exc(file=sys.stdout)
2034 grid_plugins.setRowStretch(i+1,1)
2036 vbox.addLayout(ok_cancel_buttons(d))
2040 if not d.exec_(): return
2042 fee = unicode(fee_e.text())
2044 fee = int( 100000000 * Decimal(fee) )
2046 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2049 if self.wallet.fee != fee:
2050 self.wallet.fee = fee
2053 nz = unicode(nz_e.text())
2058 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2061 if self.wallet.num_zeros != nz:
2062 self.wallet.num_zeros = nz
2063 self.config.set_key('num_zeros', nz, True)
2064 self.update_history_tab()
2065 self.update_receive_tab()
2067 usechange_result = usechange_combo.currentIndex() == 0
2068 if self.wallet.use_change != usechange_result:
2069 self.wallet.use_change = usechange_result
2070 self.config.set_key('use_change', self.wallet.use_change, True)
2073 n = int(gap_e.text())
2075 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2078 if self.wallet.gap_limit != n:
2079 r = self.wallet.change_gap_limit(n)
2081 self.update_receive_tab()
2082 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2084 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2086 need_restart = False
2088 lang_request = languages.keys()[lang_combo.currentIndex()]
2089 if lang_request != self.config.get('language'):
2090 self.config.set_key("language", lang_request, True)
2093 cur_request = str(currencies[cur_combo.currentIndex()])
2094 if cur_request != self.config.get('currency', "None"):
2095 self.config.set_key('currency', cur_request, True)
2096 self.update_wallet()
2099 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2101 self.receive_tab_set_mode(view_combo.currentIndex())
2105 def network_dialog(wallet, parent=None):
2106 interface = wallet.interface
2108 if interface.is_connected:
2109 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2111 status = _("Not connected")
2112 server = interface.server
2115 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2116 server = interface.server
2118 plist, servers_list = interface.get_servers_list()
2122 d.setWindowTitle(_('Server'))
2123 d.setMinimumSize(375, 20)
2125 vbox = QVBoxLayout()
2128 hbox = QHBoxLayout()
2130 l.setPixmap(QPixmap(":icons/network.png"))
2133 hbox.addWidget(QLabel(status))
2135 vbox.addLayout(hbox)
2139 grid = QGridLayout()
2141 vbox.addLayout(grid)
2144 server_protocol = QComboBox()
2145 server_host = QLineEdit()
2146 server_host.setFixedWidth(200)
2147 server_port = QLineEdit()
2148 server_port.setFixedWidth(60)
2150 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2151 protocol_letters = 'thsg'
2152 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2153 server_protocol.addItems(protocol_names)
2155 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2156 grid.addWidget(server_protocol, 0, 1)
2157 grid.addWidget(server_host, 0, 2)
2158 grid.addWidget(server_port, 0, 3)
2160 def change_protocol(p):
2161 protocol = protocol_letters[p]
2162 host = unicode(server_host.text())
2163 pp = plist.get(host,DEFAULT_PORTS)
2164 if protocol not in pp.keys():
2165 protocol = pp.keys()[0]
2167 server_host.setText( host )
2168 server_port.setText( port )
2170 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2172 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2173 servers_list_widget = QTreeWidget(parent)
2174 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2175 servers_list_widget.setMaximumHeight(150)
2176 servers_list_widget.setColumnWidth(0, 240)
2177 for _host in servers_list.keys():
2178 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2179 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2181 def change_server(host, protocol=None):
2182 pp = plist.get(host,DEFAULT_PORTS)
2184 port = pp.get(protocol)
2185 if not port: protocol = None
2188 if 't' in pp.keys():
2190 port = pp.get(protocol)
2192 protocol = pp.keys()[0]
2193 port = pp.get(protocol)
2195 server_host.setText( host )
2196 server_port.setText( port )
2197 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2199 if not plist: return
2200 for p in protocol_letters:
2201 i = protocol_letters.index(p)
2202 j = server_protocol.model().index(i,0)
2203 if p not in pp.keys():
2204 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2206 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2210 host, port, protocol = server.split(':')
2211 change_server(host,protocol)
2213 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2214 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2216 if not wallet.config.is_modifiable('server'):
2217 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2220 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2221 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2222 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2223 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2226 proxy_mode = QComboBox()
2227 proxy_host = QLineEdit()
2228 proxy_host.setFixedWidth(200)
2229 proxy_port = QLineEdit()
2230 proxy_port.setFixedWidth(60)
2231 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2233 def check_for_disable(index = False):
2234 if proxy_mode.currentText() != 'NONE':
2235 proxy_host.setEnabled(True)
2236 proxy_port.setEnabled(True)
2238 proxy_host.setEnabled(False)
2239 proxy_port.setEnabled(False)
2242 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2244 if not wallet.config.is_modifiable('proxy'):
2245 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2247 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2248 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2249 proxy_host.setText(proxy_config.get("host"))
2250 proxy_port.setText(proxy_config.get("port"))
2252 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2253 grid.addWidget(proxy_mode, 2, 1)
2254 grid.addWidget(proxy_host, 2, 2)
2255 grid.addWidget(proxy_port, 2, 3)
2258 vbox.addLayout(ok_cancel_buttons(d))
2261 if not d.exec_(): return
2263 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2264 if proxy_mode.currentText() != 'NONE':
2265 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2269 wallet.config.set_key("proxy", proxy, True)
2270 wallet.config.set_key("server", server, True)
2271 interface.set_server(server, proxy)
2272 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2275 def closeEvent(self, event):
2277 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2278 self.save_column_widths()
2279 self.config.set_key("column-widths", self.column_widths, True)
2280 self.config.set_key("console-history",self.console.history[-50:])
2286 def __init__(self, wallet, config, app=None):
2287 self.wallet = wallet
2288 self.config = config
2290 self.app = QApplication(sys.argv)
2293 def restore_or_create(self):
2294 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2295 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2296 if r==2: return None
2297 return 'restore' if r==1 else 'create'
2299 def seed_dialog(self):
2300 return ElectrumWindow.seed_dialog( self.wallet )
2302 def network_dialog(self):
2303 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2306 def show_seed(self):
2307 ElectrumWindow.show_seed_dialog(self.wallet)
2310 def password_dialog(self):
2311 ElectrumWindow.change_password_dialog(self.wallet)
2314 def restore_wallet(self):
2315 wallet = self.wallet
2316 # wait until we are connected, because the user might have selected another server
2317 if not wallet.interface.is_connected:
2318 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2319 waiting_dialog(waiting)
2321 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2322 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2324 wallet.set_up_to_date(False)
2325 wallet.interface.poke('synchronizer')
2326 waiting_dialog(waiting)
2327 if wallet.is_found():
2328 print_error( "Recovery successful" )
2330 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2337 w = ElectrumWindow(self.wallet, self.config)
2338 if url: w.set_url(url)