Qt receive tab:
- show payment options in tabs: URI, Address or Lightning - use vertical tabs to save space - switch between QR and text views - open standalone QR window through menu, instead of clicking on QR code
This commit is contained in:
BIN
electrum/gui/icons/link.png
Normal file
BIN
electrum/gui/icons/link.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -778,6 +778,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
tools_menu.addSeparator()
|
||||
|
||||
paytomany_menu = tools_menu.addAction(_("&Pay to many"), self.paytomany)
|
||||
tools_menu.addAction(_("&Show QR code in separate window"), self.toggle_receive_qr_window)
|
||||
|
||||
raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
|
||||
raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
|
||||
@@ -1092,6 +1093,20 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
d = LightningTxDialog(self, tx_item)
|
||||
d.show()
|
||||
|
||||
def toggle_receive_qr(self, toggle=False):
|
||||
b = not self.config.get('receive_qr_visible', False)
|
||||
self.config.set_key('receive_qr_visible', b)
|
||||
self.update_receive_widgets()
|
||||
|
||||
def update_receive_widgets(self):
|
||||
b = self.config.get('receive_qr_visible', False)
|
||||
self.receive_address_e.setVisible(b)
|
||||
self.receive_address_qr.setVisible(not b)
|
||||
self.receive_URI_e.setVisible(b)
|
||||
self.receive_URI_qr.setVisible(not b)
|
||||
self.receive_lightning_e.setVisible(b)
|
||||
self.receive_lightning_qr.setVisible(not b)
|
||||
|
||||
def create_receive_tab(self):
|
||||
# A 4-column grid layout. All the stretch is in the last column.
|
||||
# The exchange rate plugin adds a fiat widget in column 2
|
||||
@@ -1099,15 +1114,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
grid.setSpacing(8)
|
||||
grid.setColumnStretch(3, 1)
|
||||
|
||||
self.receive_message_e = SizedFreezableLineEdit(width=700)
|
||||
self.receive_message_e = SizedFreezableLineEdit(width=400)
|
||||
grid.addWidget(QLabel(_('Description')), 0, 0)
|
||||
grid.addWidget(self.receive_message_e, 0, 1, 1, 4)
|
||||
self.receive_message_e.textChanged.connect(self.update_receive_qr)
|
||||
|
||||
self.receive_amount_e = BTCAmountEdit(self.get_decimal_point)
|
||||
grid.addWidget(QLabel(_('Requested amount')), 1, 0)
|
||||
grid.addWidget(self.receive_amount_e, 1, 1)
|
||||
self.receive_amount_e.textChanged.connect(self.update_receive_qr)
|
||||
|
||||
self.fiat_receive_e = AmountEdit(self.fx.get_currency if self.fx else '')
|
||||
if not self.fx or not self.fx.is_enabled():
|
||||
@@ -1159,48 +1172,67 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
buttons.addWidget(self.create_invoice_button)
|
||||
grid.addLayout(buttons, 4, 0, 1, -1)
|
||||
|
||||
self.receive_payreq_e = ButtonsTextEdit()
|
||||
self.receive_payreq_e.setFont(QFont(MONOSPACE_FONT))
|
||||
self.receive_payreq_e.addCopyButton(self.app)
|
||||
self.receive_payreq_e.setReadOnly(True)
|
||||
self.receive_payreq_e.textChanged.connect(self.update_receive_qr)
|
||||
self.receive_payreq_e.setFocusPolicy(Qt.ClickFocus)
|
||||
|
||||
self.receive_qr = QRCodeWidget(fixedSize=220)
|
||||
self.receive_qr.mouseReleaseEvent = lambda x: self.toggle_qr_window()
|
||||
self.receive_qr.enterEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor))
|
||||
self.receive_qr.leaveEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.ArrowCursor))
|
||||
|
||||
self.receive_address_e = ButtonsTextEdit()
|
||||
self.receive_address_e.setFont(QFont(MONOSPACE_FONT))
|
||||
self.receive_address_e.addCopyButton(self.app)
|
||||
self.receive_address_e.setReadOnly(True)
|
||||
self.receive_address_e.textChanged.connect(self.update_receive_address_styling)
|
||||
self.receive_URI_e = ButtonsTextEdit()
|
||||
self.receive_lightning_e = ButtonsTextEdit()
|
||||
#self.receive_URI_e.setFocusPolicy(Qt.ClickFocus)
|
||||
|
||||
qr_show = lambda: self.show_qrcode(str(self.receive_address_e.text()), _('Receiving address'), parent=self)
|
||||
fixedSize = 200
|
||||
qr_icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png"
|
||||
self.receive_address_e.addButton(qr_icon, qr_show, _("Show as QR code"))
|
||||
for e in [self.receive_address_e, self.receive_URI_e, self.receive_lightning_e]:
|
||||
e.setFont(QFont(MONOSPACE_FONT))
|
||||
e.addCopyButton(self.app)
|
||||
e.setReadOnly(True)
|
||||
e.setFixedSize(fixedSize, fixedSize)
|
||||
e.addButton(qr_icon, self.toggle_receive_qr, _("Show as QR code"))
|
||||
|
||||
self.receive_address_qr = QRCodeWidget(fixedSize=fixedSize)
|
||||
self.receive_URI_qr = QRCodeWidget(fixedSize=fixedSize)
|
||||
self.receive_lightning_qr = QRCodeWidget(fixedSize=fixedSize)
|
||||
|
||||
self.receive_lightning_e.textChanged.connect(self.update_receive_widgets)
|
||||
|
||||
receive_address_layout = QHBoxLayout()
|
||||
receive_address_layout.addWidget(self.receive_address_e)
|
||||
receive_address_layout.addWidget(self.receive_address_qr)
|
||||
receive_URI_layout = QHBoxLayout()
|
||||
receive_URI_layout.addWidget(self.receive_URI_e)
|
||||
receive_URI_layout.addWidget(self.receive_URI_qr)
|
||||
receive_lightning_layout = QHBoxLayout()
|
||||
receive_lightning_layout.addWidget(self.receive_lightning_e)
|
||||
receive_lightning_layout.addWidget(self.receive_lightning_qr)
|
||||
|
||||
from .util import VTabWidget
|
||||
self.receive_tabs = VTabWidget()
|
||||
receive_address_widget = QWidget()
|
||||
receive_address_widget.setLayout(receive_address_layout)
|
||||
receive_URI_widget = QWidget()
|
||||
receive_URI_widget.setLayout(receive_URI_layout)
|
||||
receive_lightning_widget = QWidget()
|
||||
receive_lightning_widget.setLayout(receive_lightning_layout)
|
||||
|
||||
self.receive_tabs.addTab(receive_URI_widget, read_QIcon("link.png"), _('URI'))
|
||||
self.receive_tabs.addTab(receive_address_widget, read_QIcon("bitcoin.png"), _('Address'))
|
||||
self.receive_tabs.addTab(receive_lightning_widget, read_QIcon("lightning.png"), _('Lightning'))
|
||||
def on_current_changed(index):
|
||||
self.update_receive_qr_window()
|
||||
def on_tab_bar_clicked(index):
|
||||
w = self.receive_tabs.widget(index)
|
||||
if w == self.receive_tabs.currentWidget() and self.receive_tabs.isTabEnabled(index):
|
||||
self.toggle_receive_qr()
|
||||
self.receive_tabs.tabBarClicked.connect(on_tab_bar_clicked)
|
||||
self.receive_tabs.currentChanged.connect(on_current_changed)
|
||||
self.receive_tabs.setCurrentIndex(self.config.get('receive_tabs_index', 0))
|
||||
self.receive_tabs.currentChanged.connect(lambda i: self.config.set_key('receive_tabs_index', i))
|
||||
receive_tabs_sp = self.receive_tabs.sizePolicy()
|
||||
receive_tabs_sp.setRetainSizeWhenHidden(True)
|
||||
self.receive_tabs.setSizePolicy(receive_tabs_sp)
|
||||
self.receive_tabs.setVisible(False)
|
||||
|
||||
self.receive_requests_label = QLabel(_('Receive queue'))
|
||||
|
||||
from .request_list import RequestList
|
||||
self.request_list = RequestList(self)
|
||||
|
||||
receive_tabs = QTabWidget()
|
||||
receive_tabs.addTab(self.receive_address_e, _('Address'))
|
||||
receive_tabs.addTab(self.receive_payreq_e, _('Request'))
|
||||
receive_tabs.addTab(self.receive_qr, _('QR Code'))
|
||||
receive_tabs.setCurrentIndex(self.config.get('receive_tabs_index', 0))
|
||||
receive_tabs.currentChanged.connect(lambda i: self.config.set_key('receive_tabs_index', i))
|
||||
receive_tabs_sp = receive_tabs.sizePolicy()
|
||||
receive_tabs_sp.setRetainSizeWhenHidden(True)
|
||||
receive_tabs.setSizePolicy(receive_tabs_sp)
|
||||
|
||||
def maybe_hide_receive_tabs():
|
||||
receive_tabs.setVisible(bool(self.receive_payreq_e.text()))
|
||||
self.receive_payreq_e.textChanged.connect(maybe_hide_receive_tabs)
|
||||
maybe_hide_receive_tabs()
|
||||
|
||||
# layout
|
||||
vbox_g = QVBoxLayout()
|
||||
vbox_g.addLayout(grid)
|
||||
@@ -1208,7 +1240,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
hbox = QHBoxLayout()
|
||||
hbox.addLayout(vbox_g)
|
||||
hbox.addStretch()
|
||||
hbox.addWidget(receive_tabs)
|
||||
hbox.addWidget(self.receive_tabs)
|
||||
|
||||
w = QWidget()
|
||||
w.searchable_list = self.request_list
|
||||
@@ -1222,6 +1254,43 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
|
||||
return w
|
||||
|
||||
def show_receive_request(self, req):
|
||||
addr = req.get_address() or ''
|
||||
URI = req.get_bip21_URI() if addr else ''
|
||||
lnaddr = req.lightning_invoice or ''
|
||||
can_receive_lightning = self.wallet.lnworker and req.get_amount_sat() <= self.wallet.lnworker.num_sats_can_receive()
|
||||
if not can_receive_lightning:
|
||||
lnaddr = ''
|
||||
icon_name = "lightning.png" if can_receive_lightning else "lightning_disconnected.png"
|
||||
self.receive_tabs.setTabIcon(2, read_QIcon(icon_name))
|
||||
# encode lightning invoices as uppercase so QR encoding can use
|
||||
# alphanumeric mode; resulting in smaller QR codes
|
||||
lnaddr_qr = lnaddr.upper()
|
||||
self.receive_address_e.setText(addr)
|
||||
self.receive_address_qr.setData(addr)
|
||||
self.receive_URI_e.setText(URI)
|
||||
self.receive_URI_qr.setData(URI)
|
||||
self.receive_lightning_e.setText(lnaddr) # TODO maybe prepend "lightning:" ??
|
||||
self.receive_lightning_qr.setData(lnaddr_qr)
|
||||
# macOS hack (similar to #4777)
|
||||
self.receive_lightning_e.repaint()
|
||||
self.receive_URI_e.repaint()
|
||||
self.receive_address_e.repaint()
|
||||
# always show
|
||||
self.receive_tabs.setVisible(True)
|
||||
self.update_receive_qr_window()
|
||||
|
||||
def update_receive_qr_window(self):
|
||||
if self.qr_window and self.qr_window.isVisible():
|
||||
i = self.receive_tabs.currentIndex()
|
||||
if i == 0:
|
||||
data = self.receive_address_qr.data
|
||||
elif i == 1:
|
||||
data = self.receive_URI_qr.data
|
||||
else:
|
||||
data = self.receive_lightning_qr.data
|
||||
self.qr_window.qrw.setData(data)
|
||||
|
||||
def delete_requests(self, keys):
|
||||
for key in keys:
|
||||
self.wallet.delete_request(key)
|
||||
@@ -1276,7 +1345,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
except InvoiceError as e:
|
||||
self.show_error(_('Error creating payment request') + ':\n' + str(e))
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
self.logger.exception('Error adding payment request')
|
||||
self.show_error(_('Error adding payment request') + ':\n' + repr(e))
|
||||
return
|
||||
self.sign_payment_request(address)
|
||||
assert key is not None
|
||||
self.address_list.refresh_all()
|
||||
self.request_list.update()
|
||||
@@ -1290,7 +1363,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
title = _('Invoice') if r.is_lightning() else _('Address')
|
||||
self.do_copy(content, title=title)
|
||||
|
||||
def get_bitcoin_address_for_request(self, amount: int) -> Optional[str]:
|
||||
def get_bitcoin_address_for_request(self, amount) -> Optional[str]:
|
||||
addr = self.wallet.get_unused_address()
|
||||
if addr is None:
|
||||
if not self.wallet.is_deterministic(): # imported wallet
|
||||
@@ -1318,15 +1391,17 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
QToolTip.showText(QCursor.pos(), tooltip_text, self)
|
||||
|
||||
def clear_receive_tab(self):
|
||||
self.receive_payreq_e.setText('')
|
||||
self.receive_address_e.setText('')
|
||||
self.receive_URI_e.setText('')
|
||||
self.receive_lightning_e.setText('')
|
||||
self.receive_tabs.setVisible(False)
|
||||
self.receive_message_e.setText('')
|
||||
self.receive_amount_e.setAmount(None)
|
||||
self.expires_label.hide()
|
||||
self.expires_combo.show()
|
||||
self.request_list.clearSelection()
|
||||
|
||||
def toggle_qr_window(self):
|
||||
def toggle_receive_qr_window(self):
|
||||
from . import qrwindow
|
||||
if not self.qr_window:
|
||||
self.qr_window = qrwindow.QR_Window(self)
|
||||
@@ -1339,7 +1414,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
else:
|
||||
self.qr_window_geometry = self.qr_window.geometry()
|
||||
self.qr_window.setVisible(False)
|
||||
self.update_receive_qr()
|
||||
|
||||
def show_send_tab(self):
|
||||
self.tabs.setCurrentIndex(self.tabs.indexOf(self.send_tab))
|
||||
@@ -1347,16 +1421,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
def show_receive_tab(self):
|
||||
self.tabs.setCurrentIndex(self.tabs.indexOf(self.receive_tab))
|
||||
|
||||
def update_receive_qr(self):
|
||||
uri = str(self.receive_payreq_e.text())
|
||||
if maybe_extract_bolt11_invoice(uri):
|
||||
# encode lightning invoices as uppercase so QR encoding can use
|
||||
# alphanumeric mode; resulting in smaller QR codes
|
||||
uri = uri.upper()
|
||||
self.receive_qr.setData(uri)
|
||||
if self.qr_window and self.qr_window.isVisible():
|
||||
self.qr_window.qrw.setData(uri)
|
||||
|
||||
def update_receive_address_styling(self):
|
||||
addr = str(self.receive_address_e.text())
|
||||
if is_address(addr) and self.wallet.is_used(addr):
|
||||
@@ -1624,6 +1688,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
self.need_update.set()
|
||||
|
||||
def on_payment_failed(self, wallet, key, reason):
|
||||
invoice = self.wallet.get_invoice(key)
|
||||
self.show_error(_('Payment failed') + '\n\n' + reason)
|
||||
|
||||
def read_invoice(self):
|
||||
|
||||
@@ -86,7 +86,8 @@ class RequestList(MyTreeView):
|
||||
|
||||
def item_changed(self, idx: Optional[QModelIndex]):
|
||||
if idx is None:
|
||||
self.parent.receive_payreq_e.setText('')
|
||||
self.parent.receive_URI_e.setText('')
|
||||
self.parent.receive_lightning_e.setText('')
|
||||
self.parent.receive_address_e.setText('')
|
||||
return
|
||||
if not idx.isValid():
|
||||
@@ -98,14 +99,7 @@ class RequestList(MyTreeView):
|
||||
if req is None:
|
||||
self.update()
|
||||
return
|
||||
if req.is_lightning():
|
||||
self.parent.receive_payreq_e.setText(req.lightning_invoice) # TODO maybe prepend "lightning:" ??
|
||||
self.parent.receive_address_e.setText(req.lightning_invoice)
|
||||
else:
|
||||
self.parent.receive_payreq_e.setText(self.parent.wallet.get_request_URI(req))
|
||||
self.parent.receive_address_e.setText(req.get_address())
|
||||
self.parent.receive_payreq_e.repaint() # macOS hack (similar to #4777)
|
||||
self.parent.receive_address_e.repaint() # macOS hack (similar to #4777)
|
||||
self.parent.show_receive_request(req)
|
||||
|
||||
def clearSelection(self):
|
||||
super().clearSelection()
|
||||
@@ -138,20 +132,12 @@ class RequestList(MyTreeView):
|
||||
date = format_time(timestamp)
|
||||
amount_str = self.parent.format_amount(amount) if amount else ""
|
||||
labels = [date, message, amount_str, status_str]
|
||||
if req.is_lightning():
|
||||
icon = read_QIcon("lightning.png")
|
||||
tooltip = 'lightning request'
|
||||
else:
|
||||
icon = read_QIcon("bitcoin.png")
|
||||
tooltip = 'onchain request'
|
||||
items = [QStandardItem(e) for e in labels]
|
||||
self.set_editability(items)
|
||||
#items[self.Columns.DATE].setData(request_type, ROLE_REQUEST_TYPE)
|
||||
items[self.Columns.DATE].setData(key, ROLE_KEY)
|
||||
items[self.Columns.DATE].setData(timestamp, ROLE_SORT_ORDER)
|
||||
items[self.Columns.DATE].setIcon(icon)
|
||||
items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
|
||||
items[self.Columns.DATE].setToolTip(tooltip)
|
||||
self.std_model.insertRow(self.std_model.rowCount(), items)
|
||||
self.filter()
|
||||
self.proxy.setDynamicSortFilter(True)
|
||||
@@ -186,13 +172,13 @@ class RequestList(MyTreeView):
|
||||
self.update()
|
||||
return
|
||||
menu = QMenu(self)
|
||||
self.add_copy_menu(menu, idx)
|
||||
if req.is_lightning():
|
||||
menu.addAction(_("Copy Request"), lambda: self.parent.do_copy(req.lightning_invoice, title='Lightning Request'))
|
||||
else:
|
||||
URI = self.wallet.get_request_URI(req)
|
||||
menu.addAction(_("Copy Request"), lambda: self.parent.do_copy(URI, title='Bitcoin URI'))
|
||||
if req.get_address():
|
||||
menu.addAction(_("Copy Address"), lambda: self.parent.do_copy(req.get_address(), title='Bitcoin Address'))
|
||||
URI = self.wallet.get_request_URI(req)
|
||||
menu.addAction(_("Copy URI"), lambda: self.parent.do_copy(URI, title='Bitcoin URI'))
|
||||
if req.is_lightning():
|
||||
menu.addAction(_("Copy Lightning Request"), lambda: self.parent.do_copy(req.lightning_invoice, title='Lightning Request'))
|
||||
self.add_copy_menu(menu, idx)
|
||||
#if 'view_url' in req:
|
||||
# menu.addAction(_("View in web browser"), lambda: webopen(req['view_url']))
|
||||
menu.addAction(_("Delete"), lambda: self.parent.delete_requests([key]))
|
||||
|
||||
@@ -1301,6 +1301,47 @@ class ImageGraphicsEffect(QObject):
|
||||
return result
|
||||
|
||||
|
||||
# vertical tabs
|
||||
# from https://stackoverflow.com/questions/51230544/pyqt5-how-to-set-tabwidget-west-but-keep-the-text-horizontal
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
class VTabBar(QtWidgets.QTabBar):
|
||||
|
||||
def tabSizeHint(self, index):
|
||||
s = QtWidgets.QTabBar.tabSizeHint(self, index)
|
||||
s.transpose()
|
||||
return s
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QtWidgets.QStylePainter(self)
|
||||
opt = QtWidgets.QStyleOptionTab()
|
||||
|
||||
for i in range(self.count()):
|
||||
self.initStyleOption(opt, i)
|
||||
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, opt)
|
||||
painter.save()
|
||||
|
||||
s = opt.rect.size()
|
||||
s.transpose()
|
||||
r = QtCore.QRect(QtCore.QPoint(), s)
|
||||
r.moveCenter(opt.rect.center())
|
||||
opt.rect = r
|
||||
|
||||
c = self.tabRect(i).center()
|
||||
painter.translate(c)
|
||||
painter.rotate(90)
|
||||
painter.translate(-c)
|
||||
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, opt);
|
||||
painter.restore()
|
||||
|
||||
|
||||
class VTabWidget(QtWidgets.QTabWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
QtWidgets.QTabWidget.__init__(self, *args, **kwargs)
|
||||
self.setTabBar(VTabBar(self))
|
||||
self.setTabPosition(QtWidgets.QTabWidget.West)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication([])
|
||||
t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done"))
|
||||
|
||||
Reference in New Issue
Block a user