Browse Source

QR code support for BIP78 payjoin receiver in Qt GUI

master
Kristaps Kaupe 5 years ago
parent
commit
7414959604
No known key found for this signature in database
GPG Key ID: D47B1B4232B55437
  1. 2
      requirements/gui.txt
  2. 24
      scripts/joinmarket-qt.py
  3. 38
      scripts/qtsupport.py

2
requirements/gui.txt

@ -2,5 +2,5 @@
pywin32; platform_system == "Windows" pywin32; platform_system == "Windows"
PySide2 PySide2
PyQt5==5.14.2 PyQt5==5.14.2
qrcode[pil] qrcode
https://github.com/sunu/qt5reactor/archive/58410aaead2185e9917ae9cac9c50fe7b70e4a60.zip#egg=qt5reactor https://github.com/sunu/qt5reactor/archive/58410aaead2185e9917ae9cac9c50fe7b70e4a60.zip#egg=qt5reactor

24
scripts/joinmarket-qt.py

@ -22,7 +22,6 @@ Some widgets copied and modified from https://github.com/spesmilo/electrum
import sys, datetime, os, logging import sys, datetime, os, logging
import platform, json, threading, time import platform, json, threading, time
import qrcode
from optparse import OptionParser from optparse import OptionParser
from PySide2 import QtCore from PySide2 import QtCore
@ -31,8 +30,6 @@ from PySide2.QtGui import *
from PySide2.QtWidgets import * from PySide2.QtWidgets import *
from PIL.ImageQt import ImageQt
if platform.system() == 'Windows': if platform.system() == 'Windows':
MONOSPACE_FONT = 'Lucida Console' MONOSPACE_FONT = 'Lucida Console'
elif platform.system() == 'Darwin': elif platform.system() == 'Darwin':
@ -82,7 +79,7 @@ from qtsupport import ScheduleWizard, TumbleRestartWizard, config_tips,\
config_types, QtHandler, XStream, Buttons, OkButton, CancelButton,\ config_types, QtHandler, XStream, Buttons, OkButton, CancelButton,\
PasswordDialog, MyTreeWidget, JMQtMessageBox, BLUE_FG,\ PasswordDialog, MyTreeWidget, JMQtMessageBox, BLUE_FG,\
donation_more_message, BitcoinAmountEdit, JMIntValidator,\ donation_more_message, BitcoinAmountEdit, JMIntValidator,\
ReceiveBIP78Dialog ReceiveBIP78Dialog, QRCodePopup
from twisted.internet import task from twisted.internet import task
@ -1302,23 +1299,6 @@ class CoinsTab(QWidget):
lambda: app.clipboard().setText(txid)) lambda: app.clipboard().setText(txid))
menu.exec_(self.cTW.viewport().mapToGlobal(position)) menu.exec_(self.cTW.viewport().mapToGlobal(position))
class BitcoinQRCodePopup(QDialog):
def __init__(self, parent, address):
super().__init__(parent)
self.address = address
self.setWindowTitle(address)
img = qrcode.make('bitcoin:' + address)
self.imageLabel = QLabel()
self.imageLabel.setPixmap(QPixmap.fromImage(ImageQt(img)))
layout = QVBoxLayout()
layout.addWidget(self.imageLabel)
self.setLayout(layout)
self.initUI()
def initUI(self):
self.show()
class JMWalletTab(QWidget): class JMWalletTab(QWidget):
@ -1385,7 +1365,7 @@ class JMWalletTab(QWidget):
menu.exec_(self.walletTree.viewport().mapToGlobal(position)) menu.exec_(self.walletTree.viewport().mapToGlobal(position))
def openQRCodePopup(self, address): def openQRCodePopup(self, address):
popup = BitcoinQRCodePopup(self, address) popup = QRCodePopup(self, address, btc.encode_bip21_uri(address, {}))
popup.show() popup.show()
def updateWalletInfo(self, walletinfo=None): def updateWalletInfo(self, walletinfo=None):

38
scripts/qtsupport.py

