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
21 from util import print_error
22 import os.path, json, util
27 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
32 import PyQt4.QtGui as QtGui
33 from interface import DEFAULT_SERVERS
38 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
40 from wallet import format_satoshis
41 import bmp, mnemonic, pyqrnative, qrscanner
44 from decimal import Decimal
52 if platform.system() == 'Windows':
53 MONOSPACE_FONT = 'Lucida Console'
54 elif platform.system() == 'Darwin':
55 MONOSPACE_FONT = 'Monaco'
57 MONOSPACE_FONT = 'monospace'
59 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
61 from version import ELECTRUM_VERSION
64 class UpdateLabel(QtGui.QLabel):
65 def __init__(self, config, parent=None):
66 QtGui.QLabel.__init__(self, parent)
67 self.new_version = False
70 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
71 con.request("GET", "/version")
72 res = con.getresponse()
73 except socket.error as msg:
74 print_error("Could not retrieve version information")
78 self.latest_version = res.read()
79 self.latest_version = self.latest_version.replace("\n","")
80 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
82 self.current_version = ELECTRUM_VERSION
83 if(self.compare_versions(self.latest_version, self.current_version) == 1):
84 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
85 if(self.compare_versions(self.latest_version, latest_seen) == 1):
86 self.new_version = True
87 self.setText(_("New version available") + ": " + self.latest_version)
90 def compare_versions(self, version1, version2):
92 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
93 return cmp(normalize(version1), normalize(version2))
95 def ignore_this_version(self):
97 self.config.set_key("last_seen_version", self.latest_version, True)
98 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
101 def ignore_all_version(self):
103 self.config.set_key("last_seen_version", "9.9.9", True)
104 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
107 def open_website(self):
108 webbrowser.open("http://electrum.org/download.html")
111 def mouseReleaseEvent(self, event):
112 dialog = QDialog(self)
113 dialog.setWindowTitle(_('Electrum update'))
116 main_layout = QGridLayout()
117 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
119 ignore_version = QPushButton(_("Ignore this version"))
120 ignore_version.clicked.connect(self.ignore_this_version)
122 ignore_all_versions = QPushButton(_("Ignore all versions"))
123 ignore_all_versions.clicked.connect(self.ignore_all_version)
125 open_website = QPushButton(_("Goto download page"))
126 open_website.clicked.connect(self.open_website)
128 main_layout.addWidget(ignore_version, 1, 0)
129 main_layout.addWidget(ignore_all_versions, 1, 1)
130 main_layout.addWidget(open_website, 1, 2)
132 dialog.setLayout(main_layout)
136 if not dialog.exec_(): return
138 def numbify(entry, is_int = False):
139 text = unicode(entry.text()).strip()
140 pos = entry.cursorPosition()
142 if not is_int: chars +='.'
143 s = ''.join([i for i in text if i in chars])
147 s = s.replace('.','')
148 s = s[:p] + '.' + s[p:p+8]
150 amount = int( Decimal(s) * 100000000 )
159 entry.setCursorPosition(pos)
163 class Timer(QtCore.QThread):
166 self.emit(QtCore.SIGNAL('timersignal'))
169 class HelpButton(QPushButton):
170 def __init__(self, text):
171 QPushButton.__init__(self, '?')
172 self.setFocusPolicy(Qt.NoFocus)
173 self.setFixedWidth(20)
174 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
177 class EnterButton(QPushButton):
178 def __init__(self, text, func):
179 QPushButton.__init__(self, text)
181 self.clicked.connect(func)
183 def keyPressEvent(self, e):
184 if e.key() == QtCore.Qt.Key_Return:
187 class MyTreeWidget(QTreeWidget):
188 def __init__(self, parent):
189 QTreeWidget.__init__(self, parent)
192 for i in range(0,self.viewport().height()/5):
193 if self.itemAt(QPoint(0,i*5)) == item:
197 for j in range(0,30):
198 if self.itemAt(QPoint(0,i*5 + j)) != item:
200 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
202 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
207 class StatusBarButton(QPushButton):
208 def __init__(self, icon, tooltip, func):
209 QPushButton.__init__(self, icon, '')
210 self.setToolTip(tooltip)
212 self.setMaximumWidth(25)
213 self.clicked.connect(func)
216 def keyPressEvent(self, e):
217 if e.key() == QtCore.Qt.Key_Return:
221 class QRCodeWidget(QWidget):
223 def __init__(self, data = None, size=4):
224 QWidget.__init__(self)
225 self.setMinimumSize(210, 210)
233 def set_addr(self, addr):
234 if self.addr != addr:
240 if self.addr and not self.qr:
241 self.qr = pyqrnative.QRCode(self.size, pyqrnative.QRErrorCorrectLevel.L)
242 self.qr.addData(self.addr)
246 def paintEvent(self, e):
251 black = QColor(0, 0, 0, 255)
252 white = QColor(255, 255, 255, 255)
255 qp = QtGui.QPainter()
259 qp.drawRect(0, 0, 198, 198)
263 k = self.qr.getModuleCount()
264 qp = QtGui.QPainter()
267 boxsize = min(r.width(), r.height())*0.8/k
269 left = (r.width() - size)/2
270 top = (r.height() - size)/2
274 if self.qr.isDark(r, c):
280 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
285 class QR_Window(QWidget):
287 def __init__(self, exchanger):
288 QWidget.__init__(self)
289 self.exchanger = exchanger
290 self.setWindowTitle('Electrum - '+_('Invoice'))
291 self.setMinimumSize(800, 250)
295 self.setFocusPolicy(QtCore.Qt.NoFocus)
297 main_box = QHBoxLayout()
299 self.qrw = QRCodeWidget()
300 main_box.addWidget(self.qrw, 1)
303 main_box.addLayout(vbox)
305 self.address_label = QLabel("")
306 self.address_label.setFont(QFont(MONOSPACE_FONT))
307 vbox.addWidget(self.address_label)
309 self.label_label = QLabel("")
310 vbox.addWidget(self.label_label)
312 self.amount_label = QLabel("")
313 vbox.addWidget(self.amount_label)
316 self.setLayout(main_box)
319 def set_content(self, addr, label, amount, currency):
321 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
322 self.address_label.setText(address_text)
324 if currency == 'BTC': currency = None
328 self.amount = Decimal(amount) / self.exchanger.exchange(1, currency) if currency else amount
330 self.amount = Decimal(amount)
331 self.amount = self.amount.quantize(Decimal('1.0000'))
334 amount_text += "<span style='font-size: 18pt'>%s %s</span><br/>" % (amount, currency)
335 amount_text += "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % str(self.amount)
336 self.amount_label.setText(amount_text)
339 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
340 self.label_label.setText(label_text)
342 msg = 'bitcoin:'+self.address
343 if self.amount is not None:
344 msg += '?amount=%s'%(str( self.amount))
345 if self.label is not None:
346 msg += '&label=%s'%(self.label)
347 elif self.label is not None:
348 msg += '?label=%s'%(self.label)
350 self.qrw.set_addr( msg )
355 def waiting_dialog(f):
361 w.setWindowTitle('Electrum')
371 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
376 def ok_cancel_buttons(dialog):
379 b = QPushButton("OK")
381 b.clicked.connect(dialog.accept)
382 b = QPushButton("Cancel")
384 b.clicked.connect(dialog.reject)
388 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
389 "receive":[[310],[50,310,200,130,130],[50,310,200,130,130]] }
391 class ElectrumWindow(QMainWindow):
393 def __init__(self, wallet, config):
394 QMainWindow.__init__(self)
398 self.wallet.interface.register_callback('updated', self.update_callback)
399 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
400 self.wallet.interface.register_callback('disconnected', self.update_callback)
401 self.wallet.interface.register_callback('disconnecting', self.update_callback)
403 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
404 self.merchant_name = config.get('merchant_name', 'Invoice')
406 self.qr_window = None
407 self.funds_error = False
408 self.completions = QStringListModel()
410 self.tabs = tabs = QTabWidget(self)
411 self.column_widths = self.config.get("column-widths", default_column_widths )
412 tabs.addTab(self.create_history_tab(), _('History') )
413 tabs.addTab(self.create_send_tab(), _('Send') )
414 tabs.addTab(self.create_receive_tab(), _('Receive') )
415 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
416 tabs.addTab(self.create_console_tab(), _('Console') )
417 tabs.setMinimumSize(600, 400)
418 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
419 self.setCentralWidget(tabs)
420 self.create_status_bar()
422 g = self.config.get("winpos-qt",[100, 100, 840, 400])
423 self.setGeometry(g[0], g[1], g[2], g[3])
424 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
425 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
426 self.setWindowTitle( title )
428 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
429 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
430 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
431 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
433 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
434 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
435 self.history_list.setFocus(True)
437 self.exchanger = exchange_rate.Exchanger(self)
438 self.toggle_QR_window(self.receive_tab_mode == 2)
439 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
441 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
442 if platform.system() == 'Windows':
443 n = 3 if self.wallet.seed else 2
444 tabs.setCurrentIndex (n)
445 tabs.setCurrentIndex (0)
447 # set initial message
448 self.console.showMessage(self.wallet.banner)
451 QMainWindow.close(self)
453 self.qr_window.close()
454 self.qr_window = None
456 def connect_slots(self, sender):
457 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
458 self.previous_payto_e=''
460 def timer_actions(self):
462 self.qr_window.qrw.update_qr()
464 if self.payto_e.hasFocus():
466 r = unicode( self.payto_e.text() )
467 if r != self.previous_payto_e:
468 self.previous_payto_e = r
470 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
472 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
476 s = r + ' <' + to_address + '>'
477 self.payto_e.setText(s)
480 def update_callback(self):
481 self.emit(QtCore.SIGNAL('updatesignal'))
483 def update_wallet(self):
484 if self.wallet.interface and self.wallet.interface.is_connected:
485 if not self.wallet.up_to_date:
486 text = _("Synchronizing...")
487 icon = QIcon(":icons/status_waiting.png")
489 c, u = self.wallet.get_balance()
490 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
491 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
492 text += self.create_quote_text(Decimal(c+u)/100000000)
493 icon = QIcon(":icons/status_connected.png")
495 text = _("Not connected")
496 icon = QIcon(":icons/status_disconnected.png")
498 self.status_text = text
499 self.statusBar().showMessage(text)
500 self.status_button.setIcon( icon )
502 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
503 self.update_history_tab()
504 self.update_receive_tab()
505 self.update_contacts_tab()
506 self.update_completions()
509 def create_quote_text(self, btc_balance):
510 quote_currency = self.config.get("currency", "None")
511 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
512 if quote_balance is None:
515 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
518 def create_history_tab(self):
519 self.history_list = l = MyTreeWidget(self)
521 for i,width in enumerate(self.column_widths['history']):
522 l.setColumnWidth(i, width)
523 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
524 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
525 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
527 l.setContextMenuPolicy(Qt.CustomContextMenu)
528 l.customContextMenuRequested.connect(self.create_history_menu)
532 def create_history_menu(self, position):
533 self.history_list.selectedIndexes()
534 item = self.history_list.currentItem()
536 tx_hash = str(item.data(0, Qt.UserRole).toString())
537 if not tx_hash: return
539 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
540 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
541 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
542 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
545 def tx_details(self, tx_hash):
546 dialog = QDialog(None)
548 dialog.setWindowTitle(_("Transaction Details"))
550 main_text = QTextEdit()
551 main_text.setText(self.wallet.get_tx_details(tx_hash))
552 main_text.setReadOnly(True)
553 main_text.setMinimumSize(550,275)
555 ok_button = QPushButton(_("OK"))
556 ok_button.setDefault(True)
557 ok_button.clicked.connect(dialog.accept)
561 hbox.addWidget(ok_button)
564 vbox.addWidget(main_text)
566 dialog.setLayout(vbox)
569 def tx_label_clicked(self, item, column):
570 if column==2 and item.isSelected():
572 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
573 self.history_list.editItem( item, column )
574 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
577 def tx_label_changed(self, item, column):
581 tx_hash = str(item.data(0, Qt.UserRole).toString())
582 tx = self.wallet.transactions.get(tx_hash)
583 s = self.wallet.labels.get(tx_hash)
584 text = unicode( item.text(2) )
586 self.wallet.labels[tx_hash] = text
587 item.setForeground(2, QBrush(QColor('black')))
589 if s: self.wallet.labels.pop(tx_hash)
590 text = self.wallet.get_default_label(tx_hash)
591 item.setText(2, text)
592 item.setForeground(2, QBrush(QColor('gray')))
596 def edit_label(self, is_recv):
597 l = self.receive_list if is_recv else self.contacts_list
598 c = 2 if is_recv else 1
599 item = l.currentItem()
600 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
601 l.editItem( item, c )
602 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
604 def edit_amount(self):
605 l = self.receive_list
606 item = l.currentItem()
607 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
608 l.editItem( item, 3 )
609 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
612 def address_label_clicked(self, item, column, l, column_addr, column_label):
613 if column == column_label and item.isSelected():
614 addr = unicode( item.text(column_addr) )
615 label = unicode( item.text(column_label) )
616 if label in self.wallet.aliases.keys():
618 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
619 l.editItem( item, column )
620 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
623 def address_label_changed(self, item, column, l, column_addr, column_label):
625 if column == column_label:
626 addr = unicode( item.text(column_addr) )
627 text = unicode( item.text(column_label) )
631 if text not in self.wallet.aliases.keys():
632 old_addr = self.wallet.labels.get(text)
634 self.wallet.labels[addr] = text
637 print_error("Error: This is one of your aliases")
638 label = self.wallet.labels.get(addr,'')
639 item.setText(column_label, QString(label))
641 s = self.wallet.labels.get(addr)
643 self.wallet.labels.pop(addr)
647 self.update_history_tab()
648 self.update_completions()
650 self.recv_changed(item)
653 address = str( item.text(column_addr) )
654 text = str( item.text(3) )
656 index = self.wallet.addresses.index(address)
660 text = text.strip().upper()
661 m = re.match('^(\d+(|\.\d*))\s*(|BTC|EUR|USD|GBP|CNY|JPY|RUB|BRL)$', text)
664 currency = m.group(3)
668 currency = currency.upper()
669 self.wallet.requested_amounts[address] = (amount, currency)
671 label = self.wallet.labels.get(address)
673 label = self.merchant_name + ' - %04d'%(index+1)
674 self.wallet.labels[address] = label
677 self.qr_window.set_content( address, label, amount, currency )
681 if address in self.wallet.requested_amounts:
682 self.wallet.requested_amounts.pop(address)
684 self.update_receive_item(self.receive_list.currentItem())
687 def recv_changed(self, a):
688 "current item changed"
689 if a is not None and self.qr_window and self.qr_window.isVisible():
690 address = str(a.text(1))
691 label = self.wallet.labels.get(address)
693 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
695 amount, currency = None, None
696 self.qr_window.set_content( address, label, amount, currency )
699 def update_history_tab(self):
701 self.history_list.clear()
702 for item in self.wallet.get_tx_history():
703 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
706 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
712 icon = QIcon(":icons/unconfirmed.png")
714 icon = QIcon(":icons/clock%d.png"%conf)
716 icon = QIcon(":icons/confirmed.png")
719 icon = QIcon(":icons/unconfirmed.png")
721 if value is not None:
722 v_str = format_satoshis(value, True, self.wallet.num_zeros)
726 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
729 label, is_default_label = self.wallet.get_label(tx_hash)
731 label = _('Pruned transaction outputs')
732 is_default_label = False
734 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
735 item.setFont(2, QFont(MONOSPACE_FONT))
736 item.setFont(3, QFont(MONOSPACE_FONT))
737 item.setFont(4, QFont(MONOSPACE_FONT))
739 item.setForeground(3, QBrush(QColor("#BC1E1E")))
741 item.setData(0, Qt.UserRole, tx_hash)
742 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
744 item.setForeground(2, QBrush(QColor('grey')))
746 item.setIcon(0, icon)
747 self.history_list.insertTopLevelItem(0,item)
750 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
753 def create_send_tab(self):
758 grid.setColumnMinimumWidth(3,300)
759 grid.setColumnStretch(5,1)
761 self.payto_e = QLineEdit()
762 grid.addWidget(QLabel(_('Pay to')), 1, 0)
763 grid.addWidget(self.payto_e, 1, 1, 1, 3)
766 qrcode = qrscanner.scan_qr()
767 if 'address' in qrcode:
768 self.payto_e.setText(qrcode['address'])
769 if 'amount' in qrcode:
770 self.amount_e.setText(str(qrcode['amount']))
771 if 'label' in qrcode:
772 self.message_e.setText(qrcode['label'])
773 if 'message' in qrcode:
774 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
777 if qrscanner.is_available():
778 b = QPushButton(_("Scan QR code"))
779 b.clicked.connect(fill_from_qr)
780 grid.addWidget(b, 1, 5)
782 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)
784 completer = QCompleter()
785 completer.setCaseSensitivity(False)
786 self.payto_e.setCompleter(completer)
787 completer.setModel(self.completions)
789 self.message_e = QLineEdit()
790 grid.addWidget(QLabel(_('Description')), 2, 0)
791 grid.addWidget(self.message_e, 2, 1, 1, 3)
792 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)
794 self.amount_e = QLineEdit()
795 grid.addWidget(QLabel(_('Amount')), 3, 0)
796 grid.addWidget(self.amount_e, 3, 1, 1, 2)
797 grid.addWidget(HelpButton(
798 _('Amount to be sent.') + '\n\n' \
799 + _('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)
801 self.fee_e = QLineEdit()
802 grid.addWidget(QLabel(_('Fee')), 4, 0)
803 grid.addWidget(self.fee_e, 4, 1, 1, 2)
804 grid.addWidget(HelpButton(
805 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
806 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
807 + _('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)
809 b = EnterButton(_("Send"), self.do_send)
810 grid.addWidget(b, 6, 1)
812 b = EnterButton(_("Clear"),self.do_clear)
813 grid.addWidget(b, 6, 2)
815 self.payto_sig = QLabel('')
816 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
818 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
819 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
828 def entry_changed( is_fee ):
829 self.funds_error = False
830 amount = numbify(self.amount_e)
831 fee = numbify(self.fee_e)
832 if not is_fee: fee = None
835 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
837 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
840 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
841 text = self.status_text
844 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
845 self.funds_error = True
846 text = _( "Not enough funds" )
848 self.statusBar().showMessage(text)
849 self.amount_e.setPalette(palette)
850 self.fee_e.setPalette(palette)
852 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
853 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
858 def update_completions(self):
860 for addr,label in self.wallet.labels.items():
861 if addr in self.wallet.addressbook:
862 l.append( label + ' <' + addr + '>')
863 l = l + self.wallet.aliases.keys()
865 self.completions.setStringList(l)
871 label = unicode( self.message_e.text() )
872 r = unicode( self.payto_e.text() )
876 m1 = re.match(ALIAS_REGEXP, r)
877 # label or alias, with address in brackets
878 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
881 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
885 to_address = m2.group(2)
889 if not self.wallet.is_valid(to_address):
890 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
894 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
896 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
899 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
901 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
904 if self.wallet.use_encryption:
905 password = self.password_dialog()
912 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
913 except BaseException, e:
914 self.show_message(str(e))
918 h = self.wallet.send_tx(tx)
919 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
920 status, msg = self.wallet.receive_tx( h )
922 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
924 self.update_contacts_tab()
926 QMessageBox.warning(self, _('Error'), msg, _('OK'))
928 filename = 'unsigned_tx'
929 f = open(filename,'w')
931 out = json.dumps({"hex":str(tx), "complete":tx.is_complete, 'input_info':repr(tx.input_info).replace(' ','')}, indent=4)
934 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
937 def set_url(self, url):
938 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
939 self.tabs.setCurrentIndex(1)
940 label = self.wallet.labels.get(payto)
941 m_addr = label + ' <'+ payto+'>' if label else payto
942 self.payto_e.setText(m_addr)
944 self.message_e.setText(message)
945 self.amount_e.setText(amount)
947 self.set_frozen(self.payto_e,True)
948 self.set_frozen(self.amount_e,True)
949 self.set_frozen(self.message_e,True)
950 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
952 self.payto_sig.setVisible(False)
955 self.payto_sig.setVisible(False)
956 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
958 self.set_frozen(e,False)
960 def set_frozen(self,entry,frozen):
962 entry.setReadOnly(True)
963 entry.setFrame(False)
965 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
966 entry.setPalette(palette)
968 entry.setReadOnly(False)
971 palette.setColor(entry.backgroundRole(), QColor('white'))
972 entry.setPalette(palette)
975 def toggle_freeze(self,addr):
977 if addr in self.wallet.frozen_addresses:
978 self.wallet.unfreeze(addr)
980 self.wallet.freeze(addr)
981 self.update_receive_tab()
983 def toggle_priority(self,addr):
985 if addr in self.wallet.prioritized_addresses:
986 self.wallet.unprioritize(addr)
988 self.wallet.prioritize(addr)
989 self.update_receive_tab()
992 def create_list_tab(self, headers):
993 "generic tab creation method"
994 l = MyTreeWidget(self)
995 l.setColumnCount( len(headers) )
996 l.setHeaderLabels( headers )
1006 vbox.addWidget(buttons)
1008 hbox = QHBoxLayout()
1011 buttons.setLayout(hbox)
1016 def create_receive_tab(self):
1017 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
1018 l.setContextMenuPolicy(Qt.CustomContextMenu)
1019 l.customContextMenuRequested.connect(self.create_receive_menu)
1020 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
1021 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
1022 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
1023 self.receive_list = l
1024 self.receive_buttons_hbox = hbox
1029 def receive_tab_set_mode(self, i):
1030 self.save_column_widths()
1031 self.receive_tab_mode = i
1032 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
1034 self.update_receive_tab()
1035 self.toggle_QR_window(self.receive_tab_mode == 2)
1038 def save_column_widths(self):
1039 if self.receive_tab_mode == 0:
1040 widths = [ self.receive_list.columnWidth(1) ]
1043 for i in range(self.receive_list.columnCount() -1):
1044 widths.append(self.receive_list.columnWidth(i))
1045 self.column_widths["receive"][self.receive_tab_mode] = widths
1047 self.column_widths["history"] = []
1048 for i in range(self.history_list.columnCount() - 1):
1049 self.column_widths["history"].append(self.history_list.columnWidth(i))
1051 self.column_widths["contacts"] = []
1052 for i in range(self.contacts_list.columnCount() - 1):
1053 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1056 def create_contacts_tab(self):
1057 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1058 l.setContextMenuPolicy(Qt.CustomContextMenu)
1059 l.customContextMenuRequested.connect(self.create_contact_menu)
1060 for i,width in enumerate(self.column_widths['contacts']):
1061 l.setColumnWidth(i, width)
1063 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1064 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1065 self.contacts_list = l
1066 self.contacts_buttons_hbox = hbox
1067 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1072 def delete_imported_key(self, addr):
1073 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1074 self.wallet.imported_keys.pop(addr)
1075 self.update_receive_tab()
1076 self.update_history_tab()
1080 def create_receive_menu(self, position):
1081 # fixme: this function apparently has a side effect.
1082 # if it is not called the menu pops up several times
1083 #self.receive_list.selectedIndexes()
1085 item = self.receive_list.itemAt(position)
1087 addr = unicode(item.text(1))
1089 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1090 if self.receive_tab_mode == 2:
1091 menu.addAction(_("Request amount"), lambda: self.edit_amount())
1092 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode(_("Address"),"bitcoin:"+addr) )
1093 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1094 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
1095 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1096 if addr in self.wallet.imported_keys:
1097 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1099 if self.receive_tab_mode == 1:
1100 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1101 menu.addAction(t, lambda: self.toggle_freeze(addr))
1102 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1103 menu.addAction(t, lambda: self.toggle_priority(addr))
1105 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1108 def payto(self, x, is_alias):
1115 label = self.wallet.labels.get(addr)
1116 m_addr = label + ' <' + addr + '>' if label else addr
1117 self.tabs.setCurrentIndex(1)
1118 self.payto_e.setText(m_addr)
1119 self.amount_e.setFocus()
1121 def delete_contact(self, x, is_alias):
1122 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1123 if not is_alias and x in self.wallet.addressbook:
1124 self.wallet.addressbook.remove(x)
1125 if x in self.wallet.labels.keys():
1126 self.wallet.labels.pop(x)
1127 elif is_alias and x in self.wallet.aliases:
1128 self.wallet.aliases.pop(x)
1129 self.update_history_tab()
1130 self.update_contacts_tab()
1131 self.update_completions()
1133 def create_contact_menu(self, position):
1134 # fixme: this function apparently has a side effect.
1135 # if it is not called the menu pops up several times
1136 #self.contacts_list.selectedIndexes()
1138 item = self.contacts_list.itemAt(position)
1140 addr = unicode(item.text(0))
1141 label = unicode(item.text(1))
1142 is_alias = label in self.wallet.aliases.keys()
1143 x = label if is_alias else addr
1145 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1146 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1147 menu.addAction(_("View QR code"),lambda: self.show_qrcode(_("Address"),"bitcoin:"+addr))
1149 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1151 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1152 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1153 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1156 def update_receive_item(self, item):
1157 address = str( item.data(1,0).toString() )
1159 flags = self.wallet.get_address_flags(address)
1160 item.setData(0,0,flags)
1162 label = self.wallet.labels.get(address,'')
1163 item.setData(2,0,label)
1166 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1168 amount, currency = None, None
1170 amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
1171 item.setData(3,0,amount_str)
1173 c, u = self.wallet.get_addr_balance(address)
1174 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1175 item.setData(4,0,balance)
1177 if self.receive_tab_mode == 1:
1178 if address in self.wallet.frozen_addresses:
1179 item.setBackgroundColor(1, QColor('lightblue'))
1180 elif address in self.wallet.prioritized_addresses:
1181 item.setBackgroundColor(1, QColor('lightgreen'))
1184 def update_receive_tab(self):
1185 l = self.receive_list
1188 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1189 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1190 l.setColumnHidden(4, self.receive_tab_mode == 0)
1191 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1192 if self.receive_tab_mode ==0:
1193 width = self.column_widths['receive'][0][0]
1194 l.setColumnWidth(1, width)
1196 for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
1197 l.setColumnWidth(i, width)
1201 for address in self.wallet.all_addresses():
1203 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1206 h = self.wallet.history.get(address,[])
1208 if address in self.wallet.addresses:
1211 if gap > self.wallet.gap_limit:
1216 num_tx = '*' if h == ['*'] else "%d"%len(h)
1217 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1218 item.setFont(0, QFont(MONOSPACE_FONT))
1219 item.setFont(1, QFont(MONOSPACE_FONT))
1220 item.setFont(3, QFont(MONOSPACE_FONT))
1221 self.update_receive_item(item)
1222 if is_red and address in self.wallet.addresses:
1223 item.setBackgroundColor(1, QColor('red'))
1224 l.addTopLevelItem(item)
1226 # we use column 1 because column 0 may be hidden
1227 l.setCurrentItem(l.topLevelItem(0),1)
1229 def show_contact_details(self, m):
1230 a = self.wallet.aliases.get(m)
1232 if a[0] in self.wallet.authorities.keys():
1233 s = self.wallet.authorities.get(a[0])
1236 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1237 QMessageBox.information(self, 'Alias', msg, 'OK')
1239 def update_contacts_tab(self):
1241 l = self.contacts_list
1245 for alias, v in self.wallet.aliases.items():
1247 alias_targets.append(target)
1248 item = QTreeWidgetItem( [ target, alias, '-'] )
1249 item.setBackgroundColor(0, QColor('lightgray'))
1250 l.addTopLevelItem(item)
1252 for address in self.wallet.addressbook:
1253 if address in alias_targets: continue
1254 label = self.wallet.labels.get(address,'')
1256 #for item in self.wallet.transactions.values():
1257 # if address in item['outputs'] : n=n+1
1259 item = QTreeWidgetItem( [ address, label, tx] )
1260 item.setFont(0, QFont(MONOSPACE_FONT))
1261 l.addTopLevelItem(item)
1263 l.setCurrentItem(l.topLevelItem(0))
1266 def create_console_tab(self):
1267 from qt_console import Console
1268 import util, bitcoin, commands
1269 self.console = console = Console()
1270 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1271 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1273 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1275 def mkfunc(f, method):
1276 return lambda *args: apply( f, (method, args, self.password_dialog ))
1278 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1279 methods[m] = mkfunc(c._run, m)
1281 console.updateNamespace(methods)
1285 def create_status_bar(self):
1286 self.status_text = ""
1288 sb.setFixedHeight(35)
1289 qtVersion = qVersion()
1291 update_notification = UpdateLabel(self.config)
1292 if(update_notification.new_version):
1293 sb.addPermanentWidget(update_notification)
1295 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1296 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1297 if self.wallet.seed:
1298 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1299 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1300 if self.wallet.seed:
1301 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1302 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1303 sb.addPermanentWidget( self.status_button )
1305 self.setStatusBar(sb)
1309 self.config.set_key('gui', 'lite', True)
1312 self.lite.mini.show()
1314 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1315 self.lite.main(None)
1317 def new_contact_dialog(self):
1318 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1319 address = unicode(text)
1321 if self.wallet.is_valid(address):
1322 self.wallet.addressbook.append(address)
1324 self.update_contacts_tab()
1325 self.update_history_tab()
1326 self.update_completions()
1328 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1330 def show_master_public_key(self):
1331 dialog = QDialog(None)
1333 dialog.setWindowTitle(_("Master Public Key"))
1335 main_text = QTextEdit()
1336 main_text.setText(self.wallet.get_master_public_key())
1337 main_text.setReadOnly(True)
1338 main_text.setMaximumHeight(170)
1339 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1341 ok_button = QPushButton(_("OK"))
1342 ok_button.setDefault(True)
1343 ok_button.clicked.connect(dialog.accept)
1345 main_layout = QGridLayout()
1346 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1348 main_layout.addWidget(main_text, 1, 0)
1349 main_layout.addWidget(qrw, 1, 1 )
1351 vbox = QVBoxLayout()
1352 vbox.addLayout(main_layout)
1353 hbox = QHBoxLayout()
1355 hbox.addWidget(ok_button)
1356 vbox.addLayout(hbox)
1358 dialog.setLayout(vbox)
1363 def show_seed_dialog(wallet, parent=None):
1365 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1368 if wallet.use_encryption:
1369 password = parent.password_dialog()
1376 seed = wallet.decode_seed(password)
1378 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1381 dialog = QDialog(None)
1383 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1385 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1387 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1389 seed_text = QTextEdit(brainwallet)
1390 seed_text.setReadOnly(True)
1391 seed_text.setMaximumHeight(130)
1393 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1394 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1395 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1396 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1397 label2 = QLabel(msg2)
1398 label2.setWordWrap(True)
1401 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1402 logo.setMaximumWidth(60)
1404 qrw = QRCodeWidget(seed, 4)
1406 ok_button = QPushButton(_("OK"))
1407 ok_button.setDefault(True)
1408 ok_button.clicked.connect(dialog.accept)
1410 grid = QGridLayout()
1411 #main_layout.addWidget(logo, 0, 0)
1413 grid.addWidget(logo, 0, 0)
1414 grid.addWidget(label1, 0, 1)
1416 grid.addWidget(seed_text, 1, 0, 1, 2)
1418 grid.addWidget(qrw, 0, 2, 2, 1)
1420 vbox = QVBoxLayout()
1421 vbox.addLayout(grid)
1422 vbox.addWidget(label2)
1424 hbox = QHBoxLayout()
1426 hbox.addWidget(ok_button)
1427 vbox.addLayout(hbox)
1429 dialog.setLayout(vbox)
1433 def show_qrcode(title, data):
1437 d.setWindowTitle(title)
1438 d.setMinimumSize(270, 300)
1439 vbox = QVBoxLayout()
1440 qrw = QRCodeWidget(data)
1441 vbox.addWidget(qrw, 1)
1442 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1443 hbox = QHBoxLayout()
1447 filename = "qrcode.bmp"
1448 bmp.save_qrcode(qrw.qr, filename)
1449 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1451 b = QPushButton(_("Print"))
1453 b.clicked.connect(print_qr)
1455 b = QPushButton(_("Close"))
1457 b.clicked.connect(d.accept)
1459 vbox.addLayout(hbox)
1463 def view_private_key(self,address):
1464 if not address: return
1465 if self.wallet.use_encryption:
1466 password = self.password_dialog()
1473 pk = self.wallet.get_private_key(address, password)
1474 except BaseException, e:
1475 self.show_message(str(e))
1478 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1481 def sign_message(self,address):
1482 if not address: return
1485 d.setWindowTitle(_('Sign Message'))
1486 d.setMinimumSize(410, 290)
1488 tab_widget = QTabWidget()
1490 layout = QGridLayout(tab)
1492 sign_address = QLineEdit()
1494 sign_address.setText(address)
1495 layout.addWidget(QLabel(_('Address')), 1, 0)
1496 layout.addWidget(sign_address, 1, 1)
1498 sign_message = QTextEdit()
1499 layout.addWidget(QLabel(_('Message')), 2, 0)
1500 layout.addWidget(sign_message, 2, 1)
1501 layout.setRowStretch(2,3)
1503 sign_signature = QTextEdit()
1504 layout.addWidget(QLabel(_('Signature')), 3, 0)
1505 layout.addWidget(sign_signature, 3, 1)
1506 layout.setRowStretch(3,1)
1509 if self.wallet.use_encryption:
1510 password = self.password_dialog()
1517 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1518 sign_signature.setText(signature)
1519 except BaseException, e:
1520 self.show_message(str(e))
1523 hbox = QHBoxLayout()
1524 b = QPushButton(_("Sign"))
1526 b.clicked.connect(do_sign)
1527 b = QPushButton(_("Close"))
1528 b.clicked.connect(d.accept)
1530 layout.addLayout(hbox, 4, 1)
1531 tab_widget.addTab(tab, _("Sign"))
1535 layout = QGridLayout(tab)
1537 verify_address = QLineEdit()
1538 layout.addWidget(QLabel(_('Address')), 1, 0)
1539 layout.addWidget(verify_address, 1, 1)
1541 verify_message = QTextEdit()
1542 layout.addWidget(QLabel(_('Message')), 2, 0)
1543 layout.addWidget(verify_message, 2, 1)
1544 layout.setRowStretch(2,3)
1546 verify_signature = QTextEdit()
1547 layout.addWidget(QLabel(_('Signature')), 3, 0)
1548 layout.addWidget(verify_signature, 3, 1)
1549 layout.setRowStretch(3,1)
1553 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1554 self.show_message(_("Signature verified"))
1555 except BaseException, e:
1556 self.show_message(str(e))
1559 hbox = QHBoxLayout()
1560 b = QPushButton(_("Verify"))
1561 b.clicked.connect(do_verify)
1563 b = QPushButton(_("Close"))
1564 b.clicked.connect(d.accept)
1566 layout.addLayout(hbox, 4, 1)
1567 tab_widget.addTab(tab, _("Verify"))
1569 vbox = QVBoxLayout()
1570 vbox.addWidget(tab_widget)
1575 def toggle_QR_window(self, show):
1576 if show and not self.qr_window:
1577 self.qr_window = QR_Window(self.exchanger)
1578 self.qr_window.setVisible(True)
1579 self.qr_window_geometry = self.qr_window.geometry()
1580 item = self.receive_list.currentItem()
1582 address = str(item.text(1))
1583 label = self.wallet.labels.get(address)
1584 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1585 self.qr_window.set_content( address, label, amount, currency )
1587 elif show and self.qr_window and not self.qr_window.isVisible():
1588 self.qr_window.setVisible(True)
1589 self.qr_window.setGeometry(self.qr_window_geometry)
1591 elif not show and self.qr_window and self.qr_window.isVisible():
1592 self.qr_window_geometry = self.qr_window.geometry()
1593 self.qr_window.setVisible(False)
1595 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1596 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1597 self.receive_list.setColumnWidth(2, 200)
1600 def question(self, msg):
1601 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1603 def show_message(self, msg):
1604 QMessageBox.information(self, _('Message'), msg, _('OK'))
1606 def password_dialog(self ):
1613 vbox = QVBoxLayout()
1614 msg = _('Please enter your password')
1615 vbox.addWidget(QLabel(msg))
1617 grid = QGridLayout()
1619 grid.addWidget(QLabel(_('Password')), 1, 0)
1620 grid.addWidget(pw, 1, 1)
1621 vbox.addLayout(grid)
1623 vbox.addLayout(ok_cancel_buttons(d))
1626 if not d.exec_(): return
1627 return unicode(pw.text())
1634 def change_password_dialog( wallet, parent=None ):
1637 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1645 new_pw = QLineEdit()
1646 new_pw.setEchoMode(2)
1647 conf_pw = QLineEdit()
1648 conf_pw.setEchoMode(2)
1650 vbox = QVBoxLayout()
1652 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1653 +_('To disable wallet encryption, enter an empty new password.')) \
1654 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1656 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1657 +_("Leave these fields empty if you want to disable encryption.")
1658 vbox.addWidget(QLabel(msg))
1660 grid = QGridLayout()
1663 if wallet.use_encryption:
1664 grid.addWidget(QLabel(_('Password')), 1, 0)
1665 grid.addWidget(pw, 1, 1)
1667 grid.addWidget(QLabel(_('New Password')), 2, 0)
1668 grid.addWidget(new_pw, 2, 1)
1670 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1671 grid.addWidget(conf_pw, 3, 1)
1672 vbox.addLayout(grid)
1674 vbox.addLayout(ok_cancel_buttons(d))
1677 if not d.exec_(): return
1679 password = unicode(pw.text()) if wallet.use_encryption else None
1680 new_password = unicode(new_pw.text())
1681 new_password2 = unicode(conf_pw.text())
1684 seed = wallet.decode_seed(password)
1686 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1689 if new_password != new_password2:
1690 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1691 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1693 wallet.update_password(seed, password, new_password)
1696 def seed_dialog(wallet, parent=None):
1700 vbox = QVBoxLayout()
1701 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1702 vbox.addWidget(QLabel(msg))
1704 grid = QGridLayout()
1707 seed_e = QLineEdit()
1708 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1709 grid.addWidget(seed_e, 1, 1)
1713 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1714 grid.addWidget(gap_e, 2, 1)
1715 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1716 vbox.addLayout(grid)
1718 vbox.addLayout(ok_cancel_buttons(d))
1721 if not d.exec_(): return
1724 gap = int(unicode(gap_e.text()))
1726 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1730 seed = str(seed_e.text())
1733 print_error("Warning: Not hex, trying decode")
1735 seed = mnemonic.mn_decode( seed.split(' ') )
1737 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1741 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1746 def do_export_privkeys(self):
1747 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.")))
1749 if self.wallet.use_encryption:
1750 password = self.password_dialog()
1756 select_export = _('Select file to export your private keys to')
1757 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1759 with open(fileName, "w+") as csvfile:
1760 transaction = csv.writer(csvfile)
1761 transaction.writerow(["address", "private_key"])
1764 for addr, pk in self.wallet.get_private_keys(self.wallet.all_addresses(), password).items():
1765 transaction.writerow(["%34s"%addr,pk])
1767 self.show_message(_("Private keys exported."))
1769 except (IOError, os.error), reason:
1770 export_error_label = _("Electrum was unable to produce a private key-export.")
1771 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1773 except BaseException, e:
1774 self.show_message(str(e))
1778 def do_import_labels(self):
1779 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1780 if not labelsFile: return
1782 f = open(labelsFile, 'r')
1785 for key, value in json.loads(data).items():
1786 self.wallet.labels[key] = value
1788 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1789 except (IOError, os.error), reason:
1790 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1794 def do_export_labels(self):
1795 labels = self.wallet.labels
1797 labelsFile = util.user_dir() + '/labels.dat'
1798 f = open(labelsFile, 'w+')
1799 json.dump(labels, f)
1801 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1802 except (IOError, os.error), reason:
1803 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1805 def do_export_history(self):
1806 from gui_lite import csv_transaction
1807 csv_transaction(self.wallet)
1809 def do_import_privkey(self):
1810 if not self.wallet.imported_keys:
1811 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1812 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1813 + _('Are you sure you understand what you are doing?'), 3, 4)
1816 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1818 sec = str(text).strip()
1819 if self.wallet.use_encryption:
1820 password = self.password_dialog()
1826 addr = self.wallet.import_key(sec, password)
1828 QMessageBox.critical(None, _("Unable to import key"), "error")
1830 QMessageBox.information(None, _("Key imported"), addr)
1831 self.update_receive_tab()
1832 self.update_history_tab()
1833 except BaseException as e:
1834 QMessageBox.critical(None, _("Unable to import key"), str(e))
1836 def settings_dialog(self):
1838 d.setWindowTitle(_('Electrum Settings'))
1840 vbox = QVBoxLayout()
1842 tabs = QTabWidget(self)
1843 vbox.addWidget(tabs)
1846 grid_ui = QGridLayout(tab1)
1847 grid_ui.setColumnStretch(0,1)
1848 tabs.addTab(tab1, _('Display') )
1850 nz_label = QLabel(_('Display zeros'))
1851 grid_ui.addWidget(nz_label, 3, 0)
1853 nz_e.setText("%d"% self.wallet.num_zeros)
1854 grid_ui.addWidget(nz_e, 3, 1)
1855 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1856 grid_ui.addWidget(HelpButton(msg), 3, 2)
1857 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1858 if not self.config.is_modifiable('num_zeros'):
1859 for w in [nz_e, nz_label]: w.setEnabled(False)
1861 lang_label=QLabel(_('Language') + ':')
1862 grid_ui.addWidget(lang_label , 8, 0)
1863 lang_combo = QComboBox()
1864 from i18n import languages
1865 lang_combo.addItems(languages.values())
1867 index = languages.keys().index(self.config.get("language",''))
1870 lang_combo.setCurrentIndex(index)
1871 grid_ui.addWidget(lang_combo, 8, 1)
1872 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1873 if not self.config.is_modifiable('language'):
1874 for w in [lang_combo, lang_label]: w.setEnabled(False)
1876 currencies = self.exchanger.get_currencies()
1877 currencies.insert(0, "None")
1879 cur_label=QLabel(_('Currency') + ':')
1880 grid_ui.addWidget(cur_label , 9, 0)
1881 cur_combo = QComboBox()
1882 cur_combo.addItems(currencies)
1884 index = currencies.index(self.config.get('currency', "None"))
1887 cur_combo.setCurrentIndex(index)
1888 grid_ui.addWidget(cur_combo, 9, 1)
1889 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1891 view_label=QLabel(_('Receive Tab') + ':')
1892 grid_ui.addWidget(view_label , 10, 0)
1893 view_combo = QComboBox()
1894 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1895 view_combo.setCurrentIndex(self.receive_tab_mode)
1896 grid_ui.addWidget(view_combo, 10, 1)
1897 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1898 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1899 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1900 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1902 grid_ui.addWidget(HelpButton(hh), 10, 2)
1906 grid_wallet = QGridLayout(tab2)
1907 grid_wallet.setColumnStretch(0,1)
1908 tabs.addTab(tab2, _('Wallet') )
1910 fee_label = QLabel(_('Transaction fee'))
1911 grid_wallet.addWidget(fee_label, 0, 0)
1913 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1914 grid_wallet.addWidget(fee_e, 0, 1)
1915 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1916 + _('Recommended value') + ': 0.001'
1917 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1918 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1919 if not self.config.is_modifiable('fee'):
1920 for w in [fee_e, fee_label]: w.setEnabled(False)
1922 usechange_label = QLabel(_('Use change addresses'))
1923 grid_wallet.addWidget(usechange_label, 1, 0)
1924 usechange_combo = QComboBox()
1925 usechange_combo.addItems([_('Yes'), _('No')])
1926 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1927 grid_wallet.addWidget(usechange_combo, 1, 1)
1928 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1929 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1931 gap_label = QLabel(_('Gap limit'))
1932 grid_wallet.addWidget(gap_label, 2, 0)
1934 gap_e.setText("%d"% self.wallet.gap_limit)
1935 grid_wallet.addWidget(gap_e, 2, 1)
1936 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1937 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1938 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1939 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1940 + _('Warning') + ': ' \
1941 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1942 + _('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'
1943 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1944 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1945 if not self.config.is_modifiable('gap_limit'):
1946 for w in [gap_e, gap_label]: w.setEnabled(False)
1948 grid_wallet.setRowStretch(3,1)
1953 grid_io = QGridLayout(tab3)
1954 grid_io.setColumnStretch(0,1)
1955 tabs.addTab(tab3, _('Import/Export') )
1957 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1958 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1959 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1960 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1962 grid_io.addWidget(QLabel(_('History')), 2, 0)
1963 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1964 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1966 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1968 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1969 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1970 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1972 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1973 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1974 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1975 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1976 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1978 grid_io.setRowStretch(4,1)
1979 vbox.addLayout(ok_cancel_buttons(d))
1983 if not d.exec_(): return
1985 fee = unicode(fee_e.text())
1987 fee = int( 100000000 * Decimal(fee) )
1989 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1992 if self.wallet.fee != fee:
1993 self.wallet.fee = fee
1996 nz = unicode(nz_e.text())
2001 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2004 if self.wallet.num_zeros != nz:
2005 self.wallet.num_zeros = nz
2006 self.config.set_key('num_zeros', nz, True)
2007 self.update_history_tab()
2008 self.update_receive_tab()
2010 usechange_result = usechange_combo.currentIndex() == 0
2011 if self.wallet.use_change != usechange_result:
2012 self.wallet.use_change = usechange_result
2013 self.config.set_key('use_change', self.wallet.use_change, True)
2016 n = int(gap_e.text())
2018 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2021 if self.wallet.gap_limit != n:
2022 r = self.wallet.change_gap_limit(n)
2024 self.update_receive_tab()
2025 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2027 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2029 need_restart = False
2031 lang_request = languages.keys()[lang_combo.currentIndex()]
2032 if lang_request != self.config.get('language'):
2033 self.config.set_key("language", lang_request, True)
2036 cur_request = str(currencies[cur_combo.currentIndex()])
2037 if cur_request != self.config.get('currency', "None"):
2038 self.config.set_key('currency', cur_request, True)
2039 self.update_wallet()
2042 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2044 self.receive_tab_set_mode(view_combo.currentIndex())
2048 def network_dialog(wallet, parent=None):
2049 interface = wallet.interface
2051 if interface.is_connected:
2052 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2054 status = _("Not connected")
2055 server = interface.server
2058 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2059 server = interface.server
2061 plist, servers_list = interface.get_servers_list()
2065 d.setWindowTitle(_('Server'))
2066 d.setMinimumSize(375, 20)
2068 vbox = QVBoxLayout()
2071 hbox = QHBoxLayout()
2073 l.setPixmap(QPixmap(":icons/network.png"))
2076 hbox.addWidget(QLabel(status))
2078 vbox.addLayout(hbox)
2082 grid = QGridLayout()
2084 vbox.addLayout(grid)
2087 server_protocol = QComboBox()
2088 server_host = QLineEdit()
2089 server_host.setFixedWidth(200)
2090 server_port = QLineEdit()
2091 server_port.setFixedWidth(60)
2093 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2094 protocol_letters = 'thsg'
2095 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2096 server_protocol.addItems(protocol_names)
2098 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2099 grid.addWidget(server_protocol, 0, 1)
2100 grid.addWidget(server_host, 0, 2)
2101 grid.addWidget(server_port, 0, 3)
2103 def change_protocol(p):
2104 protocol = protocol_letters[p]
2105 host = unicode(server_host.text())
2106 pp = plist.get(host,DEFAULT_PORTS)
2107 if protocol not in pp.keys():
2108 protocol = pp.keys()[0]
2110 server_host.setText( host )
2111 server_port.setText( port )
2113 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2115 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2116 servers_list_widget = QTreeWidget(parent)
2117 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2118 servers_list_widget.setMaximumHeight(150)
2119 servers_list_widget.setColumnWidth(0, 240)
2120 for _host in servers_list.keys():
2121 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2122 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2124 def change_server(host, protocol=None):
2125 pp = plist.get(host,DEFAULT_PORTS)
2127 port = pp.get(protocol)
2128 if not port: protocol = None
2131 if 't' in pp.keys():
2133 port = pp.get(protocol)
2135 protocol = pp.keys()[0]
2136 port = pp.get(protocol)
2138 server_host.setText( host )
2139 server_port.setText( port )
2140 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2142 if not plist: return
2143 for p in protocol_letters:
2144 i = protocol_letters.index(p)
2145 j = server_protocol.model().index(i,0)
2146 if p not in pp.keys():
2147 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2149 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2153 host, port, protocol = server.split(':')
2154 change_server(host,protocol)
2156 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2157 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2159 if not wallet.config.is_modifiable('server'):
2160 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2163 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2164 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2165 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2166 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2169 proxy_mode = QComboBox()
2170 proxy_host = QLineEdit()
2171 proxy_host.setFixedWidth(200)
2172 proxy_port = QLineEdit()
2173 proxy_port.setFixedWidth(60)
2174 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2176 def check_for_disable(index = False):
2177 if proxy_mode.currentText() != 'NONE':
2178 proxy_host.setEnabled(True)
2179 proxy_port.setEnabled(True)
2181 proxy_host.setEnabled(False)
2182 proxy_port.setEnabled(False)
2185 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2187 if not wallet.config.is_modifiable('proxy'):
2188 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2190 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2191 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2192 proxy_host.setText(proxy_config.get("host"))
2193 proxy_port.setText(proxy_config.get("port"))
2195 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2196 grid.addWidget(proxy_mode, 2, 1)
2197 grid.addWidget(proxy_host, 2, 2)
2198 grid.addWidget(proxy_port, 2, 3)
2201 vbox.addLayout(ok_cancel_buttons(d))
2204 if not d.exec_(): return
2206 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2207 if proxy_mode.currentText() != 'NONE':
2208 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2212 wallet.config.set_key("proxy", proxy, True)
2213 wallet.config.set_key("server", server, True)
2214 interface.set_server(server, proxy)
2215 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2218 def closeEvent(self, event):
2220 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2221 self.save_column_widths()
2222 self.config.set_key("column-widths", self.column_widths, True)
2228 def __init__(self, wallet, config, app=None):
2229 self.wallet = wallet
2230 self.config = config
2232 self.app = QApplication(sys.argv)
2235 def restore_or_create(self):
2236 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2237 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2238 if r==2: return None
2239 return 'restore' if r==1 else 'create'
2241 def seed_dialog(self):
2242 return ElectrumWindow.seed_dialog( self.wallet )
2244 def network_dialog(self):
2245 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2248 def show_seed(self):
2249 ElectrumWindow.show_seed_dialog(self.wallet)
2252 def password_dialog(self):
2253 ElectrumWindow.change_password_dialog(self.wallet)
2256 def restore_wallet(self):
2257 wallet = self.wallet
2258 # wait until we are connected, because the user might have selected another server
2259 if not wallet.interface.is_connected:
2260 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2261 waiting_dialog(waiting)
2263 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2264 %(_("Please wait..."),_("Addresses generated:"),len(wallet.all_addresses()),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2266 wallet.set_up_to_date(False)
2267 wallet.interface.poke('synchronizer')
2268 waiting_dialog(waiting)
2269 if wallet.is_found():
2270 print_error( "Recovery successful" )
2272 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2279 w = ElectrumWindow(self.wallet, self.config)
2280 if url: w.set_url(url)