3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
28 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
30 from PyQt4.QtGui import *
31 from PyQt4.QtCore import *
32 import PyQt4.QtCore as QtCore
33 import PyQt4.QtGui as QtGui
34 from electrum.interface import DEFAULT_SERVERS
39 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41 from electrum.wallet import format_satoshis
42 from electrum.bitcoin import Transaction, is_valid
43 from electrum import mnemonic
45 import bmp, pyqrnative, qrscanner
48 from decimal import Decimal
56 if platform.system() == 'Windows':
57 MONOSPACE_FONT = 'Lucida Console'
58 elif platform.system() == 'Darwin':
59 MONOSPACE_FONT = 'Monaco'
61 MONOSPACE_FONT = 'monospace'
63 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
65 from electrum import ELECTRUM_VERSION
68 class UpdateLabel(QtGui.QLabel):
69 def __init__(self, config, parent=None):
70 QtGui.QLabel.__init__(self, parent)
71 self.new_version = False
74 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
75 con.request("GET", "/version")
76 res = con.getresponse()
77 except socket.error as msg:
78 print_error("Could not retrieve version information")
82 self.latest_version = res.read()
83 self.latest_version = self.latest_version.replace("\n","")
84 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
86 self.current_version = ELECTRUM_VERSION
87 if(self.compare_versions(self.latest_version, self.current_version) == 1):
88 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
89 if(self.compare_versions(self.latest_version, latest_seen) == 1):
90 self.new_version = True
91 self.setText(_("New version available") + ": " + self.latest_version)
94 def compare_versions(self, version1, version2):
96 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
97 return cmp(normalize(version1), normalize(version2))
99 def ignore_this_version(self):
101 self.config.set_key("last_seen_version", self.latest_version, True)
102 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
105 def ignore_all_version(self):
107 self.config.set_key("last_seen_version", "9.9.9", True)
108 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
111 def open_website(self):
112 webbrowser.open("http://electrum.org/download.html")
115 def mouseReleaseEvent(self, event):
116 dialog = QDialog(self)
117 dialog.setWindowTitle(_('Electrum update'))
120 main_layout = QGridLayout()
121 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
123 ignore_version = QPushButton(_("Ignore this version"))
124 ignore_version.clicked.connect(self.ignore_this_version)
126 ignore_all_versions = QPushButton(_("Ignore all versions"))
127 ignore_all_versions.clicked.connect(self.ignore_all_version)
129 open_website = QPushButton(_("Goto download page"))
130 open_website.clicked.connect(self.open_website)
132 main_layout.addWidget(ignore_version, 1, 0)
133 main_layout.addWidget(ignore_all_versions, 1, 1)
134 main_layout.addWidget(open_website, 1, 2)
136 dialog.setLayout(main_layout)
140 if not dialog.exec_(): return
142 def numbify(entry, is_int = False):
143 text = unicode(entry.text()).strip()
144 pos = entry.cursorPosition()
146 if not is_int: chars +='.'
147 s = ''.join([i for i in text if i in chars])
151 s = s.replace('.','')
152 s = s[:p] + '.' + s[p:p+8]
154 amount = int( Decimal(s) * 100000000 )
163 entry.setCursorPosition(pos)
167 class Timer(QtCore.QThread):
170 self.emit(QtCore.SIGNAL('timersignal'))
173 class HelpButton(QPushButton):
174 def __init__(self, text):
175 QPushButton.__init__(self, '?')
176 self.setFocusPolicy(Qt.NoFocus)
177 self.setFixedWidth(20)
178 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
181 class EnterButton(QPushButton):
182 def __init__(self, text, func):
183 QPushButton.__init__(self, text)
185 self.clicked.connect(func)
187 def keyPressEvent(self, e):
188 if e.key() == QtCore.Qt.Key_Return:
191 class MyTreeWidget(QTreeWidget):
192 def __init__(self, parent):
193 QTreeWidget.__init__(self, parent)
196 for i in range(0,self.viewport().height()/5):
197 if self.itemAt(QPoint(0,i*5)) == item:
201 for j in range(0,30):
202 if self.itemAt(QPoint(0,i*5 + j)) != item:
204 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
206 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
211 class StatusBarButton(QPushButton):
212 def __init__(self, icon, tooltip, func):
213 QPushButton.__init__(self, icon, '')
214 self.setToolTip(tooltip)
216 self.setMaximumWidth(25)
217 self.clicked.connect(func)
220 def keyPressEvent(self, e):
221 if e.key() == QtCore.Qt.Key_Return:
225 class QRCodeWidget(QWidget):
227 def __init__(self, data = None, size=4):
228 QWidget.__init__(self)
229 self.setMinimumSize(210, 210)
237 def set_addr(self, addr):
238 if self.addr != addr:
244 if self.addr and not self.qr:
245 self.qr = pyqrnative.QRCode(self.size, pyqrnative.QRErrorCorrectLevel.L)
246 self.qr.addData(self.addr)
250 def paintEvent(self, e):
255 black = QColor(0, 0, 0, 255)
256 white = QColor(255, 255, 255, 255)
259 qp = QtGui.QPainter()
263 qp.drawRect(0, 0, 198, 198)
267 k = self.qr.getModuleCount()
268 qp = QtGui.QPainter()
271 boxsize = min(r.width(), r.height())*0.8/k
273 left = (r.width() - size)/2
274 top = (r.height() - size)/2
278 if self.qr.isDark(r, c):
284 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
289 class QR_Window(QWidget):
291 def __init__(self, exchanger):
292 QWidget.__init__(self)
293 self.exchanger = exchanger
294 self.setWindowTitle('Electrum - '+_('Invoice'))
295 self.setMinimumSize(800, 250)
299 self.setFocusPolicy(QtCore.Qt.NoFocus)
301 main_box = QHBoxLayout()
303 self.qrw = QRCodeWidget()
304 main_box.addWidget(self.qrw, 1)
307 main_box.addLayout(vbox)
309 self.address_label = QLabel("")
310 self.address_label.setFont(QFont(MONOSPACE_FONT))
311 vbox.addWidget(self.address_label)
313 self.label_label = QLabel("")
314 vbox.addWidget(self.label_label)
316 self.amount_label = QLabel("")
317 vbox.addWidget(self.amount_label)
320 self.setLayout(main_box)
323 def set_content(self, addr, label, amount, currency):
325 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
326 self.address_label.setText(address_text)
328 if currency == 'BTC': currency = None
332 self.amount = Decimal(amount) / self.exchanger.exchange(1, currency) if currency else amount
334 self.amount = Decimal(amount)
335 self.amount = self.amount.quantize(Decimal('1.0000'))
338 amount_text += "<span style='font-size: 18pt'>%s %s</span><br/>" % (amount, currency)
339 amount_text += "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % str(self.amount)
340 self.amount_label.setText(amount_text)
343 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
344 self.label_label.setText(label_text)
346 msg = 'bitcoin:'+self.address
347 if self.amount is not None:
348 msg += '?amount=%s'%(str( self.amount))
349 if self.label is not None:
350 msg += '&label=%s'%(self.label)
351 elif self.label is not None:
352 msg += '?label=%s'%(self.label)
354 self.qrw.set_addr( msg )
359 def waiting_dialog(f):
365 w.setWindowTitle('Electrum')
375 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
380 def ok_cancel_buttons(dialog):
383 b = QPushButton("OK")
385 b.clicked.connect(dialog.accept)
386 b = QPushButton("Cancel")
388 b.clicked.connect(dialog.reject)
392 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
393 "receive":[[370],[370,200,130,130],[370,200,130,130]] }
395 class ElectrumWindow(QMainWindow):
397 def __init__(self, wallet, config):
398 QMainWindow.__init__(self)
402 self.wallet.interface.register_callback('updated', self.update_callback)
403 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
404 self.wallet.interface.register_callback('disconnected', self.update_callback)
405 self.wallet.interface.register_callback('disconnecting', self.update_callback)
407 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
408 self.merchant_name = config.get('merchant_name', 'Invoice')
410 set_language(config.get('language'))
412 self.qr_window = None
413 self.funds_error = False
414 self.completions = QStringListModel()
416 self.tabs = tabs = QTabWidget(self)
417 self.column_widths = self.config.get("column-widths", default_column_widths )
418 tabs.addTab(self.create_history_tab(), _('History') )
419 tabs.addTab(self.create_send_tab(), _('Send') )
420 tabs.addTab(self.create_receive_tab(), _('Receive') )
421 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
422 tabs.addTab(self.create_console_tab(), _('Console') )
423 tabs.setMinimumSize(600, 400)
424 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
425 self.setCentralWidget(tabs)
426 self.create_status_bar()
428 g = self.config.get("winpos-qt",[100, 100, 840, 400])
429 self.setGeometry(g[0], g[1], g[2], g[3])
430 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
431 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
432 self.setWindowTitle( title )
434 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
435 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
436 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
437 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
439 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
440 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
441 self.history_list.setFocus(True)
443 self.exchanger = exchange_rate.Exchanger(self)
444 self.toggle_QR_window(self.receive_tab_mode == 2)
445 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
447 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
448 if platform.system() == 'Windows':
449 n = 3 if self.wallet.seed else 2
450 tabs.setCurrentIndex (n)
451 tabs.setCurrentIndex (0)
453 # set initial message
454 self.console.showMessage(self.wallet.banner)
457 for p in self.wallet.plugins:
461 print_msg("Error:cannot initialize plugin",p)
462 traceback.print_exc(file=sys.stdout)
466 QMainWindow.close(self)
468 self.qr_window.close()
469 self.qr_window = None
471 def connect_slots(self, sender):
472 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
473 self.previous_payto_e=''
475 def timer_actions(self):
477 self.qr_window.qrw.update_qr()
479 if self.payto_e.hasFocus():
481 r = unicode( self.payto_e.text() )
482 if r != self.previous_payto_e:
483 self.previous_payto_e = r
485 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
487 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
491 s = r + ' <' + to_address + '>'
492 self.payto_e.setText(s)
495 def update_callback(self):
496 self.emit(QtCore.SIGNAL('updatesignal'))
498 def update_wallet(self):
499 if self.wallet.interface and self.wallet.interface.is_connected:
500 if not self.wallet.up_to_date:
501 text = _("Synchronizing...")
502 icon = QIcon(":icons/status_waiting.png")
504 c, u = self.wallet.get_balance()
505 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
506 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
507 text += self.create_quote_text(Decimal(c+u)/100000000)
508 icon = QIcon(":icons/status_connected.png")
510 text = _("Not connected")
511 icon = QIcon(":icons/status_disconnected.png")
513 self.status_text = text
514 self.statusBar().showMessage(text)
515 self.status_button.setIcon( icon )
517 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
518 self.update_history_tab()
519 self.update_receive_tab()
520 self.update_contacts_tab()
521 self.update_completions()
524 def create_quote_text(self, btc_balance):
525 quote_currency = self.config.get("currency", "None")
526 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
527 if quote_balance is None:
530 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
533 def create_history_tab(self):
534 self.history_list = l = MyTreeWidget(self)
536 for i,width in enumerate(self.column_widths['history']):
537 l.setColumnWidth(i, width)
538 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
539 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
540 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
542 l.setContextMenuPolicy(Qt.CustomContextMenu)
543 l.customContextMenuRequested.connect(self.create_history_menu)
547 def create_history_menu(self, position):
548 self.history_list.selectedIndexes()
549 item = self.history_list.currentItem()
551 tx_hash = str(item.data(0, Qt.UserRole).toString())
552 if not tx_hash: return
554 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
555 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
556 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
557 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
560 def show_tx_details(self, tx):
561 dialog = QDialog(None)
563 dialog.setWindowTitle(_("Transaction Details"))
565 dialog.setLayout(vbox)
566 dialog.setMinimumSize(600,300)
569 if tx_hash in self.wallet.transactions.keys():
570 is_mine, v, fee = self.wallet.get_tx_value(tx)
571 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
573 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
579 vbox.addWidget(QLabel("Transaction ID:"))
580 e = QLineEdit(tx_hash)
584 vbox.addWidget(QLabel("Date: %s"%time_str))
585 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
588 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
589 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
591 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
592 vbox.addWidget(QLabel("Transaction fee: unknown"))
594 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
596 vbox.addWidget( self.generate_transaction_information_widget(tx) )
598 ok_button = QPushButton(_("Close"))
599 ok_button.setDefault(True)
600 ok_button.clicked.connect(dialog.accept)
604 hbox.addWidget(ok_button)
608 def tx_label_clicked(self, item, column):
609 if column==2 and item.isSelected():
611 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
612 self.history_list.editItem( item, column )
613 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
616 def tx_label_changed(self, item, column):
620 tx_hash = str(item.data(0, Qt.UserRole).toString())
621 tx = self.wallet.transactions.get(tx_hash)
622 s = self.wallet.labels.get(tx_hash)
623 text = unicode( item.text(2) )
625 self.wallet.labels[tx_hash] = text
626 item.setForeground(2, QBrush(QColor('black')))
628 if s: self.wallet.labels.pop(tx_hash)
629 text = self.wallet.get_default_label(tx_hash)
630 item.setText(2, text)
631 item.setForeground(2, QBrush(QColor('gray')))
635 def edit_label(self, is_recv):
636 l = self.receive_list if is_recv else self.contacts_list
637 c = 2 if is_recv else 1
638 item = l.currentItem()
639 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
640 l.editItem( item, c )
641 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
643 def edit_amount(self):
644 l = self.receive_list
645 item = l.currentItem()
646 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
647 l.editItem( item, 3 )
648 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
651 def address_label_clicked(self, item, column, l, column_addr, column_label):
652 if column == column_label and item.isSelected():
653 addr = unicode( item.text(column_addr) )
654 label = unicode( item.text(column_label) )
655 if label in self.wallet.aliases.keys():
657 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
658 l.editItem( item, column )
659 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
662 def address_label_changed(self, item, column, l, column_addr, column_label):
664 if column == column_label:
665 addr = unicode( item.text(column_addr) )
666 text = unicode( item.text(column_label) )
670 if text not in self.wallet.aliases.keys():
671 old_addr = self.wallet.labels.get(text)
673 self.wallet.labels[addr] = text
676 print_error("Error: This is one of your aliases")
677 label = self.wallet.labels.get(addr,'')
678 item.setText(column_label, QString(label))
680 s = self.wallet.labels.get(addr)
682 self.wallet.labels.pop(addr)
686 self.update_history_tab()
687 self.update_completions()
689 self.recv_changed(item)
692 address = str( item.text(column_addr) )
693 text = str( item.text(3) )
695 index = self.wallet.addresses.index(address)
699 text = text.strip().upper()
700 m = re.match('^(\d+(|\.\d*))\s*(|BTC|EUR|USD|GBP|CNY|JPY|RUB|BRL)$', text)
703 currency = m.group(3)
707 currency = currency.upper()
708 self.wallet.requested_amounts[address] = (amount, currency)
710 label = self.wallet.labels.get(address)
712 label = self.merchant_name + ' - %04d'%(index+1)
713 self.wallet.labels[address] = label
716 self.qr_window.set_content( address, label, amount, currency )
720 if address in self.wallet.requested_amounts:
721 self.wallet.requested_amounts.pop(address)
723 self.update_receive_item(self.receive_list.currentItem())
726 def recv_changed(self, a):
727 "current item changed"
728 if a is not None and self.qr_window and self.qr_window.isVisible():
729 address = str(a.text(1))
730 label = self.wallet.labels.get(address)
732 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
734 amount, currency = None, None
735 self.qr_window.set_content( address, label, amount, currency )
738 def update_history_tab(self):
740 self.history_list.clear()
741 for item in self.wallet.get_tx_history():
742 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
745 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
751 icon = QIcon(":icons/unconfirmed.png")
753 icon = QIcon(":icons/clock%d.png"%conf)
755 icon = QIcon(":icons/confirmed.png")
758 icon = QIcon(":icons/unconfirmed.png")
760 if value is not None:
761 v_str = format_satoshis(value, True, self.wallet.num_zeros)
765 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
768 label, is_default_label = self.wallet.get_label(tx_hash)
770 label = _('Pruned transaction outputs')
771 is_default_label = False
773 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
774 item.setFont(2, QFont(MONOSPACE_FONT))
775 item.setFont(3, QFont(MONOSPACE_FONT))
776 item.setFont(4, QFont(MONOSPACE_FONT))
778 item.setForeground(3, QBrush(QColor("#BC1E1E")))
780 item.setData(0, Qt.UserRole, tx_hash)
781 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
783 item.setForeground(2, QBrush(QColor('grey')))
785 item.setIcon(0, icon)
786 self.history_list.insertTopLevelItem(0,item)
789 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
792 def create_send_tab(self):
797 grid.setColumnMinimumWidth(3,300)
798 grid.setColumnStretch(5,1)
800 self.payto_e = QLineEdit()
801 grid.addWidget(QLabel(_('Pay to')), 1, 0)
802 grid.addWidget(self.payto_e, 1, 1, 1, 3)
805 qrcode = qrscanner.scan_qr()
806 if 'address' in qrcode:
807 self.payto_e.setText(qrcode['address'])
808 if 'amount' in qrcode:
809 self.amount_e.setText(str(qrcode['amount']))
810 if 'label' in qrcode:
811 self.message_e.setText(qrcode['label'])
812 if 'message' in qrcode:
813 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
816 if qrscanner.is_available():
817 b = QPushButton(_("Scan QR code"))
818 b.clicked.connect(fill_from_qr)
819 grid.addWidget(b, 1, 5)
821 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)
823 completer = QCompleter()
824 completer.setCaseSensitivity(False)
825 self.payto_e.setCompleter(completer)
826 completer.setModel(self.completions)
828 self.message_e = QLineEdit()
829 grid.addWidget(QLabel(_('Description')), 2, 0)
830 grid.addWidget(self.message_e, 2, 1, 1, 3)
831 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)
833 self.amount_e = QLineEdit()
834 grid.addWidget(QLabel(_('Amount')), 3, 0)
835 grid.addWidget(self.amount_e, 3, 1, 1, 2)
836 grid.addWidget(HelpButton(
837 _('Amount to be sent.') + '\n\n' \
838 + _('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)
840 self.fee_e = QLineEdit()
841 grid.addWidget(QLabel(_('Fee')), 4, 0)
842 grid.addWidget(self.fee_e, 4, 1, 1, 2)
843 grid.addWidget(HelpButton(
844 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
845 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
846 + _('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)
849 b = EnterButton(_("Send"), self.do_send)
851 b = EnterButton(_("Create unsigned transaction"), self.do_send)
852 grid.addWidget(b, 6, 1)
854 b = EnterButton(_("Clear"),self.do_clear)
855 grid.addWidget(b, 6, 2)
857 self.payto_sig = QLabel('')
858 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
860 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
861 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
870 def entry_changed( is_fee ):
871 self.funds_error = False
872 amount = numbify(self.amount_e)
873 fee = numbify(self.fee_e)
874 if not is_fee: fee = None
877 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
879 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
882 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
883 text = self.status_text
886 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
887 self.funds_error = True
888 text = _( "Not enough funds" )
890 self.statusBar().showMessage(text)
891 self.amount_e.setPalette(palette)
892 self.fee_e.setPalette(palette)
894 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
895 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
900 def update_completions(self):
902 for addr,label in self.wallet.labels.items():
903 if addr in self.wallet.addressbook:
904 l.append( label + ' <' + addr + '>')
905 l = l + self.wallet.aliases.keys()
907 self.completions.setStringList(l)
913 label = unicode( self.message_e.text() )
914 r = unicode( self.payto_e.text() )
918 m1 = re.match(ALIAS_REGEXP, r)
919 # label or alias, with address in brackets
920 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
923 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
927 to_address = m2.group(2)
931 if not is_valid(to_address):
932 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
936 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
938 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
941 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
943 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
946 if self.wallet.use_encryption:
947 password = self.password_dialog()
954 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
955 except BaseException, e:
956 self.show_message(str(e))
960 for cb in self.wallet.plugin_hooks.get('send_tx'):
961 apply(cb, (wallet, self, tx))
965 self.wallet.labels[tx.hash()] = label
968 h = self.wallet.send_tx(tx)
969 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
970 status, msg = self.wallet.receive_tx( h )
972 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
974 self.update_contacts_tab()
976 QMessageBox.warning(self, _('Error'), msg, _('OK'))
978 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
980 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
981 with open(fileName,'w') as f:
982 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
983 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
985 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
990 def set_url(self, url):
991 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
992 self.tabs.setCurrentIndex(1)
993 label = self.wallet.labels.get(payto)
994 m_addr = label + ' <'+ payto+'>' if label else payto
995 self.payto_e.setText(m_addr)
997 self.message_e.setText(message)
998 self.amount_e.setText(amount)
1000 self.set_frozen(self.payto_e,True)
1001 self.set_frozen(self.amount_e,True)
1002 self.set_frozen(self.message_e,True)
1003 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1005 self.payto_sig.setVisible(False)
1008 self.payto_sig.setVisible(False)
1009 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1011 self.set_frozen(e,False)
1013 def set_frozen(self,entry,frozen):
1015 entry.setReadOnly(True)
1016 entry.setFrame(False)
1017 palette = QPalette()
1018 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1019 entry.setPalette(palette)
1021 entry.setReadOnly(False)
1022 entry.setFrame(True)
1023 palette = QPalette()
1024 palette.setColor(entry.backgroundRole(), QColor('white'))
1025 entry.setPalette(palette)
1028 def toggle_freeze(self,addr):
1030 if addr in self.wallet.frozen_addresses:
1031 self.wallet.unfreeze(addr)
1033 self.wallet.freeze(addr)
1034 self.update_receive_tab()
1036 def toggle_priority(self,addr):
1038 if addr in self.wallet.prioritized_addresses:
1039 self.wallet.unprioritize(addr)
1041 self.wallet.prioritize(addr)
1042 self.update_receive_tab()
1045 def create_list_tab(self, headers):
1046 "generic tab creation method"
1047 l = MyTreeWidget(self)
1048 l.setColumnCount( len(headers) )
1049 l.setHeaderLabels( headers )
1052 vbox = QVBoxLayout()
1059 vbox.addWidget(buttons)
1061 hbox = QHBoxLayout()
1064 buttons.setLayout(hbox)
1069 def create_receive_tab(self):
1070 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
1071 l.setContextMenuPolicy(Qt.CustomContextMenu)
1072 l.customContextMenuRequested.connect(self.create_receive_menu)
1073 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1074 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1075 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
1076 self.receive_list = l
1077 self.receive_buttons_hbox = hbox
1082 def receive_tab_set_mode(self, i):
1083 self.save_column_widths()
1084 self.receive_tab_mode = i
1085 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
1087 self.update_receive_tab()
1088 self.toggle_QR_window(self.receive_tab_mode == 2)
1091 def save_column_widths(self):
1092 if self.receive_tab_mode == 0:
1093 widths = [ self.receive_list.columnWidth(0) ]
1096 for i in range(self.receive_list.columnCount() -1):
1097 widths.append(self.receive_list.columnWidth(i))
1098 self.column_widths["receive"][self.receive_tab_mode] = widths
1100 self.column_widths["history"] = []
1101 for i in range(self.history_list.columnCount() - 1):
1102 self.column_widths["history"].append(self.history_list.columnWidth(i))
1104 self.column_widths["contacts"] = []
1105 for i in range(self.contacts_list.columnCount() - 1):
1106 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1109 def create_contacts_tab(self):
1110 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1111 l.setContextMenuPolicy(Qt.CustomContextMenu)
1112 l.customContextMenuRequested.connect(self.create_contact_menu)
1113 for i,width in enumerate(self.column_widths['contacts']):
1114 l.setColumnWidth(i, width)
1116 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1117 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1118 self.contacts_list = l
1119 self.contacts_buttons_hbox = hbox
1120 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1125 def delete_imported_key(self, addr):
1126 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1127 self.wallet.imported_keys.pop(addr)
1128 self.update_receive_tab()
1129 self.update_history_tab()
1133 def create_receive_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.receive_list.selectedIndexes()
1138 item = self.receive_list.itemAt(position)
1140 addr = unicode(item.text(0))
1142 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1143 if self.receive_tab_mode == 2:
1144 menu.addAction(_("Request amount"), lambda: self.edit_amount())
1145 menu.addAction(_("QR code"), lambda: ElectrumWindow.show_qrcode("bitcoin:" + addr, _("Address")) )
1146 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1147 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
1148 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1149 if addr in self.wallet.imported_keys:
1150 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1152 if self.receive_tab_mode == 1:
1153 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1154 menu.addAction(t, lambda: self.toggle_freeze(addr))
1155 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1156 menu.addAction(t, lambda: self.toggle_priority(addr))
1158 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1161 def payto(self, x, is_alias):
1168 label = self.wallet.labels.get(addr)
1169 m_addr = label + ' <' + addr + '>' if label else addr
1170 self.tabs.setCurrentIndex(1)
1171 self.payto_e.setText(m_addr)
1172 self.amount_e.setFocus()
1174 def delete_contact(self, x, is_alias):
1175 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1176 if not is_alias and x in self.wallet.addressbook:
1177 self.wallet.addressbook.remove(x)
1178 if x in self.wallet.labels.keys():
1179 self.wallet.labels.pop(x)
1180 elif is_alias and x in self.wallet.aliases:
1181 self.wallet.aliases.pop(x)
1182 self.update_history_tab()
1183 self.update_contacts_tab()
1184 self.update_completions()
1186 def create_contact_menu(self, position):
1187 # fixme: this function apparently has a side effect.
1188 # if it is not called the menu pops up several times
1189 #self.contacts_list.selectedIndexes()
1191 item = self.contacts_list.itemAt(position)
1193 addr = unicode(item.text(0))
1194 label = unicode(item.text(1))
1195 is_alias = label in self.wallet.aliases.keys()
1196 x = label if is_alias else addr
1198 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1199 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1200 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1202 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1204 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1205 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1206 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1209 def update_receive_item(self, item):
1210 address = str(item.data(0,0).toString())
1211 label = self.wallet.labels.get(address,'')
1212 item.setData(1,0,label)
1215 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1217 amount, currency = None, None
1219 amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
1220 item.setData(2,0,amount_str)
1222 c, u = self.wallet.get_addr_balance(address)
1223 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1224 item.setData(3,0,balance)
1226 if self.receive_tab_mode == 1:
1227 if address in self.wallet.frozen_addresses:
1228 item.setBackgroundColor(0, QColor('lightblue'))
1229 elif address in self.wallet.prioritized_addresses:
1230 item.setBackgroundColor(0, QColor('lightgreen'))
1233 def update_receive_tab(self):
1234 l = self.receive_list
1237 l.setColumnHidden(2, not self.receive_tab_mode == 2)
1238 l.setColumnHidden(3, self.receive_tab_mode == 0)
1239 l.setColumnHidden(4, not self.receive_tab_mode == 1)
1240 if self.receive_tab_mode == 0:
1241 width = self.column_widths['receive'][0][0]
1242 l.setColumnWidth(0, width)
1244 for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
1245 l.setColumnWidth(i, width)
1248 for k, account in self.wallet.accounts.items():
1249 name = account.get('name',str(k))
1250 c,u = self.wallet.get_account_balance(k)
1251 account_item = QTreeWidgetItem( [ name, '', '', format_satoshis(c+u), ''] )
1252 l.addTopLevelItem(account_item)
1253 account_item.setExpanded(True)
1256 for is_change in [0,1]:
1257 name = "Receiving" if not is_change else "Change"
1258 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1259 account_item.addChild(seq_item)
1260 if not is_change: seq_item.setExpanded(True)
1264 for address in account[is_change]:
1265 h = self.wallet.history.get(address,[])
1270 if gap > self.wallet.gap_limit:
1275 num_tx = '*' if h == ['*'] else "%d"%len(h)
1276 item = QTreeWidgetItem( [ address, '', '', '', num_tx] )
1277 item.setFont(0, QFont(MONOSPACE_FONT))
1278 item.setFont(2, QFont(MONOSPACE_FONT))
1279 self.update_receive_item(item)
1281 item.setBackgroundColor(1, QColor('red'))
1282 seq_item.addChild(item)
1284 if self.wallet.imported_keys:
1285 c,u = self.wallet.get_imported_balance()
1286 account_item = QTreeWidgetItem( [ _('Imported'), '', '', format_satoshis(c+u), ''] )
1287 l.addTopLevelItem(account_item)
1288 account_item.setExpanded(True)
1289 for address in self.wallet.imported_keys.keys():
1290 item = QTreeWidgetItem( [ address, '', '', '', ''] )
1291 item.setFont(0, QFont(MONOSPACE_FONT))
1292 item.setFont(2, QFont(MONOSPACE_FONT))
1293 self.update_receive_item(item)
1294 account_item.addChild(item)
1297 # we use column 1 because column 0 may be hidden
1298 l.setCurrentItem(l.topLevelItem(0),1)
1300 def show_contact_details(self, m):
1301 a = self.wallet.aliases.get(m)
1303 if a[0] in self.wallet.authorities.keys():
1304 s = self.wallet.authorities.get(a[0])
1307 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1308 QMessageBox.information(self, 'Alias', msg, 'OK')
1310 def update_contacts_tab(self):
1312 l = self.contacts_list
1316 for alias, v in self.wallet.aliases.items():
1318 alias_targets.append(target)
1319 item = QTreeWidgetItem( [ target, alias, '-'] )
1320 item.setBackgroundColor(0, QColor('lightgray'))
1321 l.addTopLevelItem(item)
1323 for address in self.wallet.addressbook:
1324 if address in alias_targets: continue
1325 label = self.wallet.labels.get(address,'')
1327 for tx in self.wallet.transactions.values():
1328 if address in map(lambda x: x[0], tx.outputs): n += 1
1330 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1331 item.setFont(0, QFont(MONOSPACE_FONT))
1332 l.addTopLevelItem(item)
1334 l.setCurrentItem(l.topLevelItem(0))
1337 def create_console_tab(self):
1338 from qt_console import Console
1339 from electrum import util, bitcoin, commands
1340 self.console = console = Console()
1341 self.console.history = self.config.get("console-history",[])
1342 self.console.history_index = len(self.console.history)
1344 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1345 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1347 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1349 def mkfunc(f, method):
1350 return lambda *args: apply( f, (method, args, self.password_dialog ))
1352 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1353 methods[m] = mkfunc(c._run, m)
1355 console.updateNamespace(methods)
1359 def create_status_bar(self):
1360 self.status_text = ""
1362 sb.setFixedHeight(35)
1363 qtVersion = qVersion()
1365 update_notification = UpdateLabel(self.config)
1366 if(update_notification.new_version):
1367 sb.addPermanentWidget(update_notification)
1369 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1370 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1371 if self.wallet.seed:
1372 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1373 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1374 if self.wallet.seed:
1375 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1376 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1377 sb.addPermanentWidget( self.status_button )
1379 self.setStatusBar(sb)
1383 self.config.set_key('gui', 'lite', True)
1386 self.lite.mini.show()
1388 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1389 self.lite.main(None)
1391 def new_contact_dialog(self):
1392 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1393 address = unicode(text)
1395 if is_valid(address):
1396 self.wallet.addressbook.append(address)
1398 self.update_contacts_tab()
1399 self.update_history_tab()
1400 self.update_completions()
1402 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1404 def show_master_public_key(self):
1405 dialog = QDialog(None)
1407 dialog.setWindowTitle(_("Master Public Key"))
1409 main_text = QTextEdit()
1410 main_text.setText(self.wallet.get_master_public_key())
1411 main_text.setReadOnly(True)
1412 main_text.setMaximumHeight(170)
1413 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1415 ok_button = QPushButton(_("OK"))
1416 ok_button.setDefault(True)
1417 ok_button.clicked.connect(dialog.accept)
1419 main_layout = QGridLayout()
1420 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1422 main_layout.addWidget(main_text, 1, 0)
1423 main_layout.addWidget(qrw, 1, 1 )
1425 vbox = QVBoxLayout()
1426 vbox.addLayout(main_layout)
1427 hbox = QHBoxLayout()
1429 hbox.addWidget(ok_button)
1430 vbox.addLayout(hbox)
1432 dialog.setLayout(vbox)
1437 def show_seed_dialog(self, wallet, parent=None):
1439 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1442 if wallet.use_encryption:
1443 password = parent.password_dialog()
1450 seed = wallet.decode_seed(password)
1452 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1455 self.show_seed(seed)
1458 def show_seed(self, seed):
1459 dialog = QDialog(None)
1461 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1463 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1465 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1467 seed_text = QTextEdit(brainwallet)
1468 seed_text.setReadOnly(True)
1469 seed_text.setMaximumHeight(130)
1471 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1472 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1473 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1474 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1475 label2 = QLabel(msg2)
1476 label2.setWordWrap(True)
1479 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1480 logo.setMaximumWidth(60)
1482 qrw = QRCodeWidget(seed, 4)
1484 ok_button = QPushButton(_("OK"))
1485 ok_button.setDefault(True)
1486 ok_button.clicked.connect(dialog.accept)
1488 grid = QGridLayout()
1489 #main_layout.addWidget(logo, 0, 0)
1491 grid.addWidget(logo, 0, 0)
1492 grid.addWidget(label1, 0, 1)
1494 grid.addWidget(seed_text, 1, 0, 1, 2)
1496 grid.addWidget(qrw, 0, 2, 2, 1)
1498 vbox = QVBoxLayout()
1499 vbox.addLayout(grid)
1500 vbox.addWidget(label2)
1502 hbox = QHBoxLayout()
1504 hbox.addWidget(ok_button)
1505 vbox.addLayout(hbox)
1507 dialog.setLayout(vbox)
1511 def show_qrcode(data, title = "QR code"):
1515 d.setWindowTitle(title)
1516 d.setMinimumSize(270, 300)
1517 vbox = QVBoxLayout()
1518 qrw = QRCodeWidget(data)
1519 vbox.addWidget(qrw, 1)
1520 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1521 hbox = QHBoxLayout()
1525 filename = "qrcode.bmp"
1526 bmp.save_qrcode(qrw.qr, filename)
1527 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1529 b = QPushButton(_("Save"))
1531 b.clicked.connect(print_qr)
1533 b = QPushButton(_("Close"))
1535 b.clicked.connect(d.accept)
1538 vbox.addLayout(hbox)
1542 def view_private_key(self,address):
1543 if not address: return
1544 if self.wallet.use_encryption:
1545 password = self.password_dialog()
1552 pk = self.wallet.get_private_key(address, password)
1553 except BaseException, e:
1554 self.show_message(str(e))
1557 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1560 def sign_message(self,address):
1561 if not address: return
1564 d.setWindowTitle(_('Sign Message'))
1565 d.setMinimumSize(410, 290)
1567 tab_widget = QTabWidget()
1569 layout = QGridLayout(tab)
1571 sign_address = QLineEdit()
1573 sign_address.setText(address)
1574 layout.addWidget(QLabel(_('Address')), 1, 0)
1575 layout.addWidget(sign_address, 1, 1)
1577 sign_message = QTextEdit()
1578 layout.addWidget(QLabel(_('Message')), 2, 0)
1579 layout.addWidget(sign_message, 2, 1)
1580 layout.setRowStretch(2,3)
1582 sign_signature = QTextEdit()
1583 layout.addWidget(QLabel(_('Signature')), 3, 0)
1584 layout.addWidget(sign_signature, 3, 1)
1585 layout.setRowStretch(3,1)
1588 if self.wallet.use_encryption:
1589 password = self.password_dialog()
1596 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1597 sign_signature.setText(signature)
1598 except BaseException, e:
1599 self.show_message(str(e))
1602 hbox = QHBoxLayout()
1603 b = QPushButton(_("Sign"))
1605 b.clicked.connect(do_sign)
1606 b = QPushButton(_("Close"))
1607 b.clicked.connect(d.accept)
1609 layout.addLayout(hbox, 4, 1)
1610 tab_widget.addTab(tab, _("Sign"))
1614 layout = QGridLayout(tab)
1616 verify_address = QLineEdit()
1617 layout.addWidget(QLabel(_('Address')), 1, 0)
1618 layout.addWidget(verify_address, 1, 1)
1620 verify_message = QTextEdit()
1621 layout.addWidget(QLabel(_('Message')), 2, 0)
1622 layout.addWidget(verify_message, 2, 1)
1623 layout.setRowStretch(2,3)
1625 verify_signature = QTextEdit()
1626 layout.addWidget(QLabel(_('Signature')), 3, 0)
1627 layout.addWidget(verify_signature, 3, 1)
1628 layout.setRowStretch(3,1)
1632 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1633 self.show_message(_("Signature verified"))
1634 except BaseException, e:
1635 self.show_message(str(e))
1638 hbox = QHBoxLayout()
1639 b = QPushButton(_("Verify"))
1640 b.clicked.connect(do_verify)
1642 b = QPushButton(_("Close"))
1643 b.clicked.connect(d.accept)
1645 layout.addLayout(hbox, 4, 1)
1646 tab_widget.addTab(tab, _("Verify"))
1648 vbox = QVBoxLayout()
1649 vbox.addWidget(tab_widget)
1654 def toggle_QR_window(self, show):
1655 if show and not self.qr_window:
1656 self.qr_window = QR_Window(self.exchanger)
1657 self.qr_window.setVisible(True)
1658 self.qr_window_geometry = self.qr_window.geometry()
1659 item = self.receive_list.currentItem()
1661 address = str(item.text(1))
1662 label = self.wallet.labels.get(address)
1663 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1664 self.qr_window.set_content( address, label, amount, currency )
1666 elif show and self.qr_window and not self.qr_window.isVisible():
1667 self.qr_window.setVisible(True)
1668 self.qr_window.setGeometry(self.qr_window_geometry)
1670 elif not show and self.qr_window and self.qr_window.isVisible():
1671 self.qr_window_geometry = self.qr_window.geometry()
1672 self.qr_window.setVisible(False)
1674 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1675 self.receive_list.setColumnHidden(2, self.qr_window is None or not self.qr_window.isVisible())
1676 #self.receive_list.setColumnWidth(1, 200)
1679 def question(self, msg):
1680 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1682 def show_message(self, msg):
1683 QMessageBox.information(self, _('Message'), msg, _('OK'))
1685 def password_dialog(self ):
1692 vbox = QVBoxLayout()
1693 msg = _('Please enter your password')
1694 vbox.addWidget(QLabel(msg))
1696 grid = QGridLayout()
1698 grid.addWidget(QLabel(_('Password')), 1, 0)
1699 grid.addWidget(pw, 1, 1)
1700 vbox.addLayout(grid)
1702 vbox.addLayout(ok_cancel_buttons(d))
1705 if not d.exec_(): return
1706 return unicode(pw.text())
1713 def change_password_dialog( wallet, parent=None ):
1716 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1724 new_pw = QLineEdit()
1725 new_pw.setEchoMode(2)
1726 conf_pw = QLineEdit()
1727 conf_pw.setEchoMode(2)
1729 vbox = QVBoxLayout()
1731 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1732 +_('To disable wallet encryption, enter an empty new password.')) \
1733 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1735 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1736 +_("Leave these fields empty if you want to disable encryption.")
1737 vbox.addWidget(QLabel(msg))
1739 grid = QGridLayout()
1742 if wallet.use_encryption:
1743 grid.addWidget(QLabel(_('Password')), 1, 0)
1744 grid.addWidget(pw, 1, 1)
1746 grid.addWidget(QLabel(_('New Password')), 2, 0)
1747 grid.addWidget(new_pw, 2, 1)
1749 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1750 grid.addWidget(conf_pw, 3, 1)
1751 vbox.addLayout(grid)
1753 vbox.addLayout(ok_cancel_buttons(d))
1756 if not d.exec_(): return
1758 password = unicode(pw.text()) if wallet.use_encryption else None
1759 new_password = unicode(new_pw.text())
1760 new_password2 = unicode(conf_pw.text())
1763 seed = wallet.decode_seed(password)
1765 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1768 if new_password != new_password2:
1769 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1770 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1772 wallet.update_password(seed, password, new_password)
1775 def seed_dialog(wallet, parent=None):
1779 vbox = QVBoxLayout()
1780 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1781 vbox.addWidget(QLabel(msg))
1783 grid = QGridLayout()
1786 seed_e = QLineEdit()
1787 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1788 grid.addWidget(seed_e, 1, 1)
1792 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1793 grid.addWidget(gap_e, 2, 1)
1794 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1795 vbox.addLayout(grid)
1797 vbox.addLayout(ok_cancel_buttons(d))
1800 if not d.exec_(): return
1803 gap = int(unicode(gap_e.text()))
1805 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1809 seed = str(seed_e.text())
1812 print_error("Warning: Not hex, trying decode")
1814 seed = mnemonic.mn_decode( seed.split(' ') )
1816 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1820 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1825 def generate_transaction_information_widget(self, tx):
1826 tabs = QTabWidget(self)
1829 grid_ui = QGridLayout(tab1)
1830 grid_ui.setColumnStretch(0,1)
1831 tabs.addTab(tab1, _('Outputs') )
1833 tree_widget = MyTreeWidget(self)
1834 tree_widget.setColumnCount(2)
1835 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1836 tree_widget.setColumnWidth(0, 300)
1837 tree_widget.setColumnWidth(1, 50)
1839 for output in tx.d["outputs"]:
1840 item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
1841 tree_widget.addTopLevelItem(item)
1843 tree_widget.setMaximumHeight(100)
1845 grid_ui.addWidget(tree_widget)
1848 grid_ui = QGridLayout(tab2)
1849 grid_ui.setColumnStretch(0,1)
1850 tabs.addTab(tab2, _('Inputs') )
1852 tree_widget = MyTreeWidget(self)
1853 tree_widget.setColumnCount(2)
1854 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1856 for input_line in tx.inputs:
1857 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1858 tree_widget.addTopLevelItem(item)
1860 tree_widget.setMaximumHeight(100)
1862 grid_ui.addWidget(tree_widget)
1866 def tx_dict_from_text(self, txt):
1868 tx_dict = json.loads(str(txt))
1869 assert "hex" in tx_dict.keys()
1870 assert "complete" in tx_dict.keys()
1871 if not tx_dict["complete"]:
1872 assert "input_info" in tx_dict.keys()
1874 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1879 def read_tx_from_file(self):
1880 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1884 with open(fileName, "r") as f:
1885 file_content = f.read()
1886 except (ValueError, IOError, os.error), reason:
1887 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1889 return self.tx_dict_from_text(file_content)
1892 def sign_raw_transaction(self, tx, input_info):
1893 if self.wallet.use_encryption:
1894 password = self.password_dialog()
1901 self.wallet.signrawtransaction(tx, input_info, [], password)
1903 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1905 with open(fileName, "w+") as f:
1906 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1907 self.show_message(_("Transaction saved succesfully"))
1908 except BaseException, e:
1909 self.show_message(str(e))
1912 def create_sign_transaction_window(self, tx_dict):
1913 tx = Transaction(tx_dict["hex"])
1915 dialog = QDialog(self)
1916 dialog.setMinimumWidth(500)
1917 dialog.setWindowTitle(_('Sign unsigned transaction'))
1920 vbox = QVBoxLayout()
1921 dialog.setLayout(vbox)
1922 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1924 if tx_dict["complete"] == True:
1925 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1927 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1928 vbox.addLayout(ok_cancel_buttons(dialog))
1929 input_info = json.loads(tx_dict["input_info"])
1932 self.sign_raw_transaction(tx, input_info)
1936 def do_sign_from_text(self):
1937 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1940 tx_dict = self.tx_dict_from_text(unicode(txt))
1942 self.create_sign_transaction_window(tx_dict)
1945 def do_sign_from_file(self):
1946 tx_dict = self.read_tx_from_file()
1948 self.create_sign_transaction_window(tx_dict)
1951 def send_raw_transaction(self, raw_tx):
1952 result, result_message = self.wallet.sendtx( raw_tx )
1954 self.show_message("Transaction succesfully sent: %s" % (result_message))
1956 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1959 def create_send_transaction_window(self, tx_dict):
1960 tx = Transaction(tx_dict["hex"])
1962 dialog = QDialog(self)
1963 dialog.setMinimumWidth(500)
1964 dialog.setWindowTitle(_('Send raw transaction'))
1967 vbox = QVBoxLayout()
1968 dialog.setLayout(vbox)
1969 vbox.addWidget( self.generate_transaction_information_widget(tx))
1971 if tx_dict["complete"] == False:
1972 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1974 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1975 vbox.addLayout(ok_cancel_buttons(dialog))
1978 self.send_raw_transaction(tx_dict["hex"])
1981 def do_send_from_file(self):
1982 tx_dict = self.read_tx_from_file()
1984 self.create_send_transaction_window(tx_dict)
1987 def do_send_from_text(self):
1988 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1991 tx_dict = self.tx_dict_from_text(unicode(txt))
1993 self.create_send_transaction_window(tx_dict)
1996 def do_export_privkeys(self):
1997 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.")))
1999 if self.wallet.use_encryption:
2000 password = self.password_dialog()
2006 select_export = _('Select file to export your private keys to')
2007 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
2009 with open(fileName, "w+") as csvfile:
2010 transaction = csv.writer(csvfile)
2011 transaction.writerow(["address", "private_key"])
2014 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
2015 transaction.writerow(["%34s"%addr,pk])
2017 self.show_message(_("Private keys exported."))
2019 except (IOError, os.error), reason:
2020 export_error_label = _("Electrum was unable to produce a private key-export.")
2021 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
2023 except BaseException, e:
2024 self.show_message(str(e))
2028 def do_import_labels(self):
2029 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
2030 if not labelsFile: return
2032 f = open(labelsFile, 'r')
2035 for key, value in json.loads(data).items():
2036 self.wallet.labels[key] = value
2038 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
2039 except (IOError, os.error), reason:
2040 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2044 def do_export_labels(self):
2045 labels = self.wallet.labels
2047 labelsFile = util.user_dir() + '/labels.dat'
2048 f = open(labelsFile, 'w+')
2049 json.dump(labels, f)
2051 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
2052 except (IOError, os.error), reason:
2053 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
2055 def do_export_history(self):
2056 from gui_lite import csv_transaction
2057 csv_transaction(self.wallet)
2059 def do_import_privkey(self):
2060 if not self.wallet.imported_keys:
2061 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
2062 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
2063 + _('Are you sure you understand what you are doing?'), 3, 4)
2066 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
2068 sec = str(text).strip()
2069 if self.wallet.use_encryption:
2070 password = self.password_dialog()
2076 addr = self.wallet.import_key(sec, password)
2078 QMessageBox.critical(None, _("Unable to import key"), "error")
2080 QMessageBox.information(None, _("Key imported"), addr)
2081 self.update_receive_tab()
2082 self.update_history_tab()
2083 except BaseException as e:
2084 QMessageBox.critical(None, _("Unable to import key"), str(e))
2086 def settings_dialog(self):
2088 d.setWindowTitle(_('Electrum Settings'))
2090 vbox = QVBoxLayout()
2092 tabs = QTabWidget(self)
2093 vbox.addWidget(tabs)
2096 grid_ui = QGridLayout(tab1)
2097 grid_ui.setColumnStretch(0,1)
2098 tabs.addTab(tab1, _('Display') )
2100 nz_label = QLabel(_('Display zeros'))
2101 grid_ui.addWidget(nz_label, 3, 0)
2103 nz_e.setText("%d"% self.wallet.num_zeros)
2104 grid_ui.addWidget(nz_e, 3, 1)
2105 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2106 grid_ui.addWidget(HelpButton(msg), 3, 2)
2107 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
2108 if not self.config.is_modifiable('num_zeros'):
2109 for w in [nz_e, nz_label]: w.setEnabled(False)
2111 lang_label=QLabel(_('Language') + ':')
2112 grid_ui.addWidget(lang_label , 8, 0)
2113 lang_combo = QComboBox()
2114 from i18n import languages
2115 lang_combo.addItems(languages.values())
2117 index = languages.keys().index(self.config.get("language",''))
2120 lang_combo.setCurrentIndex(index)
2121 grid_ui.addWidget(lang_combo, 8, 1)
2122 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
2123 if not self.config.is_modifiable('language'):
2124 for w in [lang_combo, lang_label]: w.setEnabled(False)
2126 currencies = self.exchanger.get_currencies()
2127 currencies.insert(0, "None")
2129 cur_label=QLabel(_('Currency') + ':')
2130 grid_ui.addWidget(cur_label , 9, 0)
2131 cur_combo = QComboBox()
2132 cur_combo.addItems(currencies)
2134 index = currencies.index(self.config.get('currency', "None"))
2137 cur_combo.setCurrentIndex(index)
2138 grid_ui.addWidget(cur_combo, 9, 1)
2139 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
2141 view_label=QLabel(_('Receive Tab') + ':')
2142 grid_ui.addWidget(view_label , 10, 0)
2143 view_combo = QComboBox()
2144 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
2145 view_combo.setCurrentIndex(self.receive_tab_mode)
2146 grid_ui.addWidget(view_combo, 10, 1)
2147 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
2148 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
2149 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
2150 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
2152 grid_ui.addWidget(HelpButton(hh), 10, 2)
2156 grid_wallet = QGridLayout(tab2)
2157 grid_wallet.setColumnStretch(0,1)
2158 tabs.addTab(tab2, _('Wallet') )
2160 fee_label = QLabel(_('Transaction fee'))
2161 grid_wallet.addWidget(fee_label, 0, 0)
2163 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
2164 grid_wallet.addWidget(fee_e, 0, 1)
2165 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
2166 + _('Recommended value') + ': 0.001'
2167 grid_wallet.addWidget(HelpButton(msg), 0, 2)
2168 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
2169 if not self.config.is_modifiable('fee'):
2170 for w in [fee_e, fee_label]: w.setEnabled(False)
2172 usechange_label = QLabel(_('Use change addresses'))
2173 grid_wallet.addWidget(usechange_label, 1, 0)
2174 usechange_combo = QComboBox()
2175 usechange_combo.addItems([_('Yes'), _('No')])
2176 usechange_combo.setCurrentIndex(not self.wallet.use_change)
2177 grid_wallet.addWidget(usechange_combo, 1, 1)
2178 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
2179 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
2181 gap_label = QLabel(_('Gap limit'))
2182 grid_wallet.addWidget(gap_label, 2, 0)
2184 gap_e.setText("%d"% self.wallet.gap_limit)
2185 grid_wallet.addWidget(gap_e, 2, 1)
2186 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2187 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2188 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2189 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2190 + _('Warning') + ': ' \
2191 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2192 + _('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'
2193 grid_wallet.addWidget(HelpButton(msg), 2, 2)
2194 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
2195 if not self.config.is_modifiable('gap_limit'):
2196 for w in [gap_e, gap_label]: w.setEnabled(False)
2198 grid_wallet.setRowStretch(3,1)
2203 grid_io = QGridLayout(tab3)
2204 grid_io.setColumnStretch(0,1)
2205 tabs.addTab(tab3, _('Import/Export') )
2207 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2208 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2209 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2210 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2212 grid_io.addWidget(QLabel(_('History')), 2, 0)
2213 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2214 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2216 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2218 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2219 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2220 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2222 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2223 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2224 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2225 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2226 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2228 grid_io.setRowStretch(4,1)
2231 grid_raw = QGridLayout(tab4)
2232 grid_raw.setColumnStretch(0,1)
2233 tabs.addTab(tab4, _('Raw tx') ) # move this to wallet tab
2235 if self.wallet.seed:
2236 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2237 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2238 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2239 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2241 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2242 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2243 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2244 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2245 grid_raw.setRowStretch(3,1)
2249 grid_plugins = QGridLayout(tab5)
2250 grid_plugins.setColumnStretch(0,1)
2251 tabs.addTab(tab5, _('Plugins') )
2252 for i, p in enumerate(self.wallet.plugins):
2254 name, description = p.get_info()
2255 cb = QCheckBox(name)
2256 cb.setChecked(p.is_enabled())
2257 cb.stateChanged.connect(lambda: cb.setChecked(p.toggle(self)))
2258 grid_plugins.addWidget(cb, i, 0)
2259 grid_plugins.addWidget(HelpButton(description), i, 2)
2261 print_msg("Error: cannot display plugin", p)
2262 traceback.print_exc(file=sys.stdout)
2264 grid_plugins.setRowStretch(i+1,1)
2266 vbox.addLayout(ok_cancel_buttons(d))
2270 if not d.exec_(): return
2272 fee = unicode(fee_e.text())
2274 fee = int( 100000000 * Decimal(fee) )
2276 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2279 if self.wallet.fee != fee:
2280 self.wallet.fee = fee
2283 nz = unicode(nz_e.text())
2288 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2291 if self.wallet.num_zeros != nz:
2292 self.wallet.num_zeros = nz
2293 self.config.set_key('num_zeros', nz, True)
2294 self.update_history_tab()
2295 self.update_receive_tab()
2297 usechange_result = usechange_combo.currentIndex() == 0
2298 if self.wallet.use_change != usechange_result:
2299 self.wallet.use_change = usechange_result
2300 self.config.set_key('use_change', self.wallet.use_change, True)
2303 n = int(gap_e.text())
2305 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2308 if self.wallet.gap_limit != n:
2309 r = self.wallet.change_gap_limit(n)
2311 self.update_receive_tab()
2312 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2314 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2316 need_restart = False
2318 lang_request = languages.keys()[lang_combo.currentIndex()]
2319 if lang_request != self.config.get('language'):
2320 self.config.set_key("language", lang_request, True)
2323 cur_request = str(currencies[cur_combo.currentIndex()])
2324 if cur_request != self.config.get('currency', "None"):
2325 self.config.set_key('currency', cur_request, True)
2326 self.update_wallet()
2329 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2331 self.receive_tab_set_mode(view_combo.currentIndex())
2335 def network_dialog(wallet, parent=None):
2336 interface = wallet.interface
2338 if interface.is_connected:
2339 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2341 status = _("Not connected")
2342 server = interface.server
2345 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2346 server = interface.server
2348 plist, servers_list = interface.get_servers_list()
2352 d.setWindowTitle(_('Server'))
2353 d.setMinimumSize(375, 20)
2355 vbox = QVBoxLayout()
2358 hbox = QHBoxLayout()
2360 l.setPixmap(QPixmap(":icons/network.png"))
2363 hbox.addWidget(QLabel(status))
2365 vbox.addLayout(hbox)
2369 grid = QGridLayout()
2371 vbox.addLayout(grid)
2374 server_protocol = QComboBox()
2375 server_host = QLineEdit()
2376 server_host.setFixedWidth(200)
2377 server_port = QLineEdit()
2378 server_port.setFixedWidth(60)
2380 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2381 protocol_letters = 'thsg'
2382 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2383 server_protocol.addItems(protocol_names)
2385 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2386 grid.addWidget(server_protocol, 0, 1)
2387 grid.addWidget(server_host, 0, 2)
2388 grid.addWidget(server_port, 0, 3)
2390 def change_protocol(p):
2391 protocol = protocol_letters[p]
2392 host = unicode(server_host.text())
2393 pp = plist.get(host,DEFAULT_PORTS)
2394 if protocol not in pp.keys():
2395 protocol = pp.keys()[0]
2397 server_host.setText( host )
2398 server_port.setText( port )
2400 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2402 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2403 servers_list_widget = QTreeWidget(parent)
2404 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2405 servers_list_widget.setMaximumHeight(150)
2406 servers_list_widget.setColumnWidth(0, 240)
2407 for _host in servers_list.keys():
2408 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2409 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2411 def change_server(host, protocol=None):
2412 pp = plist.get(host,DEFAULT_PORTS)
2414 port = pp.get(protocol)
2415 if not port: protocol = None
2418 if 't' in pp.keys():
2420 port = pp.get(protocol)
2422 protocol = pp.keys()[0]
2423 port = pp.get(protocol)
2425 server_host.setText( host )
2426 server_port.setText( port )
2427 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2429 if not plist: return
2430 for p in protocol_letters:
2431 i = protocol_letters.index(p)
2432 j = server_protocol.model().index(i,0)
2433 if p not in pp.keys():
2434 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2436 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2440 host, port, protocol = server.split(':')
2441 change_server(host,protocol)
2443 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2444 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2446 if not wallet.config.is_modifiable('server'):
2447 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2450 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2451 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2452 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2453 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2456 proxy_mode = QComboBox()
2457 proxy_host = QLineEdit()
2458 proxy_host.setFixedWidth(200)
2459 proxy_port = QLineEdit()
2460 proxy_port.setFixedWidth(60)
2461 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2463 def check_for_disable(index = False):
2464 if proxy_mode.currentText() != 'NONE':
2465 proxy_host.setEnabled(True)
2466 proxy_port.setEnabled(True)
2468 proxy_host.setEnabled(False)
2469 proxy_port.setEnabled(False)
2472 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2474 if not wallet.config.is_modifiable('proxy'):
2475 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2477 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2478 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2479 proxy_host.setText(proxy_config.get("host"))
2480 proxy_port.setText(proxy_config.get("port"))
2482 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2483 grid.addWidget(proxy_mode, 2, 1)
2484 grid.addWidget(proxy_host, 2, 2)
2485 grid.addWidget(proxy_port, 2, 3)
2488 vbox.addLayout(ok_cancel_buttons(d))
2491 if not d.exec_(): return
2493 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2494 if proxy_mode.currentText() != 'NONE':
2495 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2499 wallet.config.set_key("proxy", proxy, True)
2500 wallet.config.set_key("server", server, True)
2501 interface.set_server(server, proxy)
2502 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2505 def closeEvent(self, event):
2507 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2508 self.save_column_widths()
2509 self.config.set_key("column-widths", self.column_widths, True)
2510 self.config.set_key("console-history",self.console.history[-50:])
2516 def __init__(self, wallet, config, app=None):
2517 self.wallet = wallet
2518 self.config = config
2520 self.app = QApplication(sys.argv)
2523 def restore_or_create(self):
2524 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2525 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2526 if r==2: return None
2527 return 'restore' if r==1 else 'create'
2529 def seed_dialog(self):
2530 return ElectrumWindow.seed_dialog( self.wallet )
2532 def network_dialog(self):
2533 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2536 def show_seed(self):
2537 ElectrumWindow.show_seed_dialog(self.wallet)
2540 def password_dialog(self):
2541 ElectrumWindow.change_password_dialog(self.wallet)
2544 def restore_wallet(self):
2545 wallet = self.wallet
2546 # wait until we are connected, because the user might have selected another server
2547 if not wallet.interface.is_connected:
2548 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2549 waiting_dialog(waiting)
2551 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2552 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2554 wallet.set_up_to_date(False)
2555 wallet.interface.poke('synchronizer')
2556 waiting_dialog(waiting)
2557 if wallet.is_found():
2558 print_error( "Recovery successful" )
2560 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2567 w = ElectrumWindow(self.wallet, self.config)
2568 if url: w.set_url(url)