@ -17,12 +17,15 @@ Qt files for the wizard for initiating a tumbler run.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
''' '''
import math, re, logging, string import math, logging, qrcode, re, string
from io import BytesIO
from PySide2 import QtCore from PySide2 import QtCore
from PySide2.QtGui import * from PySide2.QtGui import *
from PySide2.QtWidgets import * from PySide2.QtWidgets import *
from jmbitcoin.amount import amount_to_sat, btc_to_sat, sat_to_btc from jmbitcoin.amount import amount_to_sat, btc_to_sat, sat_to_btc
from jmbitcoin.bip21 import decode_bip21_uri
from jmclient import (jm_single, validate_address, get_tumble_schedule) from jmclient import (jm_single, validate_address, get_tumble_schedule)
@ -943,6 +946,28 @@ class CopyOnClickLineEdit(QLineEdit):
"URI copied to clipboard", mbtype="info") "URI copied to clipboard", mbtype="info")
self.was_copied = True self.was_copied = True
class QRCodePopup(QDialog):
def __init__(self, parent, title, data):
super().__init__(parent)
self.setWindowTitle(title)
buf = BytesIO()
img = qrcode.make(data)
img.save(buf, "PNG")
self.imageLabel = QLabel()
qt_pixmap = QPixmap()
qt_pixmap.loadFromData(buf.getvalue(), "PNG")
self.imageLabel.setPixmap(qt_pixmap)
layout = QVBoxLayout()
layout.addWidget(self.imageLabel)
self.setLayout(layout)
self.initUI()
def initUI(self):
self.show()
class ReceiveBIP78Dialog(QDialog): class ReceiveBIP78Dialog(QDialog):
parameter_names = ['Amount to receive', 'Mixdepth'] parameter_names = ['Amount to receive', 'Mixdepth']
@ -1015,6 +1040,7 @@ class ReceiveBIP78Dialog(QDialog):
# Give user indication that they # Give user indication that they
# can quit without cancelling: # can quit without cancelling:
self.close_btn.setVisible(True) self.close_btn.setVisible(True)
self.qr_btn.setVisible(False)
self.btnbox.button(QDialogButtonBox.Cancel).setDisabled(True) self.btnbox.button(QDialogButtonBox.Cancel).setDisabled(True)
def start_generate(self): def start_generate(self):
@ -1080,12 +1106,22 @@ class ReceiveBIP78Dialog(QDialog):
self.close_btn = self.btnbox.addButton("C&lose", self.close_btn = self.btnbox.addButton("C&lose",
QDialogButtonBox.AcceptRole) QDialogButtonBox.AcceptRole)
self.close_btn.setVisible(False) self.close_btn.setVisible(False)
self.qr_btn = self.btnbox.addButton("Show &QR code",
QDialogButtonBox.ActionRole)
layout.addWidget(self.btnbox, i+4, 0) layout.addWidget(self.btnbox, i+4, 0)
# note that we don't use a standard 'Close' button because # note that we don't use a standard 'Close' button because
# it is also associated with 'rejection' (and we don't use "OK" because # it is also associated with 'rejection' (and we don't use "OK" because
# concept doesn't quite fit here: # concept doesn't quite fit here:
self.btnbox.rejected.connect(self.shutdown_actions) self.btnbox.rejected.connect(self.shutdown_actions)
self.generate_btn.clicked.connect(self.start_generate) self.generate_btn.clicked.connect(self.start_generate)
self.qr_btn.clicked.connect(self.open_qr_code_popup)
# does not trigger cancel_fn callback: # does not trigger cancel_fn callback:
self.close_btn.clicked.connect(self.close) self.close_btn.clicked.connect(self.close)
return layout return layout
def open_qr_code_popup(self):
bip21_uri = self.bip21_widget.text()
if bip21_uri:
parsed_uri = decode_bip21_uri(bip21_uri)
popup = QRCodePopup(self, parsed_uri['address'], bip21_uri)
popup.show()

Loading…
Cancel
Save