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
22 import os.path, json, ast
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 electrum.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 electrum.wallet import format_satoshis
41 from electrum.bitcoin import Transaction, is_valid
42 from electrum import mnemonic
44 import bmp, pyqrnative, qrscanner
47 from decimal import Decimal
55 if platform.system() == 'Windows':
56 MONOSPACE_FONT = 'Lucida Console'
57 elif platform.system() == 'Darwin':
58 MONOSPACE_FONT = 'Monaco'
60 MONOSPACE_FONT = 'monospace'
62 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
64 from electrum import ELECTRUM_VERSION
67 class UpdateLabel(QtGui.QLabel):
68 def __init__(self, config, parent=None):
69 QtGui.QLabel.__init__(self, parent)
70 self.new_version = False
73 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
74 con.request("GET", "/version")
75 res = con.getresponse()
76 except socket.error as msg:
77 print_error("Could not retrieve version information")
81 self.latest_version = res.read()
82 self.latest_version = self.latest_version.replace("\n","")
83 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
85 self.current_version = ELECTRUM_VERSION
86 if(self.compare_versions(self.latest_version, self.current_version) == 1):
87 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
88 if(self.compare_versions(self.latest_version, latest_seen) == 1):
89 self.new_version = True
90 self.setText(_("New version available") + ": " + self.latest_version)
93 def compare_versions(self, version1, version2):
95 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
96 return cmp(normalize(version1), normalize(version2))
98 def ignore_this_version(self):
100 self.config.set_key("last_seen_version", self.latest_version, True)
101 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
104 def ignore_all_version(self):
106 self.config.set_key("last_seen_version", "9.9.9", True)
107 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
110 def open_website(self):
111 webbrowser.open("http://electrum.org/download.html")
114 def mouseReleaseEvent(self, event):
115 dialog = QDialog(self)
116 dialog.setWindowTitle(_('Electrum update'))
119 main_layout = QGridLayout()
120 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
122 ignore_version = QPushButton(_("Ignore this version"))
123 ignore_version.clicked.connect(self.ignore_this_version)
125 ignore_all_versions = QPushButton(_("Ignore all versions"))
126 ignore_all_versions.clicked.connect(self.ignore_all_version)
128 open_website = QPushButton(_("Goto download page"))
129 open_website.clicked.connect(self.open_website)
131 main_layout.addWidget(ignore_version, 1, 0)
132 main_layout.addWidget(ignore_all_versions, 1, 1)
133 main_layout.addWidget(open_website, 1, 2)
135 dialog.setLayout(main_layout)
139 if not dialog.exec_(): return
141 def numbify(entry, is_int = False):
142 text = unicode(entry.text()).strip()
143 pos = entry.cursorPosition()
145 if not is_int: chars +='.'
146 s = ''.join([i for i in text if i in chars])
150 s = s.replace('.','')
151 s = s[:p] + '.' + s[p:p+8]
153 amount = int( Decimal(s) * 100000000 )
162 entry.setCursorPosition(pos)
166 class Timer(QtCore.QThread):
169 self.emit(QtCore.SIGNAL('timersignal'))
172 class HelpButton(QPushButton):
173 def __init__(self, text):
174 QPushButton.__init__(self, '?')
175 self.setFocusPolicy(Qt.NoFocus)
176 self.setFixedWidth(20)
177 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
180 class EnterButton(QPushButton):
181 def __init__(self, text, func):
182 QPushButton.__init__(self, text)
184 self.clicked.connect(func)
186 def keyPressEvent(self, e):
187 if e.key() == QtCore.Qt.Key_Return:
190 class MyTreeWidget(QTreeWidget):
191 def __init__(self, parent):
192 QTreeWidget.__init__(self, parent)
195 for i in range(0,self.viewport().height()/5):
196 if self.itemAt(QPoint(0,i*5)) == item:
200 for j in range(0,30):
201 if self.itemAt(QPoint(0,i*5 + j)) != item:
203 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
205 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
210 class StatusBarButton(QPushButton):
211 def __init__(self, icon, tooltip, func):
212 QPushButton.__init__(self, icon, '')
213 self.setToolTip(tooltip)
215 self.setMaximumWidth(25)
216 self.clicked.connect(func)
219 def keyPressEvent(self, e):
220 if e.key() == QtCore.Qt.Key_Return:
224 class QRCodeWidget(QWidget):
226 def __init__(self, data = None, size=4):
227 QWidget.__init__(self)
228 self.setMinimumSize(210, 210)
236 def set_addr(self, addr):
237 if self.addr != addr:
243 if self.addr and not self.qr:
244 self.qr = pyqrnative.QRCode(self.size, pyqrnative.QRErrorCorrectLevel.L)
245 self.qr.addData(self.addr)
249 def paintEvent(self, e):
254 black = QColor(0, 0, 0, 255)
255 white = QColor(255, 255, 255, 255)
258 qp = QtGui.QPainter()
262 qp.drawRect(0, 0, 198, 198)
266 k = self.qr.getModuleCount()
267 qp = QtGui.QPainter()
270 boxsize = min(r.width(), r.height())*0.8/k
272 left = (r.width() - size)/2
273 top = (r.height() - size)/2
277 if self.qr.isDark(r, c):
283 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
288 class QR_Window(QWidget):
290 def __init__(self, exchanger):
291 QWidget.__init__(self)
292 self.exchanger = exchanger
293 self.setWindowTitle('Electrum - '+_('Invoice'))
294 self.setMinimumSize(800, 250)
298 self.setFocusPolicy(QtCore.Qt.NoFocus)
300 main_box = QHBoxLayout()
302 self.qrw = QRCodeWidget()
303 main_box.addWidget(self.qrw, 1)
306 main_box.addLayout(vbox)
308 self.address_label = QLabel("")
309 self.address_label.setFont(QFont(MONOSPACE_FONT))
310 vbox.addWidget(self.address_label)
312 self.label_label = QLabel("")
313 vbox.addWidget(self.label_label)
315 self.amount_label = QLabel("")
316 vbox.addWidget(self.amount_label)
319 self.setLayout(main_box)
322 def set_content(self, addr, label, amount, currency):
324 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
325 self.address_label.setText(address_text)
327 if currency == 'BTC': currency = None
331 self.amount = Decimal(amount) / self.exchanger.exchange(1, currency) if currency else amount
333 self.amount = Decimal(amount)
334 self.amount = self.amount.quantize(Decimal('1.0000'))
337 amount_text += "<span style='font-size: 18pt'>%s %s</span><br/>" % (amount, currency)
338 amount_text += "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % str(self.amount)
339 self.amount_label.setText(amount_text)
342 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
343 self.label_label.setText(label_text)
345 msg = 'bitcoin:'+self.address
346 if self.amount is not None:
347 msg += '?amount=%s'%(str( self.amount))
348 if self.label is not None:
349 msg += '&label=%s'%(self.label)
350 elif self.label is not None:
351 msg += '?label=%s'%(self.label)
353 self.qrw.set_addr( msg )
358 def waiting_dialog(f):
364 w.setWindowTitle('Electrum')
374 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
379 def ok_cancel_buttons(dialog):
382 b = QPushButton("OK")
384 b.clicked.connect(dialog.accept)
385 b = QPushButton("Cancel")
387 b.clicked.connect(dialog.reject)
391 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
392 "receive":[[310],[310,200,130,130],[310,200,130,130]] }
394 class ElectrumWindow(QMainWindow):
396 def __init__(self, wallet, config):
397 QMainWindow.__init__(self)
401 self.wallet.interface.register_callback('updated', self.update_callback)
402 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
403 self.wallet.interface.register_callback('disconnected', self.update_callback)
404 self.wallet.interface.register_callback('disconnecting', self.update_callback)
406 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
407 self.merchant_name = config.get('merchant_name', 'Invoice')
409 set_language(config.get('language'))
411 self.qr_window = None
412 self.funds_error = False
413 self.completions = QStringListModel()
415 self.tabs = tabs = QTabWidget(self)
416 self.column_widths = self.config.get("column-widths", default_column_widths )
417 tabs.addTab(self.create_history_tab(), _('History') )
418 tabs.addTab(self.create_send_tab(), _('Send') )
419 tabs.addTab(self.create_receive_tab(), _('Receive') )
420 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
421 tabs.addTab(self.create_console_tab(), _('Console') )
422 tabs.setMinimumSize(600, 400)
423 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
424 self.setCentralWidget(tabs)
425 self.create_status_bar()
427 g = self.config.get("winpos-qt",[100, 100, 840, 400])
428 self.setGeometry(g[0], g[1], g[2], g[3])
429 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
430 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
431 self.setWindowTitle( title )
433 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
434 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
435 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
436 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
438 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
439 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
440 self.history_list.setFocus(True)
442 self.exchanger = exchange_rate.Exchanger(self)
443 self.toggle_QR_window(self.receive_tab_mode == 2)
444 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
446 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
447 if platform.system() == 'Windows':
448 n = 3 if self.wallet.seed else 2
449 tabs.setCurrentIndex (n)
450 tabs.setCurrentIndex (0)
452 # set initial message
453 self.console.showMessage(self.wallet.banner)
456 QMainWindow.close(self)
458 self.qr_window.close()
459 self.qr_window = None
461 def connect_slots(self, sender):
462 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
463 self.previous_payto_e=''
465 def timer_actions(self):
467 self.qr_window.qrw.update_qr()
469 if self.payto_e.hasFocus():
471 r = unicode( self.payto_e.text() )
472 if r != self.previous_payto_e:
473 self.previous_payto_e = r
475 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
477 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
481 s = r + ' <' + to_address + '>'
482 self.payto_e.setText(s)
485 def update_callback(self):
486 self.emit(QtCore.SIGNAL('updatesignal'))
488 def update_wallet(self):
489 if self.wallet.interface and self.wallet.interface.is_connected:
490 if not self.wallet.up_to_date:
491 text = _("Synchronizing...")
492 icon = QIcon(":icons/status_waiting.png")
494 c, u = self.wallet.get_balance()
495 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
496 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
497 text += self.create_quote_text(Decimal(c+u)/100000000)
498 icon = QIcon(":icons/status_connected.png")
500 text = _("Not connected")
501 icon = QIcon(":icons/status_disconnected.png")
503 self.status_text = text
504 self.statusBar().showMessage(text)
505 self.status_button.setIcon( icon )
507 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
508 self.update_history_tab()
509 self.update_receive_tab()
510 self.update_contacts_tab()
511 self.update_completions()
514 def create_quote_text(self, btc_balance):
515 quote_currency = self.config.get("currency", "None")
516 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
517 if quote_balance is None:
520 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
523 def create_history_tab(self):
524 self.history_list = l = MyTreeWidget(self)
526 for i,width in enumerate(self.column_widths['history']):
527 l.setColumnWidth(i, width)
528 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
529 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
530 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
532 l.setContextMenuPolicy(Qt.CustomContextMenu)
533 l.customContextMenuRequested.connect(self.create_history_menu)
537 def create_history_menu(self, position):
538 self.history_list.selectedIndexes()
539 item = self.history_list.currentItem()
541 tx_hash = str(item.data(0, Qt.UserRole).toString())
542 if not tx_hash: return
544 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
545 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
546 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
547 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
550 def show_tx_details(self, tx):
551 dialog = QDialog(None)
553 dialog.setWindowTitle(_("Transaction Details"))
555 dialog.setLayout(vbox)
556 dialog.setMinimumSize(600,300)
559 if tx_hash in self.wallet.transactions.keys():
560 is_mine, v, fee = self.wallet.get_tx_value(tx)
561 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
563 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
569 vbox.addWidget(QLabel("Transaction ID:"))
570 e = QLineEdit(tx_hash)
574 vbox.addWidget(QLabel("Date: %s"%time_str))
575 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
578 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
579 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
581 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
582 vbox.addWidget(QLabel("Transaction fee: unknown"))
584 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
586 vbox.addWidget( self.generate_transaction_information_widget(tx) )
588 ok_button = QPushButton(_("Close"))
589 ok_button.setDefault(True)
590 ok_button.clicked.connect(dialog.accept)
594 hbox.addWidget(ok_button)
598 def tx_label_clicked(self, item, column):
599 if column==2 and item.isSelected():
601 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
602 self.history_list.editItem( item, column )
603 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
606 def tx_label_changed(self, item, column):
610 tx_hash = str(item.data(0, Qt.UserRole).toString())
611 tx = self.wallet.transactions.get(tx_hash)
612 s = self.wallet.labels.get(tx_hash)
613 text = unicode( item.text(2) )
615 self.wallet.labels[tx_hash] = text
616 item.setForeground(2, QBrush(QColor('black')))
618 if s: self.wallet.labels.pop(tx_hash)
619 text = self.wallet.get_default_label(tx_hash)
620 item.setText(2, text)
621 item.setForeground(2, QBrush(QColor('gray')))
625 def edit_label(self, is_recv):
626 l = self.receive_list if is_recv else self.contacts_list
627 c = 2 if is_recv else 1
628 item = l.currentItem()
629 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
630 l.editItem( item, c )
631 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
633 def edit_amount(self):
634 l = self.receive_list
635 item = l.currentItem()
636 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
637 l.editItem( item, 3 )
638 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
641 def address_label_clicked(self, item, column, l, column_addr, column_label):
642 if column == column_label and item.isSelected():
643 addr = unicode( item.text(column_addr) )
644 label = unicode( item.text(column_label) )
645 if label in self.wallet.aliases.keys():
647 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
648 l.editItem( item, column )
649 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
652 def address_label_changed(self, item, column, l, column_addr, column_label):
654 if column == column_label:
655 addr = unicode( item.text(column_addr) )
656 text = unicode( item.text(column_label) )
660 if text not in self.wallet.aliases.keys():
661 old_addr = self.wallet.labels.get(text)
663 self.wallet.labels[addr] = text
666 print_error("Error: This is one of your aliases")
667 label = self.wallet.labels.get(addr,'')
668 item.setText(column_label, QString(label))
670 s = self.wallet.labels.get(addr)
672 self.wallet.labels.pop(addr)
676 self.update_history_tab()
677 self.update_completions()
679 self.recv_changed(item)
682 address = str( item.text(column_addr) )
683 text = str( item.text(3) )
685 index = self.wallet.addresses.index(address)
689 text = text.strip().upper()
690 m = re.match('^(\d+(|\.\d*))\s*(|BTC|EUR|USD|GBP|CNY|JPY|RUB|BRL)$', text)
693 currency = m.group(3)
697 currency = currency.upper()
698 self.wallet.requested_amounts[address] = (amount, currency)
700 label = self.wallet.labels.get(address)
702 label = self.merchant_name + ' - %04d'%(index+1)
703 self.wallet.labels[address] = label
706 self.qr_window.set_content( address, label, amount, currency )
710 if address in self.wallet.requested_amounts:
711 self.wallet.requested_amounts.pop(address)
713 self.update_receive_item(self.receive_list.currentItem())
716 def recv_changed(self, a):
717 "current item changed"
718 if a is not None and self.qr_window and self.qr_window.isVisible():
719 address = str(a.text(1))
720 label = self.wallet.labels.get(address)
722 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
724 amount, currency = None, None
725 self.qr_window.set_content( address, label, amount, currency )
728 def update_history_tab(self):
730 self.history_list.clear()
731 for item in self.wallet.get_tx_history():
732 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
735 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
741 icon = QIcon(":icons/unconfirmed.png")
743 icon = QIcon(":icons/clock%d.png"%conf)
745 icon = QIcon(":icons/confirmed.png")
748 icon = QIcon(":icons/unconfirmed.png")
750 if value is not None:
751 v_str = format_satoshis(value, True, self.wallet.num_zeros)
755 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
758 label, is_default_label = self.wallet.get_label(tx_hash)
760 label = _('Pruned transaction outputs')
761 is_default_label = False
763 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
764 item.setFont(2, QFont(MONOSPACE_FONT))
765 item.setFont(3, QFont(MONOSPACE_FONT))
766 item.setFont(4, QFont(MONOSPACE_FONT))
768 item.setForeground(3, QBrush(QColor("#BC1E1E")))
770 item.setData(0, Qt.UserRole, tx_hash)
771 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
773 item.setForeground(2, QBrush(QColor('grey')))
775 item.setIcon(0, icon)
776 self.history_list.insertTopLevelItem(0,item)
779 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
782 def create_send_tab(self):
787 grid.setColumnMinimumWidth(3,300)
788 grid.setColumnStretch(5,1)
790 self.payto_e = QLineEdit()
791 grid.addWidget(QLabel(_('Pay to')), 1, 0)
792 grid.addWidget(self.payto_e, 1, 1, 1, 3)
795 qrcode = qrscanner.scan_qr()
796 if 'address' in qrcode:
797 self.payto_e.setText(qrcode['address'])
798 if 'amount' in qrcode:
799 self.amount_e.setText(str(qrcode['amount']))
800 if 'label' in qrcode:
801 self.message_e.setText(qrcode['label'])
802 if 'message' in qrcode:
803 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
806 if qrscanner.is_available():
807 b = QPushButton(_("Scan QR code"))
808 b.clicked.connect(fill_from_qr)
809 grid.addWidget(b, 1, 5)
811 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)
813 completer = QCompleter()
814 completer.setCaseSensitivity(False)
815 self.payto_e.setCompleter(completer)
816 completer.setModel(self.completions)
818 self.message_e = QLineEdit()
819 grid.addWidget(QLabel(_('Description')), 2, 0)
820 grid.addWidget(self.message_e, 2, 1, 1, 3)
821 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)
823 self.amount_e = QLineEdit()
824 grid.addWidget(QLabel(_('Amount')), 3, 0)
825 grid.addWidget(self.amount_e, 3, 1, 1, 2)
826 grid.addWidget(HelpButton(
827 _('Amount to be sent.') + '\n\n' \
828 + _('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)
830 self.fee_e = QLineEdit()
831 grid.addWidget(QLabel(_('Fee')), 4, 0)
832 grid.addWidget(self.fee_e, 4, 1, 1, 2)
833 grid.addWidget(HelpButton(
834 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
835 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
836 + _('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)
839 b = EnterButton(_("Send"), self.do_send)
841 b = EnterButton(_("Create unsigned transaction"), self.do_send)
842 grid.addWidget(b, 6, 1)
844 b = EnterButton(_("Clear"),self.do_clear)
845 grid.addWidget(b, 6, 2)
847 self.payto_sig = QLabel('')
848 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
850 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
851 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
860 def entry_changed( is_fee ):
861 self.funds_error = False
862 amount = numbify(self.amount_e)
863 fee = numbify(self.fee_e)
864 if not is_fee: fee = None
867 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
869 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
872 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
873 text = self.status_text
876 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
877 self.funds_error = True
878 text = _( "Not enough funds" )
880 self.statusBar().showMessage(text)
881 self.amount_e.setPalette(palette)
882 self.fee_e.setPalette(palette)
884 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
885 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
890 def update_completions(self):
892 for addr,label in self.wallet.labels.items():
893 if addr in self.wallet.addressbook:
894 l.append( label + ' <' + addr + '>')
895 l = l + self.wallet.aliases.keys()
897 self.completions.setStringList(l)
903 label = unicode( self.message_e.text() )
904 r = unicode( self.payto_e.text() )
908 m1 = re.match(ALIAS_REGEXP, r)
909 # label or alias, with address in brackets
910 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
913 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
917 to_address = m2.group(2)
921 if not is_valid(to_address):
922 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
926 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
928 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
931 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
933 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
936 if self.wallet.use_encryption:
937 password = self.password_dialog()
944 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
945 except BaseException, e:
946 self.show_message(str(e))
950 self.wallet.labels[tx.hash()] = label
953 h = self.wallet.send_tx(tx)
954 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
955 status, msg = self.wallet.receive_tx( h )
957 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
959 self.update_contacts_tab()
961 QMessageBox.warning(self, _('Error'), msg, _('OK'))
963 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
965 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
966 with open(fileName,'w') as f:
967 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
968 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
970 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
975 def set_url(self, url):
976 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
977 self.tabs.setCurrentIndex(1)
978 label = self.wallet.labels.get(payto)
979 m_addr = label + ' <'+ payto+'>' if label else payto
980 self.payto_e.setText(m_addr)
982 self.message_e.setText(message)
983 self.amount_e.setText(amount)
985 self.set_frozen(self.payto_e,True)
986 self.set_frozen(self.amount_e,True)
987 self.set_frozen(self.message_e,True)
988 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
990 self.payto_sig.setVisible(False)
993 self.payto_sig.setVisible(False)
994 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
996 self.set_frozen(e,False)
998 def set_frozen(self,entry,frozen):
1000 entry.setReadOnly(True)
1001 entry.setFrame(False)
1002 palette = QPalette()
1003 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1004 entry.setPalette(palette)
1006 entry.setReadOnly(False)
1007 entry.setFrame(True)
1008 palette = QPalette()
1009 palette.setColor(entry.backgroundRole(), QColor('white'))
1010 entry.setPalette(palette)
1013 def toggle_freeze(self,addr):
1015 if addr in self.wallet.frozen_addresses:
1016 self.wallet.unfreeze(addr)
1018 self.wallet.freeze(addr)
1019 self.update_receive_tab()
1021 def toggle_priority(self,addr):
1023 if addr in self.wallet.prioritized_addresses:
1024 self.wallet.unprioritize(addr)
1026 self.wallet.prioritize(addr)
1027 self.update_receive_tab()
1030 def create_list_tab(self, headers):
1031 "generic tab creation method"
1032 l = MyTreeWidget(self)
1033 l.setColumnCount( len(headers) )
1034 l.setHeaderLabels( headers )
1037 vbox = QVBoxLayout()
1044 vbox.addWidget(buttons)
1046 hbox = QHBoxLayout()
1049 buttons.setLayout(hbox)
1054 def create_receive_tab(self):
1055 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
1056 l.setContextMenuPolicy(Qt.CustomContextMenu)
1057 l.customContextMenuRequested.connect(self.create_receive_menu)
1058 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1059 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1060 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
1061 self.receive_list = l
1062 self.receive_buttons_hbox = hbox
1067 def receive_tab_set_mode(self, i):
1068 self.save_column_widths()
1069 self.receive_tab_mode = i
1070 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
1072 self.update_receive_tab()
1073 self.toggle_QR_window(self.receive_tab_mode == 2)
1076 def save_column_widths(self):
1077 if self.receive_tab_mode == 0:
1078 widths = [ self.receive_list.columnWidth(0) ]
1081 for i in range(self.receive_list.columnCount() -1):
1082 widths.append(self.receive_list.columnWidth(i))
1083 self.column_widths["receive"][self.receive_tab_mode] = widths
1085 self.column_widths["history"] = []
1086 for i in range(self.history_list.columnCount() - 1):
1087 self.column_widths["history"].append(self.history_list.columnWidth(i))
1089 self.column_widths["contacts"] = []
1090 for i in range(self.contacts_list.columnCount() - 1):
1091 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1094 def create_contacts_tab(self):
1095 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1096 l.setContextMenuPolicy(Qt.CustomContextMenu)
1097 l.customContextMenuRequested.connect(self.create_contact_menu)
1098 for i,width in enumerate(self.column_widths['contacts']):
1099 l.setColumnWidth(i, width)
1101 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1102 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1103 self.contacts_list = l
1104 self.contacts_buttons_hbox = hbox
1105 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1110 def delete_imported_key(self, addr):
1111 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1112 self.wallet.imported_keys.pop(addr)
1113 self.update_receive_tab()
1114 self.update_history_tab()
1118 def create_receive_menu(self, position):
1119 # fixme: this function apparently has a side effect.
1120 # if it is not called the menu pops up several times
1121 #self.receive_list.selectedIndexes()
1123 item = self.receive_list.itemAt(position)
1125 addr = unicode(item.text(0))
1127 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1128 if self.receive_tab_mode == 2:
1129 menu.addAction(_("Request amount"), lambda: self.edit_amount())
1130 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode(_("Address"),"bitcoin:"+addr) )
1131 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1132 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
1133 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1134 if addr in self.wallet.imported_keys:
1135 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1137 if self.receive_tab_mode == 1:
1138 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1139 menu.addAction(t, lambda: self.toggle_freeze(addr))
1140 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1141 menu.addAction(t, lambda: self.toggle_priority(addr))
1143 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1146 def payto(self, x, is_alias):
1153 label = self.wallet.labels.get(addr)
1154 m_addr = label + ' <' + addr + '>' if label else addr
1155 self.tabs.setCurrentIndex(1)
1156 self.payto_e.setText(m_addr)
1157 self.amount_e.setFocus()
1159 def delete_contact(self, x, is_alias):
1160 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1161 if not is_alias and x in self.wallet.addressbook:
1162 self.wallet.addressbook.remove(x)
1163 if x in self.wallet.labels.keys():
1164 self.wallet.labels.pop(x)
1165 elif is_alias and x in self.wallet.aliases:
1166 self.wallet.aliases.pop(x)
1167 self.update_history_tab()
1168 self.update_contacts_tab()
1169 self.update_completions()
1171 def create_contact_menu(self, position):
1172 # fixme: this function apparently has a side effect.
1173 # if it is not called the menu pops up several times
1174 #self.contacts_list.selectedIndexes()
1176 item = self.contacts_list.itemAt(position)
1178 addr = unicode(item.text(0))
1179 label = unicode(item.text(1))
1180 is_alias = label in self.wallet.aliases.keys()
1181 x = label if is_alias else addr
1183 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1184 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1185 menu.addAction(_("View QR code"),lambda: self.show_qrcode(_("Address"),"bitcoin:"+addr))
1187 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1189 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1190 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1191 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1194 def update_receive_item(self, item):
1195 address = str(item.data(0,0).toString())
1196 label = self.wallet.labels.get(address,'')
1197 item.setData(1,0,label)
1200 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1202 amount, currency = None, None
1204 amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
1205 item.setData(2,0,amount_str)
1207 c, u = self.wallet.get_addr_balance(address)
1208 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1209 item.setData(3,0,balance)
1211 if self.receive_tab_mode == 1:
1212 if address in self.wallet.frozen_addresses:
1213 item.setBackgroundColor(0, QColor('lightblue'))
1214 elif address in self.wallet.prioritized_addresses:
1215 item.setBackgroundColor(0, QColor('lightgreen'))
1218 def update_receive_tab(self):
1219 l = self.receive_list
1222 l.setColumnHidden(2, not self.receive_tab_mode == 2)
1223 l.setColumnHidden(3, self.receive_tab_mode == 0)
1224 l.setColumnHidden(4, not self.receive_tab_mode == 1)
1225 if self.receive_tab_mode == 0:
1226 width = self.column_widths['receive'][0][0]
1227 l.setColumnWidth(0, width)
1229 for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
1230 l.setColumnWidth(i, width)
1233 for k, account in self.wallet.accounts.items():
1234 name = account.get('name',str(k))
1235 account_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1236 l.addTopLevelItem(account_item)
1237 account_item.setExpanded(True)
1239 for is_change in [0,1]:
1240 name = "Receiving" if not is_change else "Change"
1241 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1242 account_item.addChild(seq_item)
1243 if not is_change: seq_item.setExpanded(True)
1247 for address in account[is_change]:
1248 h = self.wallet.history.get(address,[])
1253 if gap > self.wallet.gap_limit:
1258 num_tx = '*' if h == ['*'] else "%d"%len(h)
1259 item = QTreeWidgetItem( [ address, '', '', '', num_tx] )
1260 item.setFont(0, QFont(MONOSPACE_FONT))
1261 item.setFont(2, QFont(MONOSPACE_FONT))
1262 self.update_receive_item(item)
1263 if is_red and not is_change:
1264 item.setBackgroundColor(1, QColor('red'))
1265 seq_item.addChild(item)
1267 if self.wallet.imported_keys:
1268 account_item = QTreeWidgetItem( [ "Imported", '', '', '', ''] )
1269 l.addTopLevelItem(account_item)
1270 account_item.setExpanded(True)
1271 for address in self.wallet.imported_keys.keys():
1272 item = QTreeWidgetItem( [ address, '', '', '', ''] )
1273 item.setFont(0, QFont(MONOSPACE_FONT))
1274 item.setFont(2, QFont(MONOSPACE_FONT))
1275 self.update_receive_item(item)
1276 if is_red and not is_change:
1277 item.setBackgroundColor(1, QColor('red'))
1278 account_item.addChild(item)
1281 # we use column 1 because column 0 may be hidden
1282 l.setCurrentItem(l.topLevelItem(0),1)
1284 def show_contact_details(self, m):
1285 a = self.wallet.aliases.get(m)
1287 if a[0] in self.wallet.authorities.keys():
1288 s = self.wallet.authorities.get(a[0])
1291 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1292 QMessageBox.information(self, 'Alias', msg, 'OK')
1294 def update_contacts_tab(self):
1296 l = self.contacts_list
1300 for alias, v in self.wallet.aliases.items():
1302 alias_targets.append(target)
1303 item = QTreeWidgetItem( [ target, alias, '-'] )
1304 item.setBackgroundColor(0, QColor('lightgray'))
1305 l.addTopLevelItem(item)
1307 for address in self.wallet.addressbook:
1308 if address in alias_targets: continue
1309 label = self.wallet.labels.get(address,'')
1311 for tx in self.wallet.transactions.values():
1312 if address in map(lambda x: x[0], tx.outputs): n += 1
1314 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1315 item.setFont(0, QFont(MONOSPACE_FONT))
1316 l.addTopLevelItem(item)
1318 l.setCurrentItem(l.topLevelItem(0))
1321 def create_console_tab(self):
1322 from qt_console import Console
1323 from electrum import util, bitcoin, commands
1324 self.console = console = Console()
1325 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1326 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1328 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1330 def mkfunc(f, method):
1331 return lambda *args: apply( f, (method, args, self.password_dialog ))
1333 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1334 methods[m] = mkfunc(c._run, m)
1336 console.updateNamespace(methods)
1340 def create_status_bar(self):
1341 self.status_text = ""
1343 sb.setFixedHeight(35)
1344 qtVersion = qVersion()
1346 update_notification = UpdateLabel(self.config)
1347 if(update_notification.new_version):
1348 sb.addPermanentWidget(update_notification)
1350 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1351 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1352 if self.wallet.seed:
1353 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1354 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1355 if self.wallet.seed:
1356 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1357 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1358 sb.addPermanentWidget( self.status_button )
1360 self.setStatusBar(sb)
1364 self.config.set_key('gui', 'lite', True)
1367 self.lite.mini.show()
1369 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1370 self.lite.main(None)
1372 def new_contact_dialog(self):
1373 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1374 address = unicode(text)
1376 if is_valid(address):
1377 self.wallet.addressbook.append(address)
1379 self.update_contacts_tab()
1380 self.update_history_tab()
1381 self.update_completions()
1383 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1385 def show_master_public_key(self):
1386 dialog = QDialog(None)
1388 dialog.setWindowTitle(_("Master Public Key"))
1390 main_text = QTextEdit()
1391 main_text.setText(self.wallet.get_master_public_key())
1392 main_text.setReadOnly(True)
1393 main_text.setMaximumHeight(170)
1394 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1396 ok_button = QPushButton(_("OK"))
1397 ok_button.setDefault(True)
1398 ok_button.clicked.connect(dialog.accept)
1400 main_layout = QGridLayout()
1401 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1403 main_layout.addWidget(main_text, 1, 0)
1404 main_layout.addWidget(qrw, 1, 1 )
1406 vbox = QVBoxLayout()
1407 vbox.addLayout(main_layout)
1408 hbox = QHBoxLayout()
1410 hbox.addWidget(ok_button)
1411 vbox.addLayout(hbox)
1413 dialog.setLayout(vbox)
1418 def show_seed_dialog(self, wallet, parent=None):
1420 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1423 if wallet.use_encryption:
1424 password = parent.password_dialog()
1431 seed = wallet.decode_seed(password)
1433 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1436 self.show_seed(seed)
1439 def show_seed(self, seed):
1440 dialog = QDialog(None)
1442 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1444 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1446 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1448 seed_text = QTextEdit(brainwallet)
1449 seed_text.setReadOnly(True)
1450 seed_text.setMaximumHeight(130)
1452 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1453 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1454 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1455 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1456 label2 = QLabel(msg2)
1457 label2.setWordWrap(True)
1460 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1461 logo.setMaximumWidth(60)
1463 qrw = QRCodeWidget(seed, 4)
1465 ok_button = QPushButton(_("OK"))
1466 ok_button.setDefault(True)
1467 ok_button.clicked.connect(dialog.accept)
1469 grid = QGridLayout()
1470 #main_layout.addWidget(logo, 0, 0)
1472 grid.addWidget(logo, 0, 0)
1473 grid.addWidget(label1, 0, 1)
1475 grid.addWidget(seed_text, 1, 0, 1, 2)
1477 grid.addWidget(qrw, 0, 2, 2, 1)
1479 vbox = QVBoxLayout()
1480 vbox.addLayout(grid)
1481 vbox.addWidget(label2)
1483 hbox = QHBoxLayout()
1485 hbox.addWidget(ok_button)
1486 vbox.addLayout(hbox)
1488 dialog.setLayout(vbox)
1492 def show_qrcode(title, data):
1496 d.setWindowTitle(title)
1497 d.setMinimumSize(270, 300)
1498 vbox = QVBoxLayout()
1499 qrw = QRCodeWidget(data)
1500 vbox.addWidget(qrw, 1)
1501 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1502 hbox = QHBoxLayout()
1506 filename = "qrcode.bmp"
1507 bmp.save_qrcode(qrw.qr, filename)
1508 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1510 b = QPushButton(_("Print"))
1512 b.clicked.connect(print_qr)
1514 b = QPushButton(_("Close"))
1516 b.clicked.connect(d.accept)
1518 vbox.addLayout(hbox)
1522 def view_private_key(self,address):
1523 if not address: return
1524 if self.wallet.use_encryption:
1525 password = self.password_dialog()
1532 pk = self.wallet.get_private_key(address, password)
1533 except BaseException, e:
1534 self.show_message(str(e))
1537 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1540 def sign_message(self,address):
1541 if not address: return
1544 d.setWindowTitle(_('Sign Message'))
1545 d.setMinimumSize(410, 290)
1547 tab_widget = QTabWidget()
1549 layout = QGridLayout(tab)
1551 sign_address = QLineEdit()
1553 sign_address.setText(address)
1554 layout.addWidget(QLabel(_('Address')), 1, 0)
1555 layout.addWidget(sign_address, 1, 1)
1557 sign_message = QTextEdit()
1558 layout.addWidget(QLabel(_('Message')), 2, 0)
1559 layout.addWidget(sign_message, 2, 1)
1560 layout.setRowStretch(2,3)
1562 sign_signature = QTextEdit()
1563 layout.addWidget(QLabel(_('Signature')), 3, 0)
1564 layout.addWidget(sign_signature, 3, 1)
1565 layout.setRowStretch(3,1)
1568 if self.wallet.use_encryption:
1569 password = self.password_dialog()
1576 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1577 sign_signature.setText(signature)
1578 except BaseException, e:
1579 self.show_message(str(e))
1582 hbox = QHBoxLayout()
1583 b = QPushButton(_("Sign"))
1585 b.clicked.connect(do_sign)
1586 b = QPushButton(_("Close"))
1587 b.clicked.connect(d.accept)
1589 layout.addLayout(hbox, 4, 1)
1590 tab_widget.addTab(tab, _("Sign"))
1594 layout = QGridLayout(tab)
1596 verify_address = QLineEdit()
1597 layout.addWidget(QLabel(_('Address')), 1, 0)
1598 layout.addWidget(verify_address, 1, 1)
1600 verify_message = QTextEdit()
1601 layout.addWidget(QLabel(_('Message')), 2, 0)
1602 layout.addWidget(verify_message, 2, 1)
1603 layout.setRowStretch(2,3)
1605 verify_signature = QTextEdit()
1606 layout.addWidget(QLabel(_('Signature')), 3, 0)
1607 layout.addWidget(verify_signature, 3, 1)
1608 layout.setRowStretch(3,1)
1612 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1613 self.show_message(_("Signature verified"))
1614 except BaseException, e:
1615 self.show_message(str(e))
1618 hbox = QHBoxLayout()
1619 b = QPushButton(_("Verify"))
1620 b.clicked.connect(do_verify)
1622 b = QPushButton(_("Close"))
1623 b.clicked.connect(d.accept)
1625 layout.addLayout(hbox, 4, 1)
1626 tab_widget.addTab(tab, _("Verify"))
1628 vbox = QVBoxLayout()
1629 vbox.addWidget(tab_widget)
1634 def toggle_QR_window(self, show):
1635 if show and not self.qr_window:
1636 self.qr_window = QR_Window(self.exchanger)
1637 self.qr_window.setVisible(True)
1638 self.qr_window_geometry = self.qr_window.geometry()
1639 item = self.receive_list.currentItem()
1641 address = str(item.text(1))
1642 label = self.wallet.labels.get(address)
1643 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1644 self.qr_window.set_content( address, label, amount, currency )
1646 elif show and self.qr_window and not self.qr_window.isVisible():
1647 self.qr_window.setVisible(True)
1648 self.qr_window.setGeometry(self.qr_window_geometry)
1650 elif not show and self.qr_window and self.qr_window.isVisible():
1651 self.qr_window_geometry = self.qr_window.geometry()
1652 self.qr_window.setVisible(False)
1654 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1655 self.receive_list.setColumnHidden(2, self.qr_window is None or not self.qr_window.isVisible())
1656 self.receive_list.setColumnWidth(1, 200)
1659 def question(self, msg):
1660 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1662 def show_message(self, msg):
1663 QMessageBox.information(self, _('Message'), msg, _('OK'))
1665 def password_dialog(self ):
1672 vbox = QVBoxLayout()
1673 msg = _('Please enter your password')
1674 vbox.addWidget(QLabel(msg))
1676 grid = QGridLayout()
1678 grid.addWidget(QLabel(_('Password')), 1, 0)
1679 grid.addWidget(pw, 1, 1)
1680 vbox.addLayout(grid)
1682 vbox.addLayout(ok_cancel_buttons(d))
1685 if not d.exec_(): return
1686 return unicode(pw.text())
1693 def change_password_dialog( wallet, parent=None ):
1696 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1704 new_pw = QLineEdit()
1705 new_pw.setEchoMode(2)
1706 conf_pw = QLineEdit()
1707 conf_pw.setEchoMode(2)
1709 vbox = QVBoxLayout()
1711 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1712 +_('To disable wallet encryption, enter an empty new password.')) \
1713 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1715 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1716 +_("Leave these fields empty if you want to disable encryption.")
1717 vbox.addWidget(QLabel(msg))
1719 grid = QGridLayout()
1722 if wallet.use_encryption:
1723 grid.addWidget(QLabel(_('Password')), 1, 0)
1724 grid.addWidget(pw, 1, 1)
1726 grid.addWidget(QLabel(_('New Password')), 2, 0)
1727 grid.addWidget(new_pw, 2, 1)
1729 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1730 grid.addWidget(conf_pw, 3, 1)
1731 vbox.addLayout(grid)
1733 vbox.addLayout(ok_cancel_buttons(d))
1736 if not d.exec_(): return
1738 password = unicode(pw.text()) if wallet.use_encryption else None
1739 new_password = unicode(new_pw.text())
1740 new_password2 = unicode(conf_pw.text())
1743 seed = wallet.decode_seed(password)
1745 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1748 if new_password != new_password2:
1749 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1750 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1752 wallet.update_password(seed, password, new_password)
1755 def seed_dialog(wallet, parent=None):
1759 vbox = QVBoxLayout()
1760 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1761 vbox.addWidget(QLabel(msg))
1763 grid = QGridLayout()
1766 seed_e = QLineEdit()
1767 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1768 grid.addWidget(seed_e, 1, 1)
1772 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1773 grid.addWidget(gap_e, 2, 1)
1774 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1775 vbox.addLayout(grid)
1777 vbox.addLayout(ok_cancel_buttons(d))
1780 if not d.exec_(): return
1783 gap = int(unicode(gap_e.text()))
1785 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1789 seed = str(seed_e.text())
1792 print_error("Warning: Not hex, trying decode")
1794 seed = mnemonic.mn_decode( seed.split(' ') )
1796 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1800 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1805 def generate_transaction_information_widget(self, tx):
1806 tabs = QTabWidget(self)
1809 grid_ui = QGridLayout(tab1)
1810 grid_ui.setColumnStretch(0,1)
1811 tabs.addTab(tab1, _('Outputs') )
1813 tree_widget = MyTreeWidget(self)
1814 tree_widget.setColumnCount(2)
1815 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1816 tree_widget.setColumnWidth(0, 300)
1817 tree_widget.setColumnWidth(1, 50)
1819 for output in tx.d["outputs"]:
1820 item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
1821 tree_widget.addTopLevelItem(item)
1823 tree_widget.setMaximumHeight(100)
1825 grid_ui.addWidget(tree_widget)
1828 grid_ui = QGridLayout(tab2)
1829 grid_ui.setColumnStretch(0,1)
1830 tabs.addTab(tab2, _('Inputs') )
1832 tree_widget = MyTreeWidget(self)
1833 tree_widget.setColumnCount(2)
1834 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1836 for input_line in tx.inputs:
1837 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1838 tree_widget.addTopLevelItem(item)
1840 tree_widget.setMaximumHeight(100)
1842 grid_ui.addWidget(tree_widget)
1846 def tx_dict_from_text(self, txt):
1848 tx_dict = json.loads(str(txt))
1849 assert "hex" in tx_dict.keys()
1850 assert "complete" in tx_dict.keys()
1851 if not tx_dict["complete"]:
1852 assert "input_info" in tx_dict.keys()
1854 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1859 def read_tx_from_file(self):
1860 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1864 with open(fileName, "r") as f:
1865 file_content = f.read()
1866 except (ValueError, IOError, os.error), reason:
1867 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1869 return self.tx_dict_from_text(file_content)
1872 def sign_raw_transaction(self, tx, input_info):
1873 if self.wallet.use_encryption:
1874 password = self.password_dialog()
1881 self.wallet.signrawtransaction(tx, input_info, [], password)
1883 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1885 with open(fileName, "w+") as f:
1886 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1887 self.show_message(_("Transaction saved succesfully"))
1888 except BaseException, e:
1889 self.show_message(str(e))
1892 def create_sign_transaction_window(self, tx_dict):
1893 tx = Transaction(tx_dict["hex"])
1895 dialog = QDialog(self)
1896 dialog.setMinimumWidth(500)
1897 dialog.setWindowTitle(_('Sign unsigned transaction'))
1900 vbox = QVBoxLayout()
1901 dialog.setLayout(vbox)
1902 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1904 if tx_dict["complete"] == True:
1905 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1907 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1908 vbox.addLayout(ok_cancel_buttons(dialog))
1909 input_info = json.loads(tx_dict["input_info"])
1912 self.sign_raw_transaction(tx, input_info)
1916 def do_sign_from_text(self):
1917 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1920 tx_dict = self.tx_dict_from_text(unicode(txt))
1922 self.create_sign_transaction_window(tx_dict)
1925 def do_sign_from_file(self):
1926 tx_dict = self.read_tx_from_file()
1928 self.create_sign_transaction_window(tx_dict)
1931 def send_raw_transaction(self, raw_tx):
1932 result, result_message = self.wallet.sendtx( raw_tx )
1934 self.show_message("Transaction succesfully sent: %s" % (result_message))
1936 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1939 def create_send_transaction_window(self, tx_dict):
1940 tx = Transaction(tx_dict["hex"])
1942 dialog = QDialog(self)
1943 dialog.setMinimumWidth(500)
1944 dialog.setWindowTitle(_('Send raw transaction'))
1947 vbox = QVBoxLayout()
1948 dialog.setLayout(vbox)
1949 vbox.addWidget( self.generate_transaction_information_widget(tx))
1951 if tx_dict["complete"] == False:
1952 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1954 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1955 vbox.addLayout(ok_cancel_buttons(dialog))
1958 self.send_raw_transaction(tx_dict["hex"])
1961 def do_send_from_file(self):
1962 tx_dict = self.read_tx_from_file()
1964 self.create_send_transaction_window(tx_dict)
1967 def do_send_from_text(self):
1968 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1971 tx_dict = self.tx_dict_from_text(unicode(txt))
1973 self.create_send_transaction_window(tx_dict)
1976 def do_export_privkeys(self):
1977 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.")))
1979 if self.wallet.use_encryption:
1980 password = self.password_dialog()
1986 select_export = _('Select file to export your private keys to')
1987 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1989 with open(fileName, "w+") as csvfile:
1990 transaction = csv.writer(csvfile)
1991 transaction.writerow(["address", "private_key"])
1994 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1995 transaction.writerow(["%34s"%addr,pk])
1997 self.show_message(_("Private keys exported."))
1999 except (IOError, os.error), reason:
2000 export_error_label = _("Electrum was unable to produce a private key-export.")
2001 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
2003 except BaseException, e:
2004 self.show_message(str(e))
2008 def do_import_labels(self):
2009 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
2010 if not labelsFile: return
2012 f = open(labelsFile, 'r')
2015 for key, value in json.loads(data).items():
2016 self.wallet.labels[key] = value
2018 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
2019 except (IOError, os.error), reason:
2020 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2024 def do_export_labels(self):
2025 labels = self.wallet.labels
2027 labelsFile = util.user_dir() + '/labels.dat'
2028 f = open(labelsFile, 'w+')
2029 json.dump(labels, f)
2031 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
2032 except (IOError, os.error), reason:
2033 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
2035 def do_export_history(self):
2036 from gui_lite import csv_transaction
2037 csv_transaction(self.wallet)
2039 def do_import_privkey(self):
2040 if not self.wallet.imported_keys:
2041 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
2042 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
2043 + _('Are you sure you understand what you are doing?'), 3, 4)
2046 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
2048 sec = str(text).strip()
2049 if self.wallet.use_encryption:
2050 password = self.password_dialog()
2056 addr = self.wallet.import_key(sec, password)
2058 QMessageBox.critical(None, _("Unable to import key"), "error")
2060 QMessageBox.information(None, _("Key imported"), addr)
2061 self.update_receive_tab()
2062 self.update_history_tab()
2063 except BaseException as e:
2064 QMessageBox.critical(None, _("Unable to import key"), str(e))
2066 def settings_dialog(self):
2068 d.setWindowTitle(_('Electrum Settings'))
2070 vbox = QVBoxLayout()
2072 tabs = QTabWidget(self)
2073 vbox.addWidget(tabs)
2076 grid_ui = QGridLayout(tab1)
2077 grid_ui.setColumnStretch(0,1)
2078 tabs.addTab(tab1, _('Display') )
2080 nz_label = QLabel(_('Display zeros'))
2081 grid_ui.addWidget(nz_label, 3, 0)
2083 nz_e.setText("%d"% self.wallet.num_zeros)
2084 grid_ui.addWidget(nz_e, 3, 1)
2085 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2086 grid_ui.addWidget(HelpButton(msg), 3, 2)
2087 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
2088 if not self.config.is_modifiable('num_zeros'):
2089 for w in [nz_e, nz_label]: w.setEnabled(False)
2091 lang_label=QLabel(_('Language') + ':')
2092 grid_ui.addWidget(lang_label , 8, 0)
2093 lang_combo = QComboBox()
2094 from i18n import languages
2095 lang_combo.addItems(languages.values())
2097 index = languages.keys().index(self.config.get("language",''))
2100 lang_combo.setCurrentIndex(index)
2101 grid_ui.addWidget(lang_combo, 8, 1)
2102 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
2103 if not self.config.is_modifiable('language'):
2104 for w in [lang_combo, lang_label]: w.setEnabled(False)
2106 currencies = self.exchanger.get_currencies()
2107 currencies.insert(0, "None")
2109 cur_label=QLabel(_('Currency') + ':')
2110 grid_ui.addWidget(cur_label , 9, 0)
2111 cur_combo = QComboBox()
2112 cur_combo.addItems(currencies)
2114 index = currencies.index(self.config.get('currency', "None"))
2117 cur_combo.setCurrentIndex(index)
2118 grid_ui.addWidget(cur_combo, 9, 1)
2119 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
2121 view_label=QLabel(_('Receive Tab') + ':')
2122 grid_ui.addWidget(view_label , 10, 0)
2123 view_combo = QComboBox()
2124 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
2125 view_combo.setCurrentIndex(self.receive_tab_mode)
2126 grid_ui.addWidget(view_combo, 10, 1)
2127 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
2128 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
2129 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
2130 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
2132 grid_ui.addWidget(HelpButton(hh), 10, 2)
2136 grid_wallet = QGridLayout(tab2)
2137 grid_wallet.setColumnStretch(0,1)
2138 tabs.addTab(tab2, _('Wallet') )
2140 fee_label = QLabel(_('Transaction fee'))
2141 grid_wallet.addWidget(fee_label, 0, 0)
2143 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
2144 grid_wallet.addWidget(fee_e, 0, 1)
2145 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
2146 + _('Recommended value') + ': 0.001'
2147 grid_wallet.addWidget(HelpButton(msg), 0, 2)
2148 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
2149 if not self.config.is_modifiable('fee'):
2150 for w in [fee_e, fee_label]: w.setEnabled(False)
2152 usechange_label = QLabel(_('Use change addresses'))
2153 grid_wallet.addWidget(usechange_label, 1, 0)
2154 usechange_combo = QComboBox()
2155 usechange_combo.addItems([_('Yes'), _('No')])
2156 usechange_combo.setCurrentIndex(not self.wallet.use_change)
2157 grid_wallet.addWidget(usechange_combo, 1, 1)
2158 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
2159 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
2161 gap_label = QLabel(_('Gap limit'))
2162 grid_wallet.addWidget(gap_label, 2, 0)
2164 gap_e.setText("%d"% self.wallet.gap_limit)
2165 grid_wallet.addWidget(gap_e, 2, 1)
2166 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2167 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2168 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2169 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2170 + _('Warning') + ': ' \
2171 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2172 + _('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'
2173 grid_wallet.addWidget(HelpButton(msg), 2, 2)
2174 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
2175 if not self.config.is_modifiable('gap_limit'):
2176 for w in [gap_e, gap_label]: w.setEnabled(False)
2178 grid_wallet.setRowStretch(3,1)
2183 grid_io = QGridLayout(tab3)
2184 grid_io.setColumnStretch(0,1)
2185 tabs.addTab(tab3, _('Import/Export') )
2187 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2188 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2189 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2190 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2192 grid_io.addWidget(QLabel(_('History')), 2, 0)
2193 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2194 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2196 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2198 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2199 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2200 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2202 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2203 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2204 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2205 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2206 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2208 grid_io.setRowStretch(4,1)
2211 grid_raw = QGridLayout(tab4)
2212 grid_raw.setColumnStretch(0,1)
2213 tabs.addTab(tab4, _('Raw transactions') )
2215 #grid_raw.addWidget(QLabel(_("Read raw transaction")), 3, 0)
2216 #grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),3,1)
2217 #grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),3,2)
2218 #grid_raw.addWidget(HelpButton(_("This will show you some useful information about an unsigned transaction")),3,3)
2220 if self.wallet.seed:
2221 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2222 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2223 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2224 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2226 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2227 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2228 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2229 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2230 grid_raw.setRowStretch(3,1)
2232 vbox.addLayout(ok_cancel_buttons(d))
2236 if not d.exec_(): return
2238 fee = unicode(fee_e.text())
2240 fee = int( 100000000 * Decimal(fee) )
2242 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2245 if self.wallet.fee != fee:
2246 self.wallet.fee = fee
2249 nz = unicode(nz_e.text())
2254 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2257 if self.wallet.num_zeros != nz:
2258 self.wallet.num_zeros = nz
2259 self.config.set_key('num_zeros', nz, True)
2260 self.update_history_tab()
2261 self.update_receive_tab()
2263 usechange_result = usechange_combo.currentIndex() == 0
2264 if self.wallet.use_change != usechange_result:
2265 self.wallet.use_change = usechange_result
2266 self.config.set_key('use_change', self.wallet.use_change, True)
2269 n = int(gap_e.text())
2271 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2274 if self.wallet.gap_limit != n:
2275 r = self.wallet.change_gap_limit(n)
2277 self.update_receive_tab()
2278 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2280 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2282 need_restart = False
2284 lang_request = languages.keys()[lang_combo.currentIndex()]
2285 if lang_request != self.config.get('language'):
2286 self.config.set_key("language", lang_request, True)
2289 cur_request = str(currencies[cur_combo.currentIndex()])
2290 if cur_request != self.config.get('currency', "None"):
2291 self.config.set_key('currency', cur_request, True)
2292 self.update_wallet()
2295 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2297 self.receive_tab_set_mode(view_combo.currentIndex())
2301 def network_dialog(wallet, parent=None):
2302 interface = wallet.interface
2304 if interface.is_connected:
2305 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2307 status = _("Not connected")
2308 server = interface.server
2311 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2312 server = interface.server
2314 plist, servers_list = interface.get_servers_list()
2318 d.setWindowTitle(_('Server'))
2319 d.setMinimumSize(375, 20)
2321 vbox = QVBoxLayout()
2324 hbox = QHBoxLayout()
2326 l.setPixmap(QPixmap(":icons/network.png"))
2329 hbox.addWidget(QLabel(status))
2331 vbox.addLayout(hbox)
2335 grid = QGridLayout()
2337 vbox.addLayout(grid)
2340 server_protocol = QComboBox()
2341 server_host = QLineEdit()
2342 server_host.setFixedWidth(200)
2343 server_port = QLineEdit()
2344 server_port.setFixedWidth(60)
2346 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2347 protocol_letters = 'thsg'
2348 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2349 server_protocol.addItems(protocol_names)
2351 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2352 grid.addWidget(server_protocol, 0, 1)
2353 grid.addWidget(server_host, 0, 2)
2354 grid.addWidget(server_port, 0, 3)
2356 def change_protocol(p):
2357 protocol = protocol_letters[p]
2358 host = unicode(server_host.text())
2359 pp = plist.get(host,DEFAULT_PORTS)
2360 if protocol not in pp.keys():
2361 protocol = pp.keys()[0]
2363 server_host.setText( host )
2364 server_port.setText( port )
2366 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2368 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2369 servers_list_widget = QTreeWidget(parent)
2370 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2371 servers_list_widget.setMaximumHeight(150)
2372 servers_list_widget.setColumnWidth(0, 240)
2373 for _host in servers_list.keys():
2374 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2375 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2377 def change_server(host, protocol=None):
2378 pp = plist.get(host,DEFAULT_PORTS)
2380 port = pp.get(protocol)
2381 if not port: protocol = None
2384 if 't' in pp.keys():
2386 port = pp.get(protocol)
2388 protocol = pp.keys()[0]
2389 port = pp.get(protocol)
2391 server_host.setText( host )
2392 server_port.setText( port )
2393 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2395 if not plist: return
2396 for p in protocol_letters:
2397 i = protocol_letters.index(p)
2398 j = server_protocol.model().index(i,0)
2399 if p not in pp.keys():
2400 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2402 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2406 host, port, protocol = server.split(':')
2407 change_server(host,protocol)
2409 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2410 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2412 if not wallet.config.is_modifiable('server'):
2413 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2416 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2417 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2418 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2419 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2422 proxy_mode = QComboBox()
2423 proxy_host = QLineEdit()
2424 proxy_host.setFixedWidth(200)
2425 proxy_port = QLineEdit()
2426 proxy_port.setFixedWidth(60)
2427 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2429 def check_for_disable(index = False):
2430 if proxy_mode.currentText() != 'NONE':
2431 proxy_host.setEnabled(True)
2432 proxy_port.setEnabled(True)
2434 proxy_host.setEnabled(False)
2435 proxy_port.setEnabled(False)
2438 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2440 if not wallet.config.is_modifiable('proxy'):
2441 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2443 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2444 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2445 proxy_host.setText(proxy_config.get("host"))
2446 proxy_port.setText(proxy_config.get("port"))
2448 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2449 grid.addWidget(proxy_mode, 2, 1)
2450 grid.addWidget(proxy_host, 2, 2)
2451 grid.addWidget(proxy_port, 2, 3)
2454 vbox.addLayout(ok_cancel_buttons(d))
2457 if not d.exec_(): return
2459 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2460 if proxy_mode.currentText() != 'NONE':
2461 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2465 wallet.config.set_key("proxy", proxy, True)
2466 wallet.config.set_key("server", server, True)
2467 interface.set_server(server, proxy)
2468 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2471 def closeEvent(self, event):
2473 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2474 self.save_column_widths()
2475 self.config.set_key("column-widths", self.column_widths, True)
2481 def __init__(self, wallet, config, app=None):
2482 self.wallet = wallet
2483 self.config = config
2485 self.app = QApplication(sys.argv)
2488 def restore_or_create(self):
2489 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2490 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2491 if r==2: return None
2492 return 'restore' if r==1 else 'create'
2494 def seed_dialog(self):
2495 return ElectrumWindow.seed_dialog( self.wallet )
2497 def network_dialog(self):
2498 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2501 def show_seed(self):
2502 ElectrumWindow.show_seed_dialog(self.wallet)
2505 def password_dialog(self):
2506 ElectrumWindow.change_password_dialog(self.wallet)
2509 def restore_wallet(self):
2510 wallet = self.wallet
2511 # wait until we are connected, because the user might have selected another server
2512 if not wallet.interface.is_connected:
2513 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2514 waiting_dialog(waiting)
2516 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2517 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2519 wallet.set_up_to_date(False)
2520 wallet.interface.poke('synchronizer')
2521 waiting_dialog(waiting)
2522 if wallet.is_found():
2523 print_error( "Recovery successful" )
2525 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2532 w = ElectrumWindow(self.wallet, self.config)
2533 if url: w.set_url(url)