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_wall_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')
932 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
935 def set_url(self, url):
936 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
937 self.tabs.setCurrentIndex(1)
938 label = self.wallet.labels.get(payto)
939 m_addr = label + ' <'+ payto+'>' if label else payto
940 self.payto_e.setText(m_addr)
942 self.message_e.setText(message)
943 self.amount_e.setText(amount)
945 self.set_frozen(self.payto_e,True)
946 self.set_frozen(self.amount_e,True)
947 self.set_frozen(self.message_e,True)
948 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
950 self.payto_sig.setVisible(False)
953 self.payto_sig.setVisible(False)
954 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
956 self.set_frozen(e,False)
958 def set_frozen(self,entry,frozen):
960 entry.setReadOnly(True)
961 entry.setFrame(False)
963 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
964 entry.setPalette(palette)
966 entry.setReadOnly(False)
969 palette.setColor(entry.backgroundRole(), QColor('white'))
970 entry.setPalette(palette)
973 def toggle_freeze(self,addr):
975 if addr in self.wallet.frozen_addresses:
976 self.wallet.unfreeze(addr)
978 self.wallet.freeze(addr)
979 self.update_receive_tab()
981 def toggle_priority(self,addr):
983 if addr in self.wallet.prioritized_addresses:
984 self.wallet.unprioritize(addr)
986 self.wallet.prioritize(addr)
987 self.update_receive_tab()
990 def create_list_tab(self, headers):
991 "generic tab creation method"
992 l = MyTreeWidget(self)
993 l.setColumnCount( len(headers) )
994 l.setHeaderLabels( headers )
1004 vbox.addWidget(buttons)
1006 hbox = QHBoxLayout()
1009 buttons.setLayout(hbox)
1014 def create_receive_tab(self):
1015 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
1016 l.setContextMenuPolicy(Qt.CustomContextMenu)
1017 l.customContextMenuRequested.connect(self.create_receive_menu)
1018 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
1019 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
1020 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
1021 self.receive_list = l
1022 self.receive_buttons_hbox = hbox
1027 def receive_tab_set_mode(self, i):
1028 self.save_column_widths()
1029 self.receive_tab_mode = i
1030 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
1032 self.update_receive_tab()
1033 self.toggle_QR_window(self.receive_tab_mode == 2)
1036 def save_column_widths(self):
1037 if self.receive_tab_mode == 0:
1038 widths = [ self.receive_list.columnWidth(1) ]
1041 for i in range(self.receive_list.columnCount() -1):
1042 widths.append(self.receive_list.columnWidth(i))
1043 self.column_widths["receive"][self.receive_tab_mode] = widths
1045 self.column_widths["history"] = []
1046 for i in range(self.history_list.columnCount() - 1):
1047 self.column_widths["history"].append(self.history_list.columnWidth(i))
1049 self.column_widths["contacts"] = []
1050 for i in range(self.contacts_list.columnCount() - 1):
1051 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1054 def create_contacts_tab(self):
1055 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1056 l.setContextMenuPolicy(Qt.CustomContextMenu)
1057 l.customContextMenuRequested.connect(self.create_contact_menu)
1058 for i,width in enumerate(self.column_widths['contacts']):
1059 l.setColumnWidth(i, width)
1061 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1062 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1063 self.contacts_list = l
1064 self.contacts_buttons_hbox = hbox
1065 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1070 def delete_imported_key(self, addr):
1071 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1072 self.wallet.imported_keys.pop(addr)
1073 self.update_receive_tab()
1074 self.update_history_tab()
1078 def create_receive_menu(self, position):
1079 # fixme: this function apparently has a side effect.
1080 # if it is not called the menu pops up several times
1081 #self.receive_list.selectedIndexes()
1083 item = self.receive_list.itemAt(position)
1085 addr = unicode(item.text(1))
1087 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1088 if self.receive_tab_mode == 2:
1089 menu.addAction(_("Request amount"), lambda: self.edit_amount())
1090 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode(_("Address"),"bitcoin:"+addr) )
1091 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1092 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
1093 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1094 if addr in self.wallet.imported_keys:
1095 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1097 if self.receive_tab_mode == 1:
1098 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1099 menu.addAction(t, lambda: self.toggle_freeze(addr))
1100 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1101 menu.addAction(t, lambda: self.toggle_priority(addr))
1103 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1106 def payto(self, x, is_alias):
1113 label = self.wallet.labels.get(addr)
1114 m_addr = label + ' <' + addr + '>' if label else addr
1115 self.tabs.setCurrentIndex(1)
1116 self.payto_e.setText(m_addr)
1117 self.amount_e.setFocus()
1119 def delete_contact(self, x, is_alias):
1120 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1121 if not is_alias and x in self.wallet.addressbook:
1122 self.wallet.addressbook.remove(x)
1123 if x in self.wallet.labels.keys():
1124 self.wallet.labels.pop(x)
1125 elif is_alias and x in self.wallet.aliases:
1126 self.wallet.aliases.pop(x)
1127 self.update_history_tab()
1128 self.update_contacts_tab()
1129 self.update_completions()
1131 def create_contact_menu(self, position):
1132 # fixme: this function apparently has a side effect.
1133 # if it is not called the menu pops up several times
1134 #self.contacts_list.selectedIndexes()
1136 item = self.contacts_list.itemAt(position)
1138 addr = unicode(item.text(0))
1139 label = unicode(item.text(1))
1140 is_alias = label in self.wallet.aliases.keys()
1141 x = label if is_alias else addr
1143 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1144 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1145 menu.addAction(_("View QR code"),lambda: self.show_qrcode(_("Address"),"bitcoin:"+addr))
1147 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1149 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1150 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1151 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1154 def update_receive_item(self, item):
1155 address = str( item.data(1,0).toString() )
1157 flags = self.wallet.get_address_flags(address)
1158 item.setData(0,0,flags)
1160 label = self.wallet.labels.get(address,'')
1161 item.setData(2,0,label)
1164 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1166 amount, currency = None, None
1168 amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
1169 item.setData(3,0,amount_str)
1171 c, u = self.wallet.get_addr_balance(address)
1172 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1173 item.setData(4,0,balance)
1175 if self.receive_tab_mode == 1:
1176 if address in self.wallet.frozen_addresses:
1177 item.setBackgroundColor(1, QColor('lightblue'))
1178 elif address in self.wallet.prioritized_addresses:
1179 item.setBackgroundColor(1, QColor('lightgreen'))
1182 def update_receive_tab(self):
1183 l = self.receive_list
1186 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1187 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1188 l.setColumnHidden(4, self.receive_tab_mode == 0)
1189 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1190 if self.receive_tab_mode ==0:
1191 width = self.column_widths['receive'][0][0]
1192 l.setColumnWidth(1, width)
1194 for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
1195 l.setColumnWidth(i, width)
1199 for address in self.wallet.all_addresses():
1201 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1204 h = self.wallet.history.get(address,[])
1206 if address in self.wallet.addresses:
1209 if gap > self.wallet.gap_limit:
1214 num_tx = '*' if h == ['*'] else "%d"%len(h)
1215 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1216 item.setFont(0, QFont(MONOSPACE_FONT))
1217 item.setFont(1, QFont(MONOSPACE_FONT))
1218 item.setFont(3, QFont(MONOSPACE_FONT))
1219 self.update_receive_item(item)
1220 if is_red and address in self.wallet.addresses:
1221 item.setBackgroundColor(1, QColor('red'))
1222 l.addTopLevelItem(item)
1224 # we use column 1 because column 0 may be hidden
1225 l.setCurrentItem(l.topLevelItem(0),1)
1227 def show_contact_details(self, m):
1228 a = self.wallet.aliases.get(m)
1230 if a[0] in self.wallet.authorities.keys():
1231 s = self.wallet.authorities.get(a[0])
1234 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1235 QMessageBox.information(self, 'Alias', msg, 'OK')
1237 def update_contacts_tab(self):
1239 l = self.contacts_list
1243 for alias, v in self.wallet.aliases.items():
1245 alias_targets.append(target)
1246 item = QTreeWidgetItem( [ target, alias, '-'] )
1247 item.setBackgroundColor(0, QColor('lightgray'))
1248 l.addTopLevelItem(item)
1250 for address in self.wallet.addressbook:
1251 if address in alias_targets: continue
1252 label = self.wallet.labels.get(address,'')
1254 #for item in self.wallet.transactions.values():
1255 # if address in item['outputs'] : n=n+1
1257 item = QTreeWidgetItem( [ address, label, tx] )
1258 item.setFont(0, QFont(MONOSPACE_FONT))
1259 l.addTopLevelItem(item)
1261 l.setCurrentItem(l.topLevelItem(0))
1264 def create_wall_tab(self):
1265 from qt_console import Console
1266 self.console = console = Console()
1267 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1271 def create_status_bar(self):
1272 self.status_text = ""
1274 sb.setFixedHeight(35)
1275 qtVersion = qVersion()
1277 update_notification = UpdateLabel(self.config)
1278 if(update_notification.new_version):
1279 sb.addPermanentWidget(update_notification)
1281 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1282 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1283 if self.wallet.seed:
1284 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1285 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1286 if self.wallet.seed:
1287 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1288 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1289 sb.addPermanentWidget( self.status_button )
1291 self.setStatusBar(sb)
1295 self.config.set_key('gui', 'lite', True)
1298 self.lite.mini.show()
1300 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1301 self.lite.main(None)
1303 def new_contact_dialog(self):
1304 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1305 address = unicode(text)
1307 if self.wallet.is_valid(address):
1308 self.wallet.addressbook.append(address)
1310 self.update_contacts_tab()
1311 self.update_history_tab()
1312 self.update_completions()
1314 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1316 def show_master_public_key(self):
1317 dialog = QDialog(None)
1319 dialog.setWindowTitle(_("Master Public Key"))
1321 main_text = QTextEdit()
1322 main_text.setText(self.wallet.get_master_public_key())
1323 main_text.setReadOnly(True)
1324 main_text.setMaximumHeight(170)
1325 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1327 ok_button = QPushButton(_("OK"))
1328 ok_button.setDefault(True)
1329 ok_button.clicked.connect(dialog.accept)
1331 main_layout = QGridLayout()
1332 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1334 main_layout.addWidget(main_text, 1, 0)
1335 main_layout.addWidget(qrw, 1, 1 )
1337 vbox = QVBoxLayout()
1338 vbox.addLayout(main_layout)
1339 hbox = QHBoxLayout()
1341 hbox.addWidget(ok_button)
1342 vbox.addLayout(hbox)
1344 dialog.setLayout(vbox)
1349 def show_seed_dialog(wallet, parent=None):
1351 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1354 if wallet.use_encryption:
1355 password = parent.password_dialog()
1362 seed = wallet.decode_seed(password)
1364 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1367 dialog = QDialog(None)
1369 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1371 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1373 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1375 seed_text = QTextEdit(brainwallet)
1376 seed_text.setReadOnly(True)
1377 seed_text.setMaximumHeight(130)
1379 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1380 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1381 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1382 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1383 label2 = QLabel(msg2)
1384 label2.setWordWrap(True)
1387 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1388 logo.setMaximumWidth(60)
1390 qrw = QRCodeWidget(seed, 4)
1392 ok_button = QPushButton(_("OK"))
1393 ok_button.setDefault(True)
1394 ok_button.clicked.connect(dialog.accept)
1396 grid = QGridLayout()
1397 #main_layout.addWidget(logo, 0, 0)
1399 grid.addWidget(logo, 0, 0)
1400 grid.addWidget(label1, 0, 1)
1402 grid.addWidget(seed_text, 1, 0, 1, 2)
1404 grid.addWidget(qrw, 0, 2, 2, 1)
1406 vbox = QVBoxLayout()
1407 vbox.addLayout(grid)
1408 vbox.addWidget(label2)
1410 hbox = QHBoxLayout()
1412 hbox.addWidget(ok_button)
1413 vbox.addLayout(hbox)
1415 dialog.setLayout(vbox)
1419 def show_qrcode(title, data):
1423 d.setWindowTitle(title)
1424 d.setMinimumSize(270, 300)
1425 vbox = QVBoxLayout()
1426 qrw = QRCodeWidget(data)
1427 vbox.addWidget(qrw, 1)
1428 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1429 hbox = QHBoxLayout()
1433 filename = "qrcode.bmp"
1434 bmp.save_qrcode(qrw.qr, filename)
1435 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1437 b = QPushButton(_("Print"))
1439 b.clicked.connect(print_qr)
1441 b = QPushButton(_("Close"))
1443 b.clicked.connect(d.accept)
1445 vbox.addLayout(hbox)
1449 def view_private_key(self,address):
1450 if not address: return
1451 if self.wallet.use_encryption:
1452 password = self.password_dialog()
1459 pk = self.wallet.get_private_key(address, password)
1460 except BaseException, e:
1461 self.show_message(str(e))
1464 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1467 def sign_message(self,address):
1468 if not address: return
1471 d.setWindowTitle(_('Sign Message'))
1472 d.setMinimumSize(410, 290)
1474 tab_widget = QTabWidget()
1476 layout = QGridLayout(tab)
1478 sign_address = QLineEdit()
1480 sign_address.setText(address)
1481 layout.addWidget(QLabel(_('Address')), 1, 0)
1482 layout.addWidget(sign_address, 1, 1)
1484 sign_message = QTextEdit()
1485 layout.addWidget(QLabel(_('Message')), 2, 0)
1486 layout.addWidget(sign_message, 2, 1)
1487 layout.setRowStretch(2,3)
1489 sign_signature = QTextEdit()
1490 layout.addWidget(QLabel(_('Signature')), 3, 0)
1491 layout.addWidget(sign_signature, 3, 1)
1492 layout.setRowStretch(3,1)
1495 if self.wallet.use_encryption:
1496 password = self.password_dialog()
1503 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1504 sign_signature.setText(signature)
1505 except BaseException, e:
1506 self.show_message(str(e))
1509 hbox = QHBoxLayout()
1510 b = QPushButton(_("Sign"))
1512 b.clicked.connect(do_sign)
1513 b = QPushButton(_("Close"))
1514 b.clicked.connect(d.accept)
1516 layout.addLayout(hbox, 4, 1)
1517 tab_widget.addTab(tab, _("Sign"))
1521 layout = QGridLayout(tab)
1523 verify_address = QLineEdit()
1524 layout.addWidget(QLabel(_('Address')), 1, 0)
1525 layout.addWidget(verify_address, 1, 1)
1527 verify_message = QTextEdit()
1528 layout.addWidget(QLabel(_('Message')), 2, 0)
1529 layout.addWidget(verify_message, 2, 1)
1530 layout.setRowStretch(2,3)
1532 verify_signature = QTextEdit()
1533 layout.addWidget(QLabel(_('Signature')), 3, 0)
1534 layout.addWidget(verify_signature, 3, 1)
1535 layout.setRowStretch(3,1)
1539 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1540 self.show_message(_("Signature verified"))
1541 except BaseException, e:
1542 self.show_message(str(e))
1545 hbox = QHBoxLayout()
1546 b = QPushButton(_("Verify"))
1547 b.clicked.connect(do_verify)
1549 b = QPushButton(_("Close"))
1550 b.clicked.connect(d.accept)
1552 layout.addLayout(hbox, 4, 1)
1553 tab_widget.addTab(tab, _("Verify"))
1555 vbox = QVBoxLayout()
1556 vbox.addWidget(tab_widget)
1561 def toggle_QR_window(self, show):
1562 if show and not self.qr_window:
1563 self.qr_window = QR_Window(self.exchanger)
1564 self.qr_window.setVisible(True)
1565 self.qr_window_geometry = self.qr_window.geometry()
1566 item = self.receive_list.currentItem()
1568 address = str(item.text(1))
1569 label = self.wallet.labels.get(address)
1570 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1571 self.qr_window.set_content( address, label, amount, currency )
1573 elif show and self.qr_window and not self.qr_window.isVisible():
1574 self.qr_window.setVisible(True)
1575 self.qr_window.setGeometry(self.qr_window_geometry)
1577 elif not show and self.qr_window and self.qr_window.isVisible():
1578 self.qr_window_geometry = self.qr_window.geometry()
1579 self.qr_window.setVisible(False)
1581 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1582 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1583 self.receive_list.setColumnWidth(2, 200)
1586 def question(self, msg):
1587 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1589 def show_message(self, msg):
1590 QMessageBox.information(self, _('Message'), msg, _('OK'))
1592 def password_dialog(self ):
1599 vbox = QVBoxLayout()
1600 msg = _('Please enter your password')
1601 vbox.addWidget(QLabel(msg))
1603 grid = QGridLayout()
1605 grid.addWidget(QLabel(_('Password')), 1, 0)
1606 grid.addWidget(pw, 1, 1)
1607 vbox.addLayout(grid)
1609 vbox.addLayout(ok_cancel_buttons(d))
1612 if not d.exec_(): return
1613 return unicode(pw.text())
1620 def change_password_dialog( wallet, parent=None ):
1623 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1631 new_pw = QLineEdit()
1632 new_pw.setEchoMode(2)
1633 conf_pw = QLineEdit()
1634 conf_pw.setEchoMode(2)
1636 vbox = QVBoxLayout()
1638 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1639 +_('To disable wallet encryption, enter an empty new password.')) \
1640 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1642 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1643 +_("Leave these fields empty if you want to disable encryption.")
1644 vbox.addWidget(QLabel(msg))
1646 grid = QGridLayout()
1649 if wallet.use_encryption:
1650 grid.addWidget(QLabel(_('Password')), 1, 0)
1651 grid.addWidget(pw, 1, 1)
1653 grid.addWidget(QLabel(_('New Password')), 2, 0)
1654 grid.addWidget(new_pw, 2, 1)
1656 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1657 grid.addWidget(conf_pw, 3, 1)
1658 vbox.addLayout(grid)
1660 vbox.addLayout(ok_cancel_buttons(d))
1663 if not d.exec_(): return
1665 password = unicode(pw.text()) if wallet.use_encryption else None
1666 new_password = unicode(new_pw.text())
1667 new_password2 = unicode(conf_pw.text())
1670 seed = wallet.decode_seed(password)
1672 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1675 if new_password != new_password2:
1676 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1677 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1679 wallet.update_password(seed, password, new_password)
1682 def seed_dialog(wallet, parent=None):
1686 vbox = QVBoxLayout()
1687 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1688 vbox.addWidget(QLabel(msg))
1690 grid = QGridLayout()
1693 seed_e = QLineEdit()
1694 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1695 grid.addWidget(seed_e, 1, 1)
1699 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1700 grid.addWidget(gap_e, 2, 1)
1701 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1702 vbox.addLayout(grid)
1704 vbox.addLayout(ok_cancel_buttons(d))
1707 if not d.exec_(): return
1710 gap = int(unicode(gap_e.text()))
1712 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1716 seed = str(seed_e.text())
1719 print_error("Warning: Not hex, trying decode")
1721 seed = mnemonic.mn_decode( seed.split(' ') )
1723 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1727 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1732 def do_export_privkeys(self):
1733 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.")))
1735 if self.wallet.use_encryption:
1736 password = self.password_dialog()
1742 select_export = _('Select file to export your private keys to')
1743 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1745 with open(fileName, "w+") as csvfile:
1746 transaction = csv.writer(csvfile)
1747 transaction.writerow(["address", "private_key"])
1749 for addr in self.wallet.all_addresses():
1750 m_addr = "%34s"%addr
1751 transaction.writerow([m_addr, str(self.wallet.get_private_key(addr, password))])
1753 self.show_message(_("Private keys exported."))
1755 except (IOError, os.error), reason:
1756 export_error_label = _("Electrum was unable to produce a private key-export.")
1757 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1759 except BaseException, e:
1760 self.show_message(str(e))
1764 def do_import_labels(self):
1765 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
1766 if not labelsFile: return
1768 f = open(labelsFile, 'r')
1771 for key, value in json.loads(data).items():
1772 self.wallet.labels[key] = value
1774 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1775 except (IOError, os.error), reason:
1776 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1780 def do_export_labels(self):
1781 labels = self.wallet.labels
1783 labelsFile = util.user_dir() + '/labels.dat'
1784 f = open(labelsFile, 'w+')
1785 json.dump(labels, f)
1787 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
1788 except (IOError, os.error), reason:
1789 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1791 def do_export_history(self):
1792 from gui_lite import csv_transaction
1793 csv_transaction(self.wallet)
1795 def do_import_privkey(self):
1796 if not self.wallet.imported_keys:
1797 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1798 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1799 + _('Are you sure you understand what you are doing?'), 3, 4)
1802 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1804 sec = str(text).strip()
1805 if self.wallet.use_encryption:
1806 password = self.password_dialog()
1812 addr = self.wallet.import_key(sec, password)
1814 QMessageBox.critical(None, _("Unable to import key"), "error")
1816 QMessageBox.information(None, _("Key imported"), addr)
1817 self.update_receive_tab()
1818 self.update_history_tab()
1819 except BaseException as e:
1820 QMessageBox.critical(None, _("Unable to import key"), str(e))
1822 def settings_dialog(self):
1824 d.setWindowTitle(_('Electrum Settings'))
1826 vbox = QVBoxLayout()
1828 tabs = QTabWidget(self)
1829 vbox.addWidget(tabs)
1832 grid_ui = QGridLayout(tab1)
1833 grid_ui.setColumnStretch(0,1)
1834 tabs.addTab(tab1, _('Display') )
1836 nz_label = QLabel(_('Display zeros'))
1837 grid_ui.addWidget(nz_label, 3, 0)
1839 nz_e.setText("%d"% self.wallet.num_zeros)
1840 grid_ui.addWidget(nz_e, 3, 1)
1841 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1842 grid_ui.addWidget(HelpButton(msg), 3, 2)
1843 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1844 if not self.config.is_modifiable('num_zeros'):
1845 for w in [nz_e, nz_label]: w.setEnabled(False)
1847 lang_label=QLabel(_('Language') + ':')
1848 grid_ui.addWidget(lang_label , 8, 0)
1849 lang_combo = QComboBox()
1850 from i18n import languages
1851 lang_combo.addItems(languages.values())
1853 index = languages.keys().index(self.config.get("language",''))
1856 lang_combo.setCurrentIndex(index)
1857 grid_ui.addWidget(lang_combo, 8, 1)
1858 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1859 if not self.config.is_modifiable('language'):
1860 for w in [lang_combo, lang_label]: w.setEnabled(False)
1862 currencies = self.exchanger.get_currencies()
1863 currencies.insert(0, "None")
1865 cur_label=QLabel(_('Currency') + ':')
1866 grid_ui.addWidget(cur_label , 9, 0)
1867 cur_combo = QComboBox()
1868 cur_combo.addItems(currencies)
1870 index = currencies.index(self.config.get('currency', "None"))
1873 cur_combo.setCurrentIndex(index)
1874 grid_ui.addWidget(cur_combo, 9, 1)
1875 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1877 view_label=QLabel(_('Receive Tab') + ':')
1878 grid_ui.addWidget(view_label , 10, 0)
1879 view_combo = QComboBox()
1880 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1881 view_combo.setCurrentIndex(self.receive_tab_mode)
1882 grid_ui.addWidget(view_combo, 10, 1)
1883 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1884 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1885 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1886 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1888 grid_ui.addWidget(HelpButton(hh), 10, 2)
1892 grid_wallet = QGridLayout(tab2)
1893 grid_wallet.setColumnStretch(0,1)
1894 tabs.addTab(tab2, _('Wallet') )
1896 fee_label = QLabel(_('Transaction fee'))
1897 grid_wallet.addWidget(fee_label, 0, 0)
1899 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1900 grid_wallet.addWidget(fee_e, 0, 1)
1901 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1902 + _('Recommended value') + ': 0.001'
1903 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1904 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1905 if not self.config.is_modifiable('fee'):
1906 for w in [fee_e, fee_label]: w.setEnabled(False)
1908 usechange_label = QLabel(_('Use change addresses'))
1909 grid_wallet.addWidget(usechange_label, 1, 0)
1910 usechange_combo = QComboBox()
1911 usechange_combo.addItems([_('Yes'), _('No')])
1912 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1913 grid_wallet.addWidget(usechange_combo, 1, 1)
1914 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1915 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1917 gap_label = QLabel(_('Gap limit'))
1918 grid_wallet.addWidget(gap_label, 2, 0)
1920 gap_e.setText("%d"% self.wallet.gap_limit)
1921 grid_wallet.addWidget(gap_e, 2, 1)
1922 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1923 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1924 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1925 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1926 + _('Warning') + ': ' \
1927 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1928 + _('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'
1929 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1930 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1931 if not self.config.is_modifiable('gap_limit'):
1932 for w in [gap_e, gap_label]: w.setEnabled(False)
1934 grid_wallet.setRowStretch(3,1)
1939 grid_io = QGridLayout(tab3)
1940 grid_io.setColumnStretch(0,1)
1941 tabs.addTab(tab3, _('Import/Export') )
1943 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1944 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1945 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1946 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1948 grid_io.addWidget(QLabel(_('History')), 2, 0)
1949 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1950 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1952 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1954 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1955 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1956 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1958 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1959 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1960 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1961 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1962 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1964 grid_io.setRowStretch(4,1)
1965 vbox.addLayout(ok_cancel_buttons(d))
1969 if not d.exec_(): return
1971 fee = unicode(fee_e.text())
1973 fee = int( 100000000 * Decimal(fee) )
1975 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1978 if self.wallet.fee != fee:
1979 self.wallet.fee = fee
1982 nz = unicode(nz_e.text())
1987 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1990 if self.wallet.num_zeros != nz:
1991 self.wallet.num_zeros = nz
1992 self.config.set_key('num_zeros', nz, True)
1993 self.update_history_tab()
1994 self.update_receive_tab()
1996 usechange_result = usechange_combo.currentIndex() == 0
1997 if self.wallet.use_change != usechange_result:
1998 self.wallet.use_change = usechange_result
1999 self.config.set_key('use_change', self.wallet.use_change, True)
2002 n = int(gap_e.text())
2004 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2007 if self.wallet.gap_limit != n:
2008 r = self.wallet.change_gap_limit(n)
2010 self.update_receive_tab()
2011 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2013 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2015 need_restart = False
2017 lang_request = languages.keys()[lang_combo.currentIndex()]
2018 if lang_request != self.config.get('language'):
2019 self.config.set_key("language", lang_request, True)
2022 cur_request = str(currencies[cur_combo.currentIndex()])
2023 if cur_request != self.config.get('currency', "None"):
2024 self.config.set_key('currency', cur_request, True)
2025 self.update_wallet()
2028 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2030 self.receive_tab_set_mode(view_combo.currentIndex())
2034 def network_dialog(wallet, parent=None):
2035 interface = wallet.interface
2037 if interface.is_connected:
2038 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2040 status = _("Not connected")
2041 server = interface.server
2044 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2045 server = interface.server
2047 plist, servers_list = interface.get_servers_list()
2051 d.setWindowTitle(_('Server'))
2052 d.setMinimumSize(375, 20)
2054 vbox = QVBoxLayout()
2057 hbox = QHBoxLayout()
2059 l.setPixmap(QPixmap(":icons/network.png"))
2062 hbox.addWidget(QLabel(status))
2064 vbox.addLayout(hbox)
2068 grid = QGridLayout()
2070 vbox.addLayout(grid)
2073 server_protocol = QComboBox()
2074 server_host = QLineEdit()
2075 server_host.setFixedWidth(200)
2076 server_port = QLineEdit()
2077 server_port.setFixedWidth(60)
2079 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2080 protocol_letters = 'thsg'
2081 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2082 server_protocol.addItems(protocol_names)
2084 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2085 grid.addWidget(server_protocol, 0, 1)
2086 grid.addWidget(server_host, 0, 2)
2087 grid.addWidget(server_port, 0, 3)
2089 def change_protocol(p):
2090 protocol = protocol_letters[p]
2091 host = unicode(server_host.text())
2092 pp = plist.get(host,DEFAULT_PORTS)
2093 if protocol not in pp.keys():
2094 protocol = pp.keys()[0]
2096 server_host.setText( host )
2097 server_port.setText( port )
2099 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2101 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2102 servers_list_widget = QTreeWidget(parent)
2103 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2104 servers_list_widget.setMaximumHeight(150)
2105 servers_list_widget.setColumnWidth(0, 240)
2106 for _host in servers_list.keys():
2107 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2108 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2110 def change_server(host, protocol=None):
2111 pp = plist.get(host,DEFAULT_PORTS)
2113 port = pp.get(protocol)
2114 if not port: protocol = None
2117 if 't' in pp.keys():
2119 port = pp.get(protocol)
2121 protocol = pp.keys()[0]
2122 port = pp.get(protocol)
2124 server_host.setText( host )
2125 server_port.setText( port )
2126 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2128 if not plist: return
2129 for p in protocol_letters:
2130 i = protocol_letters.index(p)
2131 j = server_protocol.model().index(i,0)
2132 if p not in pp.keys():
2133 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2135 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2139 host, port, protocol = server.split(':')
2140 change_server(host,protocol)
2142 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2143 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2145 if not wallet.config.is_modifiable('server'):
2146 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2149 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2150 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2151 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2152 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2155 proxy_mode = QComboBox()
2156 proxy_host = QLineEdit()
2157 proxy_host.setFixedWidth(200)
2158 proxy_port = QLineEdit()
2159 proxy_port.setFixedWidth(60)
2160 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2162 def check_for_disable(index = False):
2163 if proxy_mode.currentText() != 'NONE':
2164 proxy_host.setEnabled(True)
2165 proxy_port.setEnabled(True)
2167 proxy_host.setEnabled(False)
2168 proxy_port.setEnabled(False)
2171 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2173 if not wallet.config.is_modifiable('proxy'):
2174 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2176 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2177 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2178 proxy_host.setText(proxy_config.get("host"))
2179 proxy_port.setText(proxy_config.get("port"))
2181 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2182 grid.addWidget(proxy_mode, 2, 1)
2183 grid.addWidget(proxy_host, 2, 2)
2184 grid.addWidget(proxy_port, 2, 3)
2187 vbox.addLayout(ok_cancel_buttons(d))
2190 if not d.exec_(): return
2192 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2193 if proxy_mode.currentText() != 'NONE':
2194 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2198 wallet.config.set_key("proxy", proxy, True)
2199 wallet.config.set_key("server", server, True)
2200 interface.set_server(server, proxy)
2201 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2204 def closeEvent(self, event):
2206 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2207 self.save_column_widths()
2208 self.config.set_key("column-widths", self.column_widths, True)
2214 def __init__(self, wallet, config, app=None):
2215 self.wallet = wallet
2216 self.config = config
2218 self.app = QApplication(sys.argv)
2221 def restore_or_create(self):
2222 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2223 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2224 if r==2: return None
2225 return 'restore' if r==1 else 'create'
2227 def seed_dialog(self):
2228 return ElectrumWindow.seed_dialog( self.wallet )
2230 def network_dialog(self):
2231 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2234 def show_seed(self):
2235 ElectrumWindow.show_seed_dialog(self.wallet)
2238 def password_dialog(self):
2239 ElectrumWindow.change_password_dialog(self.wallet)
2242 def restore_wallet(self):
2243 wallet = self.wallet
2244 # wait until we are connected, because the user might have selected another server
2245 if not wallet.interface.is_connected:
2246 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2247 waiting_dialog(waiting)
2249 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2250 %(_("Please wait..."),_("Addresses generated:"),len(wallet.all_addresses()),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2252 wallet.set_up_to_date(False)
2253 wallet.interface.poke('synchronizer')
2254 waiting_dialog(waiting)
2255 if wallet.is_found():
2256 print_error( "Recovery successful" )
2258 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2265 w = ElectrumWindow(self.wallet, self.config)
2266 if url: w.set_url(url)