commit bfbd6f0e9df5d05f9021e43168ef4e6fce95c60d
parent babead68b6e301ffbe56ca2707370b3895e174fa
Author: ThomasV <thomasv1@gmx.de>
Date: Fri, 24 Oct 2014 16:44:33 +0200
Merge pull request #899 from Tafelpoot/qrcode_fix2
QR code fixes
Diffstat:
8 files changed, 111 insertions(+), 50 deletions(-)
diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
@@ -128,7 +128,7 @@ class InstallWizard(QDialog):
def enter_seed_dialog(self, msg, sid, func=None):
if func is None:
func = self.is_any
- vbox, seed_e = seed_dialog.enter_seed_box(msg, sid)
+ vbox, seed_e = seed_dialog.enter_seed_box(msg, self, sid)
vbox.addStretch(1)
hbox, button = ok_cancel_buttons2(self, _('Next'))
vbox.addLayout(hbox)
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -45,7 +45,7 @@ from electrum import Imported_Wallet
from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
from network_dialog import NetworkDialog
from qrcodewidget import QRCodeWidget, QRDialog
-from qrtextedit import QRTextEdit
+from qrtextedit import ScanQRTextEdit, ShowQRTextEdit
from decimal import Decimal
@@ -1828,15 +1828,36 @@ class ElectrumWindow(QMainWindow):
main_layout = QGridLayout()
mpk_dict = self.wallet.get_master_public_keys()
- i = 0
- for key, value in mpk_dict.items():
- main_layout.addWidget(QLabel(key), i, 0)
- mpk_text = QRTextEdit()
- mpk_text.setReadOnly(True)
+ # filter out the empty keys (PendingAccount)
+ mpk_dict = {acc:mpk for acc,mpk in mpk_dict.items() if mpk}
+
+ # only show the combobox in case multiple accounts are available
+ if len(mpk_dict) > 1:
+ main_layout.addWidget(QLabel(_("Select Account")), 0, 0)
+
+ combobox = QComboBox()
+ for name in mpk_dict:
+ combobox.addItem(name)
+ combobox.setCurrentIndex(0)
+ main_layout.addWidget(combobox, 1, 0)
+
+ account = unicode(combobox.currentText())
+ mpk_text = ShowQRTextEdit(text=mpk_dict[account])
+ mpk_text.setMaximumHeight(170)
+ mpk_text.selectAll() # for easy copying
+ main_layout.addWidget(mpk_text, 2, 0)
+
+ def show_mpk(account):
+ mpk = mpk_dict.get(unicode(account), "")
+ mpk_text.setText(mpk)
+
+ combobox.currentIndexChanged[str].connect(lambda acc: show_mpk(acc))
+ elif len(mpk_dict) == 1:
+ mpk = mpk_dict.values()[0]
+ mpk_text = ShowQRTextEdit(text=mpk)
mpk_text.setMaximumHeight(170)
- mpk_text.setText(value)
- main_layout.addWidget(mpk_text, i + 1, 0)
- i += 2
+ mpk_text.selectAll() # for easy copying
+ main_layout.addWidget(mpk_text, 2, 0)
vbox = QVBoxLayout()
vbox.addLayout(main_layout)
@@ -1845,7 +1866,6 @@ class ElectrumWindow(QMainWindow):
dialog.setLayout(vbox)
dialog.exec_()
-
@protected
def show_seed_dialog(self, password):
if not self.wallet.has_seed():
@@ -1900,9 +1920,7 @@ class ElectrumWindow(QMainWindow):
vbox = QVBoxLayout()
vbox.addWidget( QLabel(_("Address") + ': ' + address))
vbox.addWidget( QLabel(_("Public key") + ':'))
- keys = QRTextEdit()
- keys.setReadOnly(True)
- keys.setText('\n'.join(pubkey_list))
+ keys = ShowQRTextEdit(text='\n'.join(pubkey_list))
vbox.addWidget(keys)
vbox.addLayout(close_button(d))
d.setLayout(vbox)
@@ -1924,9 +1942,7 @@ class ElectrumWindow(QMainWindow):
vbox = QVBoxLayout()
vbox.addWidget( QLabel(_("Address") + ': ' + address))
vbox.addWidget( QLabel(_("Private key") + ':'))
- keys = QRTextEdit()
- keys.setReadOnly(True)
- keys.setText('\n'.join(pk_list))
+ keys = ShowQRTextEdit(text='\n'.join(pk_list))
vbox.addWidget(keys)
vbox.addLayout(close_button(d))
d.setLayout(vbox)
@@ -2128,6 +2144,12 @@ class ElectrumWindow(QMainWindow):
def read_tx_from_qrcode(self):
from electrum import qrscanner
+ if qrscanner.proc is None:
+ try:
+ qrscanner.init(self.config)
+ except Exception, e:
+ QMessageBox.warning(self, _('Error'), _(e), _('OK'))
+ return
try:
data = qrscanner.scan_qr(self.config)
except BaseException, e:
@@ -2135,12 +2157,14 @@ class ElectrumWindow(QMainWindow):
return
if not data:
return
+ # if the user scanned a bitcoin URI
+ if data.startswith("bitcoin:"):
+ self.pay_from_URI(data)
+ return
+ # else if the user scanned an offline signed tx
# transactions are binary, but qrcode seems to return utf8...
z = data.decode('utf8')
- s = ''
- for b in z:
- s += chr(ord(b))
- data = s.encode('hex')
+ data = ''.join(chr(ord(b)) for b in z).encode('hex')
tx = self.tx_from_text(data)
if not tx:
return
diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py
@@ -18,7 +18,7 @@
from PyQt4.QtCore import *
from PyQt4.QtGui import *
-from qrtextedit import QRTextEdit
+from qrtextedit import ScanQRTextEdit
import re
from decimal import Decimal
@@ -30,11 +30,9 @@ RE_ALIAS = '(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>'
frozen_style = "QWidget { background-color:none; border:none;}"
normal_style = "QPlainTextEdit { }"
-class PayToEdit(QRTextEdit):
-
+class PayToEdit(ScanQRTextEdit):
def __init__(self, win):
- QRTextEdit.__init__(self)
- self.win = win
+ super(PayToEdit,self).__init__(win=win)
self.amount_edit = win.amount_e
self.document().contentsChanged.connect(self.update_size)
self.heightMin = 0
@@ -235,3 +233,10 @@ class PayToEdit(QRTextEdit):
cr = self.cursorRect()
cr.setWidth(self.c.popup().sizeHintForColumn(0) + self.c.popup().verticalScrollBar().sizeHint().width())
self.c.complete(cr)
+
+
+ def qr_input(self):
+ data = super(PayToEdit,self).qr_input()
+ if data.startswith("bitcoin:"):
+ self.scan_f(data)
+ # TODO: update fee
diff --git a/gui/qt/qrcodewidget.py b/gui/qt/qrcodewidget.py
@@ -114,7 +114,7 @@ class QRDialog(QDialog):
def copy_to_clipboard():
bmp.save_qrcode(qrw.qr, filename)
- self.parent().app.clipboard().setImage(QImage(filename))
+ QApplication.clipboard().setImage(QImage(filename))
QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
b = QPushButton(_("Copy"))
diff --git a/gui/qt/qrtextedit.py b/gui/qt/qrtextedit.py
@@ -3,14 +3,13 @@ from PyQt4.QtGui import *
from PyQt4.QtCore import *
class QRTextEdit(QPlainTextEdit):
-
+ """Abstract class for QR-code related TextEdits. Do not use directly."""
def __init__(self, text=None):
- QPlainTextEdit.__init__(self, text)
+ super(QRTextEdit, self).__init__(text)
self.button = QToolButton(self)
self.button.setIcon(QIcon(":icons/qrcode.png"))
self.button.setStyleSheet("QToolButton { border: none; padding: 0px; }")
self.button.setVisible(True)
- self.button.clicked.connect(lambda: self.qr_show() if self.isReadOnly() else self.qr_input())
self.setText = self.setPlainText
def resizeEvent(self, e):
@@ -21,13 +20,11 @@ class QRTextEdit(QPlainTextEdit):
(self.rect().bottom() - frameWidth - sz.height()))
return o
- def contextMenuEvent(self, e):
- m = self.createStandardContextMenu()
- if self.isReadOnly():
- m.addAction(_("Show as QR code"), self.qr_show)
- else:
- m.addAction(_("Read QR code"), self.qr_input)
- m.exec_(e.globalPos())
+class ShowQRTextEdit(QRTextEdit):
+ def __init__(self, text=None):
+ super(ShowQRTextEdit, self).__init__(text)
+ self.setReadOnly(1)
+ self.button.clicked.connect(self.qr_show)
def qr_show(self):
from qrcodewidget import QRDialog
@@ -37,13 +34,42 @@ class QRTextEdit(QPlainTextEdit):
s = unicode(self.toPlainText())
QRDialog(s).exec_()
+ def contextMenuEvent(self, e):
+ m = self.createStandardContextMenu()
+ m.addAction(_("Show as QR code"), self.qr_show)
+ m.exec_(e.globalPos())
+
+
+class ScanQRTextEdit(QRTextEdit):
+ def __init__(self, win, text=""):
+ super(ScanQRTextEdit,self).__init__(text)
+ self.setReadOnly(0)
+ self.win = win
+ assert win, "You must pass a window with access to the config to ScanQRTextEdit constructor."
+ if win:
+ assert hasattr(win,"config"), "You must pass a window with access to the config to ScanQRTextEdit constructor."
+ self.button.clicked.connect(self.qr_input)
+
+
def qr_input(self):
from electrum import qrscanner
+ if qrscanner.proc is None:
+ try:
+ qrscanner.init(self.win.config)
+ except Exception, e:
+ QMessageBox.warning(self, _('Error'), _(e), _('OK'))
+ return
try:
data = qrscanner.scan_qr(self.win.config)
except BaseException, e:
- QMessageBox.warning(self.win, _('Error'), _(e), _('OK'))
+ QMessageBox.warning(self, _('Error'), _(e), _('OK'))
return
if type(data) != str:
return
self.setText(data)
+ return data
+
+ def contextMenuEvent(self, e):
+ m = self.createStandardContextMenu()
+ m.addAction(_("Read QR code"), self.qr_input)
+ m.exec_(e.globalPos())
diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py
@@ -23,7 +23,7 @@ from electrum.i18n import _
from electrum import mnemonic
from qrcodewidget import QRCodeWidget, QRDialog
from util import close_button
-from qrtextedit import QRTextEdit
+from qrtextedit import ShowQRTextEdit, ScanQRTextEdit
class SeedDialog(QDialog):
def __init__(self, parent, seed, imported_keys):
@@ -72,15 +72,13 @@ def show_seed_box(seed, sid=None):
+ _("If you ever need to recover your wallet from seed, you will need both this seed and your cold seed.") + " " \
label1 = QLabel(msg+ ":")
- seed_text = QRTextEdit(seed)
- seed_text.setReadOnly(True)
+ seed_text = ShowQRTextEdit(text=seed)
seed_text.setMaximumHeight(130)
label2 = QLabel(msg2)
label2.setWordWrap(True)
logo = QLabel()
-
logo.setPixmap(QPixmap(icon_filename(sid)).scaledToWidth(56))
logo.setMaximumWidth(60)
@@ -96,8 +94,7 @@ def show_seed_box(seed, sid=None):
return vbox
-def enter_seed_box(msg, sid=None):
-
+def enter_seed_box(msg, window, sid=None):
vbox = QVBoxLayout()
logo = QLabel()
logo.setPixmap(QPixmap(icon_filename(sid)).scaledToWidth(56))
@@ -106,7 +103,7 @@ def enter_seed_box(msg, sid=None):
label = QLabel(msg)
label.setWordWrap(True)
- seed_e = QRTextEdit()
+ seed_e = ScanQRTextEdit(win=window)
seed_e.setMaximumHeight(100)
seed_e.setTabChangesFocus(True)
diff --git a/gui/qt/util.py b/gui/qt/util.py
@@ -137,7 +137,7 @@ def line_dialog(parent, title, label, ok_label, default=None):
return unicode(txt.text())
def text_dialog(parent, title, label, ok_label, default=None):
- from qrtextedit import QRTextEdit
+ from qrtextedit import ScanQRTextEdit
dialog = QDialog(parent)
dialog.setMinimumWidth(500)
dialog.setWindowTitle(title)
@@ -145,7 +145,7 @@ def text_dialog(parent, title, label, ok_label, default=None):
l = QVBoxLayout()
dialog.setLayout(l)
l.addWidget(QLabel(label))
- txt = QRTextEdit()
+ txt = ScanQRTextEdit(parent)
if default:
txt.setText(default)
l.addWidget(txt)
diff --git a/lib/qrscanner.py b/lib/qrscanner.py
@@ -6,28 +6,37 @@ try:
except ImportError:
zbar = None
+proc = None
-def scan_qr(config):
+def init(config):
if not zbar:
raise BaseException("\n".join([_("Cannot start QR scanner."),_("The zbar package is not available."),_("On Linux, try 'sudo apt-get install python-zbar'")]))
device = config.get("video_device", "default")
if device == 'default':
device = ''
+ global proc
proc = zbar.Processor()
proc.init(video_device=device)
+
+def scan_qr(self):
+ if not zbar:
+ raise BaseException("\n".join([_("Cannot start QR scanner."),_("The zbar package is not available."),_("On Linux, try 'sudo apt-get install python-zbar'")]))
+ if proc is None:
+ raise BaseException("Start proc first")
proc.visible = True
while True:
try:
proc.process_one()
except Exception:
# User closed the preview window
- return {}
+ return ""
for r in proc.results:
if str(r.type) != 'QRCODE':
continue
+ # hiding the preview window stops the camera
+ proc.visible = False
return r.data
-
def _find_system_cameras():
device_root = "/sys/class/video4linux"
devices = {} # Name -> device