Browse Source

qt: (refactor) split "receive tab" out from main_window.py

patch-4
SomberNight 3 years ago
parent
commit
5b29e6d4f5
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 5
      electrum/gui/qt/invoice_list.py
  2. 461
      electrum/gui/qt/main_window.py
  3. 11
      electrum/gui/qt/rebalance_dialog.py
  4. 422
      electrum/gui/qt/receive_tab.py
  5. 29
      electrum/gui/qt/request_list.py
  6. 2
      electrum/gui/qt/send_tab.py
  7. 2
      electrum/plugins/hw_wallet/qt.py

5
electrum/gui/qt/invoice_list.py

@ -71,6 +71,8 @@ class InvoiceList(MyTreeView):
window = send_tab.window
super().__init__(window, self.create_menu,
stretch_column=self.Columns.DESCRIPTION)
self.wallet = window.wallet
self.send_tab = send_tab
self.std_model = QStandardItemModel(self)
self.proxy = MySortModel(self, sort_role=ROLE_SORT_ORDER)
self.proxy.setSourceModel(self.std_model)
@ -78,9 +80,6 @@ class InvoiceList(MyTreeView):
self.setSortingEnabled(True)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.send_tab = send_tab
self.wallet = window.wallet
def refresh_row(self, key, row):
assert row is not None
invoice = self.wallet.invoices.get(key)

461
electrum/gui/qt/main_window.py

@ -28,7 +28,6 @@ import threading
import os
import traceback
import json
import shutil
import weakref
import csv
from decimal import Decimal
@ -38,17 +37,15 @@ import queue
import asyncio
from typing import Optional, TYPE_CHECKING, Sequence, List, Union, Dict, Set
import concurrent.futures
from urllib.parse import urlparse
from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont
from PyQt5.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal, QPoint
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import (QMessageBox, QComboBox, QSystemTrayIcon, QTabWidget,
from PyQt5.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal
from PyQt5.QtWidgets import (QMessageBox, QSystemTrayIcon, QTabWidget,
QMenuBar, QFileDialog, QCheckBox, QLabel,
QVBoxLayout, QGridLayout, QLineEdit,
QHBoxLayout, QPushButton, QScrollArea, QTextEdit,
QShortcut, QMainWindow, QCompleter, QInputDialog,
QWidget, QSizePolicy, QStatusBar, QToolTip, QDialog,
QShortcut, QMainWindow, QInputDialog,
QWidget, QSizePolicy, QStatusBar, QToolTip,
QMenu, QAction, QStackedWidget, QToolButton)
import electrum
@ -63,30 +60,24 @@ from electrum.util import (format_time, get_asyncio_loop,
bh2u, bfh, InvalidPassword,
UserFacingException,
get_new_wallet_name, send_exception_to_crash_reporter,
InvalidBitcoinURI, maybe_extract_lightning_payment_identifier, NotEnoughFunds,
NoDynamicFeeEstimates,
AddTransactionException, BITCOIN_BIP21_URI_SCHEME,
InvoiceError, parse_max_spend)
from electrum.invoices import PR_DEFAULT_EXPIRATION_WHEN_CREATING, Invoice
from electrum.invoices import PR_PAID, PR_UNPAID, PR_FAILED, PR_EXPIRED, pr_expiration_values, Invoice
AddTransactionException, BITCOIN_BIP21_URI_SCHEME)
from electrum.invoices import PR_PAID, Invoice
from electrum.transaction import (Transaction, PartialTxInput,
PartialTransaction, PartialTxOutput)
from electrum.wallet import (Multisig_Wallet, CannotBumpFee, Abstract_Wallet,
from electrum.wallet import (Multisig_Wallet, Abstract_Wallet,
sweep_preparations, InternalAddressCorruption,
CannotDoubleSpendTx, CannotCPFP)
CannotCPFP)
from electrum.version import ELECTRUM_VERSION
from electrum.network import (Network, TxBroadcastError, BestEffortRequestFailed,
UntrustedServerReturnedError, NetworkException)
from electrum.network import Network, UntrustedServerReturnedError, NetworkException
from electrum.exchange_rate import FxThread
from electrum.simple_config import SimpleConfig
from electrum.logging import Logger
from electrum.lnutil import ln_dummy_address, extract_nodeid, ConnStringFormatError
from electrum.lnaddr import lndecode, LnInvoiceException
from electrum.lnurl import decode_lnurl, request_lnurl, callback_lnurl, LNURLError, LNURL6Data
from electrum.lnaddr import lndecode
from .exception_window import Exception_Hook
from .amountedit import AmountEdit, BTCAmountEdit, FreezableLineEdit, FeerateEdit, SizedFreezableLineEdit
from .qrcodewidget import QRCodeWidget, QRDialog
from .amountedit import BTCAmountEdit
from .qrcodewidget import QRDialog
from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit, ScanShowQRTextEdit
from .transaction_dialog import show_transaction
from .fee_slider import FeeSlider, FeeComboBox
@ -98,14 +89,13 @@ from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialo
filename_field, address_field, char_width_in_lineedit, webopen,
TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT,
getOpenFileName, getSaveFileName, BlockingWaitingDialog)
from .util import ButtonsTextEdit, ButtonsLineEdit
from .util import ButtonsLineEdit
from .util import QtEventListener, qt_event_listener, event_listener
from .installwizard import WIF_HELP_TEXT
from .history_list import HistoryList, HistoryModel
from .update_checker import UpdateCheck, UpdateCheckThread
from .channels_list import ChannelsList
from .confirm_tx_dialog import ConfirmTxDialog
from .transaction_dialog import PreviewTxDialog
from .rbf_dialog import BumpFeeDialog, DSCancelDialog
from .qrreader import scan_qrcode
from .swap_dialog import SwapDialog
@ -141,36 +131,6 @@ class StatusBarButton(QToolButton):
if e.key() in [Qt.Key_Return, Qt.Key_Enter]:
self.func()
class ReceiveTabWidget(QWidget):
min_size = QSize(200, 200)
def __init__(self, window, textedit, qr, help_widget):
self.textedit = textedit
self.qr = qr
self.help_widget = help_widget
QWidget.__init__(self)
for w in [textedit, qr, help_widget]:
w.setMinimumSize(self.min_size)
for w in [textedit, qr]:
w.mousePressEvent = window.toggle_receive_qr
tooltip = _('Click to switch between text and QR code view')
w.setToolTip(tooltip)
textedit.setFocusPolicy(Qt.NoFocus)
hbox = QHBoxLayout()
hbox.setContentsMargins(0, 0, 0, 0)
hbox.addWidget(textedit)
hbox.addWidget(help_widget)
hbox.addWidget(qr)
self.setLayout(hbox)
def update_visibility(self, is_qr):
if str(self.textedit.text()):
self.help_widget.setVisible(False)
self.textedit.setVisible(not is_qr)
self.qr.setVisible(is_qr)
else:
self.help_widget.setVisible(True)
self.textedit.setVisible(False)
self.qr.setVisible(False)
def protected(func):
'''Password request wrapper. The password is passed to the function
@ -365,7 +325,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
# Refresh edits with the new rate
edit = self.send_tab.fiat_send_e if self.send_tab.fiat_send_e.is_last_edited else self.send_tab.amount_e
edit.textEdited.emit(edit.text())
edit = self.fiat_receive_e if self.fiat_receive_e.is_last_edited else self.receive_amount_e
edit = self.receive_tab.fiat_receive_e if self.receive_tab.fiat_receive_e.is_last_edited else self.receive_tab.receive_amount_e
edit.textEdited.emit(edit.text())
# History tab needs updating if it used spot
if self.fx.history_used_spot:
@ -528,8 +488,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.update_lock_icon()
self.update_buttons_on_seed()
self.update_console()
self.clear_receive_tab()
self.request_list.update()
self.receive_tab.clear_receive_tab()
self.receive_tab.request_list.update()
self.channels_list.update()
self.tabs.show()
self.init_geometry()
@ -893,7 +853,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def timer_actions(self):
# refresh invoices and requests because they show ETA
self.request_list.refresh_all()
self.receive_tab.request_list.refresh_all()
self.send_tab.invoice_list.refresh_all()
# Note this runs in the GUI thread
if self.need_update.is_set():
@ -938,7 +898,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def base_unit(self):
return self.config.get_base_unit()
def connect_fields(self, window, btc_e, fiat_e, fee_e):
def connect_fields(self, btc_e, fiat_e):
def edit_changed(edit):
if edit.follows:
@ -950,8 +910,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
if rate.is_nan() or amount is None:
if edit is fiat_e:
btc_e.setText("")
if fee_e:
fee_e.setText("")
else:
fiat_e.setText("")
else:
@ -960,8 +918,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
btc_e.setAmount(int(amount / Decimal(rate) * COIN))
btc_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
btc_e.follows = False
if fee_e:
window.update_fee()
else:
fiat_e.follows = True
fiat_e.setText(self.fx.ccy_amount_str(
@ -1064,8 +1020,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
if wallet != self.wallet:
return
self.history_model.refresh('update_tabs')
self.request_list.update()
self.update_current_request()
self.receive_tab.request_list.update()
self.receive_tab.update_current_request()
self.send_tab.invoice_list.update()
self.address_list.update()
self.utxo_list.update()
@ -1075,7 +1031,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def refresh_tabs(self, wallet=None):
self.history_model.refresh('refresh_tabs')
self.request_list.refresh_all()
self.receive_tab.request_list.refresh_all()
self.send_tab.invoice_list.refresh_all()
self.address_list.refresh_all()
self.utxo_list.refresh_all()
@ -1116,345 +1072,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
d = LightningTxDialog(self, tx_item)
d.show()
def toggle_receive_qr(self, e):
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_URI_widget.update_visibility(b)
self.receive_address_widget.update_visibility(b)
self.receive_lightning_widget.update_visibility(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
self.receive_grid = grid = QGridLayout()
grid.setSpacing(8)
grid.setColumnStretch(3, 1)
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_amount_e = BTCAmountEdit(self.get_decimal_point)
grid.addWidget(QLabel(_('Requested amount')), 1, 0)
grid.addWidget(self.receive_amount_e, 1, 1)
self.fiat_receive_e = AmountEdit(self.fx.get_currency if self.fx else '')
if not self.fx or not self.fx.is_enabled():
self.fiat_receive_e.setVisible(False)
grid.addWidget(self.fiat_receive_e, 1, 2, Qt.AlignLeft)
self.connect_fields(self, self.receive_amount_e, self.fiat_receive_e, None)
self.connect_fields(self, self.send_tab.amount_e, self.send_tab.fiat_send_e, None)
self.expires_combo = QComboBox()
evl = sorted(pr_expiration_values.items())
evl_keys = [i[0] for i in evl]
evl_values = [i[1] for i in evl]
default_expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
try:
i = evl_keys.index(default_expiry)
except ValueError:
i = 0
self.expires_combo.addItems(evl_values)
self.expires_combo.setCurrentIndex(i)
def on_expiry(i):
self.config.set_key('request_expiry', evl_keys[i])
self.expires_combo.currentIndexChanged.connect(on_expiry)
msg = ''.join([
_('Expiration date of your request.'), ' ',
_('This information is seen by the recipient if you send them a signed payment request.'),
'\n\n',
_('For on-chain requests, the address gets reserved until expiration. After that, it might get reused.'), ' ',
_('The bitcoin address never expires and will always be part of this electrum wallet.'), ' ',
_('You can reuse a bitcoin address any number of times but it is not good for your privacy.'),
'\n\n',
_('For Lightning requests, payments will not be accepted after the expiration.'),
])
grid.addWidget(HelpLabel(_('Expires after') + ' (?)', msg), 2, 0)
grid.addWidget(self.expires_combo, 2, 1)
self.expires_label = QLineEdit('')
self.expires_label.setReadOnly(1)
self.expires_label.setFocusPolicy(Qt.NoFocus)
self.expires_label.hide()
grid.addWidget(self.expires_label, 2, 1)
self.clear_invoice_button = QPushButton(_('Clear'))
self.clear_invoice_button.clicked.connect(self.clear_receive_tab)
self.create_invoice_button = QPushButton(_('Create Request'))
self.create_invoice_button.clicked.connect(lambda: self.create_invoice())
self.receive_buttons = buttons = QHBoxLayout()
buttons.addStretch(1)
buttons.addWidget(self.clear_invoice_button)
buttons.addWidget(self.create_invoice_button)
grid.addLayout(buttons, 4, 0, 1, -1)
self.receive_address_e = ButtonsTextEdit()
self.receive_address_help_text = WWLabel('')
vbox = QVBoxLayout()
vbox.addWidget(self.receive_address_help_text)
self.receive_address_help = QWidget()
self.receive_address_help.setVisible(False)
self.receive_address_help.setLayout(vbox)
self.receive_URI_e = ButtonsTextEdit()
self.receive_URI_help = WWLabel('')
self.receive_lightning_e = ButtonsTextEdit()
self.receive_lightning_help_text = WWLabel('')
self.receive_rebalance_button = QPushButton('Rebalance')
self.receive_rebalance_button.suggestion = None
def on_receive_rebalance():
if self.receive_rebalance_button.suggestion:
chan1, chan2, delta = self.receive_rebalance_button.suggestion
self.rebalance_dialog(chan1, chan2, amount_sat=delta)
self.receive_rebalance_button.clicked.connect(on_receive_rebalance)
self.receive_swap_button = QPushButton('Swap')
self.receive_swap_button.suggestion = None
def on_receive_swap():
if self.receive_swap_button.suggestion:
chan, swap_recv_amount_sat = self.receive_swap_button.suggestion
self.run_swap_dialog(is_reverse=True, recv_amount_sat=swap_recv_amount_sat, channels=[chan])
self.receive_swap_button.clicked.connect(on_receive_swap)
buttons = QHBoxLayout()
buttons.addWidget(self.receive_rebalance_button)
buttons.addWidget(self.receive_swap_button)
vbox = QVBoxLayout()
vbox.addWidget(self.receive_lightning_help_text)
vbox.addLayout(buttons)
self.receive_lightning_help = QWidget()
self.receive_lightning_help.setVisible(False)
self.receive_lightning_help.setLayout(vbox)
self.receive_address_qr = QRCodeWidget()
self.receive_URI_qr = QRCodeWidget()
self.receive_lightning_qr = QRCodeWidget()
for e in [self.receive_address_e, self.receive_URI_e, self.receive_lightning_e]:
e.setFont(QFont(MONOSPACE_FONT))
e.addCopyButton()
e.setReadOnly(True)
self.receive_lightning_e.textChanged.connect(self.update_receive_widgets)
self.receive_address_widget = ReceiveTabWidget(self,
self.receive_address_e, self.receive_address_qr, self.receive_address_help)
self.receive_URI_widget = ReceiveTabWidget(self,
self.receive_URI_e, self.receive_URI_qr, self.receive_URI_help)
self.receive_lightning_widget = ReceiveTabWidget(self,
self.receive_lightning_e, self.receive_lightning_qr, self.receive_lightning_help)
from .util import VTabWidget
self.receive_tabs = VTabWidget()
self.receive_tabs.setMinimumHeight(ReceiveTabWidget.min_size.height() + 4) # for margins
self.receive_tabs.addTab(self.receive_URI_widget, read_QIcon("link.png"), _('URI'))
self.receive_tabs.addTab(self.receive_address_widget, read_QIcon("bitcoin.png"), _('Address'))
self.receive_tabs.addTab(self.receive_lightning_widget, read_QIcon("lightning.png"), _('Lightning'))
self.receive_tabs.currentChanged.connect(self.update_receive_qr_window)
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 = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
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)
# layout
vbox_g = QVBoxLayout()
vbox_g.addLayout(grid)
vbox_g.addStretch()
hbox = QHBoxLayout()
hbox.addLayout(vbox_g)
hbox.addStretch()
hbox.addWidget(self.receive_tabs)
w = QWidget()
w.searchable_list = self.request_list
vbox = QVBoxLayout(w)
vbox.addLayout(hbox)
vbox.addStretch()
vbox.addWidget(self.receive_requests_label)
vbox.addWidget(self.request_list)
vbox.setStretchFactor(hbox, 40)
vbox.setStretchFactor(self.request_list, 60)
self.request_list.update() # after parented and put into a layout, can update without flickering
return w
def update_current_request(self):
key = self.request_list.get_current_key()
req = self.wallet.get_request(key) if key else None
if req is None:
self.receive_URI_e.setText('')
self.receive_lightning_e.setText('')
self.receive_address_e.setText('')
return
addr = req.get_address() or ''
amount_sat = req.get_amount_sat() or 0
address_help = '' if addr else _('Amount too small to be received onchain')
URI_help = ''
lnaddr = req.lightning_invoice
bip21_lightning = lnaddr if self.config.get('bip21_lightning', False) else None
URI = req.get_bip21_URI(lightning=bip21_lightning)
lightning_online = self.wallet.lnworker and self.wallet.lnworker.num_peers() > 0
can_receive_lightning = self.wallet.lnworker and amount_sat <= self.wallet.lnworker.num_sats_can_receive()
has_expired = self.wallet.get_request_status(key) == PR_EXPIRED
if has_expired:
URI_help = ln_help = address_help = _('This request has expired')
URI = lnaddr = address = ''
can_rebalance = False
can_swap = False
elif lnaddr is None:
ln_help = _('This request does not have a Lightning invoice.')
lnaddr = ''
can_rebalance = False
can_swap = False
elif not lightning_online:
ln_help = _('You must be online to receive Lightning payments.')
lnaddr = ''
can_rebalance = False
can_swap = False
elif not can_receive_lightning:
self.receive_rebalance_button.suggestion = self.wallet.lnworker.suggest_rebalance_to_receive(amount_sat)
self.receive_swap_button.suggestion = self.wallet.lnworker.suggest_swap_to_receive(amount_sat)
can_rebalance = bool(self.receive_rebalance_button.suggestion)
can_swap = bool(self.receive_swap_button.suggestion)
lnaddr = ''
ln_help = _('You do not have the capacity to receive that amount with Lightning.')
if can_rebalance:
ln_help += '\n\n' + _('You may have that capacity if you rebalance your channels.')
elif can_swap:
ln_help += '\n\n' + _('You may have that capacity if you swap some of your funds.')
else:
ln_help = ''
can_rebalance = False
can_swap = False
self.receive_rebalance_button.setVisible(can_rebalance)
self.receive_swap_button.setVisible(can_swap)
self.receive_rebalance_button.setEnabled(can_rebalance and self.num_tasks() == 0)
self.receive_swap_button.setEnabled(can_swap and self.num_tasks() == 0)
icon_name = "lightning.png" if lnaddr 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.update_receive_address_styling()
self.receive_address_qr.setData(addr)
self.receive_address_help_text.setText(address_help)
self.receive_URI_e.setText(URI)
self.receive_URI_qr.setData(URI)
self.receive_URI_help.setText(URI_help)
self.receive_lightning_e.setText(lnaddr) # TODO maybe prepend "lightning:" ??
self.receive_lightning_help_text.setText(ln_help)
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_URI_qr.data
elif i == 1:
data = self.receive_address_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)
self.request_list.delete_item(key)
self.clear_receive_tab()
def sign_payment_request(self, addr):
alias = self.config.get('alias')
if alias and self.alias_info:
alias_addr, alias_name, validated = self.alias_info
if alias_addr:
if self.wallet.is_mine(alias_addr):
msg = _('This payment request will be signed.') + '\n' + _('Please enter your password')
password = None
if self.wallet.has_keystore_encryption():
password = self.password_dialog(msg)
if not password:
return
try:
self.wallet.sign_payment_request(addr, alias, alias_addr, password)
except Exception as e:
self.show_error(repr(e))
return
else:
return
def create_invoice(self):
amount_sat = self.receive_amount_e.get_amount()
message = self.receive_message_e.text()
expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
if amount_sat and amount_sat < self.wallet.dust_threshold():
address = None
if not self.wallet.has_lightning():
return
else:
address = self.get_bitcoin_address_for_request(amount_sat)
if not address:
return
self.address_list.update()
# generate even if we cannot receive
lightning = self.wallet.has_lightning()
try:
key = self.wallet.create_request(amount_sat, message, expiry, address, lightning=lightning)
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()
self.request_list.set_current_key(key)
# clear request fields
self.receive_amount_e.setText('')
self.receive_message_e.setText('')
# copy to clipboard
r = self.wallet.get_request(key)
content = r.lightning_invoice if r.is_lightning() else r.get_address()
title = _('Invoice') if r.is_lightning() else _('Address')
self.do_copy(content, title=title)
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
msg = [
_('No more addresses in your wallet.'), ' ',
_('You are using a non-deterministic wallet, which cannot create new addresses.'), ' ',
_('If you want to create new addresses, use a deterministic wallet instead.'), '\n\n',
_('Creating a new payment request will reuse one of your addresses and overwrite an existing request. Continue anyway?'),
]
if not self.question(''.join(msg)):
return
addr = self.wallet.get_receiving_address()
else: # deterministic wallet
if not self.question(_("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?")):
return
addr = self.wallet.create_new_address(False)
return addr
from .receive_tab import ReceiveTab
return ReceiveTab(self)
def do_copy(self, content: str, *, title: str = None) -> None:
self.app.clipboard().setText(content)
@ -1464,17 +1084,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
tooltip_text = _("{} copied to clipboard").format(title)
QToolTip.showText(QCursor.pos(), tooltip_text, self)
def clear_receive_tab(self):
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):
from . import qrwindow
if not self.qr_window:
@ -1495,16 +1104,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def show_receive_tab(self):
self.tabs.setCurrentIndex(self.tabs.indexOf(self.receive_tab))
def update_receive_address_styling(self):
addr = str(self.receive_address_e.text())
if is_address(addr) and self.wallet.adb.is_used(addr):
self.receive_address_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
self.receive_address_e.setToolTip(_("This address has already been used. "
"For better privacy, do not reuse it for new payments."))
else:
self.receive_address_e.setStyleSheet("")
self.receive_address_e.setToolTip("")
def create_send_tab(self):
from .send_tab import SendTab
return SendTab(self)
@ -1546,11 +1145,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
msg += ' ' + self.format_amount_and_units(amount)
msg += '\n' + req.get_message()
self.notify(msg)
self.request_list.delete_item(key)
self.receive_tabs.setVisible(False)
self.receive_tab.request_list.delete_item(key)
self.receive_tab.receive_tabs.setVisible(False)
self.need_update.set()
else:
self.request_list.refresh_item(key)
self.receive_tab.request_list.refresh_item(key)
@qt_event_listener
def on_event_invoice_status(self, wallet, key):
@ -1770,7 +1369,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.show_error(str(e))
else:
self.need_update.set() # history, addresses, coins
self.clear_receive_tab()
self.receive_tab.clear_receive_tab()
def payto_contacts(self, labels):
self.send_tab.payto_contacts(labels)
@ -2723,7 +2322,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
export_meta_gui(self, _('invoices'), self.wallet.export_invoices)
def import_requests(self):
import_meta_gui(self, _('requests'), self.wallet.import_requests, self.request_list.update)
import_meta_gui(self, _('requests'), self.wallet.import_requests, self.receive_tab.request_list.update)
def export_requests(self):
export_meta_gui(self, _('requests'), self.wallet.export_requests)
@ -2850,7 +2449,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self._do_import(title, header_layout, lambda x: self.wallet.import_private_keys(x, password))
def refresh_amount_edits(self):
edits = self.send_tab.amount_e, self.receive_amount_e
edits = self.send_tab.amount_e, self.receive_tab.receive_amount_e
amounts = [edit.get_amount() for edit in edits]
for edit, amount in zip(edits, amounts):
edit.setAmount(amount)
@ -2858,7 +2457,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def update_fiat(self):
b = self.fx and self.fx.is_enabled()
self.send_tab.fiat_send_e.setVisible(b)
self.fiat_receive_e.setVisible(b)
self.receive_tab.fiat_receive_e.setVisible(b)
self.history_model.refresh('update_fiat')
self.history_list.update()
self.address_list.refresh_headers()

11
electrum/gui/qt/rebalance_dialog.py

@ -1,14 +1,21 @@
from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton
from electrum.i18n import _
from electrum.lnchannel import Channel
from .util import WindowModalDialog, Buttons, OkButton, CancelButton, WWLabel
from .amountedit import BTCAmountEdit
if TYPE_CHECKING:
from .main_window import ElectrumWindow
class RebalanceDialog(WindowModalDialog):
def __init__(self, window, chan1, chan2, amount_sat):
def __init__(self, window: 'ElectrumWindow', chan1: Channel, chan2: Channel, amount_sat):
WindowModalDialog.__init__(self, window, _("Rebalance channels"))
self.window = window
self.wallet = window.wallet
@ -66,4 +73,4 @@ class RebalanceDialog(WindowModalDialog):
amount_msat = self.amount_e.get_amount() * 1000
coro = self.wallet.lnworker.rebalance_channels(self.chan1, self.chan2, amount_msat=amount_msat)
self.window.run_coroutine_from_thread(coro, _('Rebalancing channels'))
self.window.update_current_request() # this will gray out the button
self.window.receive_tab.update_current_request() # this will gray out the button

422
electrum/gui/qt/receive_tab.py

@ -0,0 +1,422 @@
# Copyright (C) 2022 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
from typing import Optional, TYPE_CHECKING
from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import (QComboBox, QLabel, QVBoxLayout, QGridLayout, QLineEdit,
QHBoxLayout, QPushButton, QWidget, QSizePolicy)
from electrum.bitcoin import is_address
from electrum.i18n import _
from electrum.util import InvoiceError
from electrum.invoices import PR_DEFAULT_EXPIRATION_WHEN_CREATING
from electrum.invoices import PR_EXPIRED, pr_expiration_values
from electrum.logging import Logger
from .amountedit import AmountEdit, BTCAmountEdit, SizedFreezableLineEdit
from .qrcodewidget import QRCodeWidget
from .util import read_QIcon, ColorScheme, HelpLabel, WWLabel, MessageBoxMixin, MONOSPACE_FONT
from .util import ButtonsTextEdit
if TYPE_CHECKING:
from . import ElectrumGui
from .main_window import ElectrumWindow
class ReceiveTab(QWidget, MessageBoxMixin, Logger):
def __init__(self, window: 'ElectrumWindow'):
QWidget.__init__(self, window)
Logger.__init__(self)
self.window = window
self.wallet = window.wallet
self.fx = window.fx
self.config = window.config
# A 4-column grid layout. All the stretch is in the last column.
# The exchange rate plugin adds a fiat widget in column 2
self.receive_grid = grid = QGridLayout()
grid.setSpacing(8)
grid.setColumnStretch(3, 1)
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_amount_e = BTCAmountEdit(self.window.get_decimal_point)
grid.addWidget(QLabel(_('Requested amount')), 1, 0)
grid.addWidget(self.receive_amount_e, 1, 1)
self.fiat_receive_e = AmountEdit(self.fx.get_currency if self.fx else '')
if not self.fx or not self.fx.is_enabled():
self.fiat_receive_e.setVisible(False)
grid.addWidget(self.fiat_receive_e, 1, 2, Qt.AlignLeft)
self.window.connect_fields(self.receive_amount_e, self.fiat_receive_e)
self.expires_combo = QComboBox()
evl = sorted(pr_expiration_values.items())
evl_keys = [i[0] for i in evl]
evl_values = [i[1] for i in evl]
default_expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
try:
i = evl_keys.index(default_expiry)
except ValueError:
i = 0
self.expires_combo.addItems(evl_values)
self.expires_combo.setCurrentIndex(i)
def on_expiry(i):
self.config.set_key('request_expiry', evl_keys[i])
self.expires_combo.currentIndexChanged.connect(on_expiry)
msg = ''.join([
_('Expiration date of your request.'), ' ',
_('This information is seen by the recipient if you send them a signed payment request.'),
'\n\n',
_('For on-chain requests, the address gets reserved until expiration. After that, it might get reused.'), ' ',
_('The bitcoin address never expires and will always be part of this electrum wallet.'), ' ',
_('You can reuse a bitcoin address any number of times but it is not good for your privacy.'),
'\n\n',
_('For Lightning requests, payments will not be accepted after the expiration.'),
])
grid.addWidget(HelpLabel(_('Expires after') + ' (?)', msg), 2, 0)
grid.addWidget(self.expires_combo, 2, 1)
self.expires_label = QLineEdit('')
self.expires_label.setReadOnly(1)
self.expires_label.setFocusPolicy(Qt.NoFocus)
self.expires_label.hide()
grid.addWidget(self.expires_label, 2, 1)
self.clear_invoice_button = QPushButton(_('Clear'))
self.clear_invoice_button.clicked.connect(self.clear_receive_tab)
self.create_invoice_button = QPushButton(_('Create Request'))
self.create_invoice_button.clicked.connect(lambda: self.create_invoice())
self.receive_buttons = buttons = QHBoxLayout()
buttons.addStretch(1)
buttons.addWidget(self.clear_invoice_button)
buttons.addWidget(self.create_invoice_button)
grid.addLayout(buttons, 4, 0, 1, -1)
self.receive_address_e = ButtonsTextEdit()
self.receive_address_help_text = WWLabel('')
vbox = QVBoxLayout()
vbox.addWidget(self.receive_address_help_text)
self.receive_address_help = QWidget()
self.receive_address_help.setVisible(False)
self.receive_address_help.setLayout(vbox)
self.receive_URI_e = ButtonsTextEdit()
self.receive_URI_help = WWLabel('')
self.receive_lightning_e = ButtonsTextEdit()
self.receive_lightning_help_text = WWLabel('')
self.receive_rebalance_button = QPushButton('Rebalance')
self.receive_rebalance_button.suggestion = None
def on_receive_rebalance():
if self.receive_rebalance_button.suggestion:
chan1, chan2, delta = self.receive_rebalance_button.suggestion
self.window.rebalance_dialog(chan1, chan2, amount_sat=delta)
self.receive_rebalance_button.clicked.connect(on_receive_rebalance)
self.receive_swap_button = QPushButton('Swap')
self.receive_swap_button.suggestion = None
def on_receive_swap():
if self.receive_swap_button.suggestion:
chan, swap_recv_amount_sat = self.receive_swap_button.suggestion
self.window.run_swap_dialog(is_reverse=True, recv_amount_sat=swap_recv_amount_sat, channels=[chan])
self.receive_swap_button.clicked.connect(on_receive_swap)
buttons = QHBoxLayout()
buttons.addWidget(self.receive_rebalance_button)
buttons.addWidget(self.receive_swap_button)
vbox = QVBoxLayout()
vbox.addWidget(self.receive_lightning_help_text)
vbox.addLayout(buttons)
self.receive_lightning_help = QWidget()
self.receive_lightning_help.setVisible(False)
self.receive_lightning_help.setLayout(vbox)
self.receive_address_qr = QRCodeWidget()
self.receive_URI_qr = QRCodeWidget()
self.receive_lightning_qr = QRCodeWidget()
for e in [self.receive_address_e, self.receive_URI_e, self.receive_lightning_e]:
e.setFont(QFont(MONOSPACE_FONT))
e.addCopyButton()
e.setReadOnly(True)
self.receive_lightning_e.textChanged.connect(self.update_receive_widgets)
self.receive_address_widget = ReceiveTabWidget(self,
self.receive_address_e, self.receive_address_qr, self.receive_address_help)
self.receive_URI_widget = ReceiveTabWidget(self,
self.receive_URI_e, self.receive_URI_qr, self.receive_URI_help)
self.receive_lightning_widget = ReceiveTabWidget(self,
self.receive_lightning_e, self.receive_lightning_qr, self.receive_lightning_help)
from .util import VTabWidget
self.receive_tabs = VTabWidget()
self.receive_tabs.setMinimumHeight(ReceiveTabWidget.min_size.height() + 4) # for margins
self.receive_tabs.addTab(self.receive_URI_widget, read_QIcon("link.png"), _('URI'))
self.receive_tabs.addTab(self.receive_address_widget, read_QIcon("bitcoin.png"), _('Address'))
self.receive_tabs.addTab(self.receive_lightning_widget, read_QIcon("lightning.png"), _('Lightning'))
self.receive_tabs.currentChanged.connect(self.update_receive_qr_window)
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 = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
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)
# layout
vbox_g = QVBoxLayout()
vbox_g.addLayout(grid)
vbox_g.addStretch()
hbox = QHBoxLayout()
hbox.addLayout(vbox_g)
hbox.addStretch()
hbox.addWidget(self.receive_tabs)
self.searchable_list = self.request_list
vbox = QVBoxLayout(self)
vbox.addLayout(hbox)
vbox.addStretch()
vbox.addWidget(self.receive_requests_label)
vbox.addWidget(self.request_list)
vbox.setStretchFactor(hbox, 40)
vbox.setStretchFactor(self.request_list, 60)
self.request_list.update() # after parented and put into a layout, can update without flickering
def toggle_receive_qr(self, e):
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_URI_widget.update_visibility(b)
self.receive_address_widget.update_visibility(b)
self.receive_lightning_widget.update_visibility(b)
def update_current_request(self):
key = self.request_list.get_current_key()
req = self.wallet.get_request(key) if key else None
if req is None:
self.receive_URI_e.setText('')
self.receive_lightning_e.setText('')
self.receive_address_e.setText('')
return
addr = req.get_address() or ''
amount_sat = req.get_amount_sat() or 0
address_help = '' if addr else _('Amount too small to be received onchain')
URI_help = ''
lnaddr = req.lightning_invoice
bip21_lightning = lnaddr if self.config.get('bip21_lightning', False) else None
URI = req.get_bip21_URI(lightning=bip21_lightning)
lightning_online = self.wallet.lnworker and self.wallet.lnworker.num_peers() > 0
can_receive_lightning = self.wallet.lnworker and amount_sat <= self.wallet.lnworker.num_sats_can_receive()
has_expired = self.wallet.get_request_status(key) == PR_EXPIRED
if has_expired:
URI_help = ln_help = address_help = _('This request has expired')
URI = lnaddr = address = ''
can_rebalance = False
can_swap = False
elif lnaddr is None:
ln_help = _('This request does not have a Lightning invoice.')
lnaddr = ''
can_rebalance = False
can_swap = False
elif not lightning_online:
ln_help = _('You must be online to receive Lightning payments.')
lnaddr = ''
can_rebalance = False
can_swap = False
elif not can_receive_lightning:
self.receive_rebalance_button.suggestion = self.wallet.lnworker.suggest_rebalance_to_receive(amount_sat)
self.receive_swap_button.suggestion = self.wallet.lnworker.suggest_swap_to_receive(amount_sat)
can_rebalance = bool(self.receive_rebalance_button.suggestion)
can_swap = bool(self.receive_swap_button.suggestion)
lnaddr = ''
ln_help = _('You do not have the capacity to receive that amount with Lightning.')
if can_rebalance:
ln_help += '\n\n' + _('You may have that capacity if you rebalance your channels.')
elif can_swap:
ln_help += '\n\n' + _('You may have that capacity if you swap some of your funds.')
else:
ln_help = ''
can_rebalance = False
can_swap = False
self.receive_rebalance_button.setVisible(can_rebalance)
self.receive_swap_button.setVisible(can_swap)
self.receive_rebalance_button.setEnabled(can_rebalance and self.window.num_tasks() == 0)
self.receive_swap_button.setEnabled(can_swap and self.window.num_tasks() == 0)
icon_name = "lightning.png" if lnaddr 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.update_receive_address_styling()
self.receive_address_qr.setData(addr)
self.receive_address_help_text.setText(address_help)
self.receive_URI_e.setText(URI)
self.receive_URI_qr.setData(URI)
self.receive_URI_help.setText(URI_help)
self.receive_lightning_e.setText(lnaddr) # TODO maybe prepend "lightning:" ??
self.receive_lightning_help_text.setText(ln_help)
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.window.qr_window and self.window.qr_window.isVisible():
i = self.receive_tabs.currentIndex()
if i == 0:
data = self.receive_URI_qr.data
elif i == 1:
data = self.receive_address_qr.data
else:
data = self.receive_lightning_qr.data
self.window.qr_window.qrw.setData(data)
def sign_payment_request(self, addr):
alias = self.config.get('alias')
if alias and self.wallet.contacts.alias_info:
alias_addr, alias_name, validated = self.wallet.contacts.alias_info
if alias_addr:
if self.wallet.is_mine(alias_addr):
msg = _('This payment request will be signed.') + '\n' + _('Please enter your password')
password = None
if self.wallet.has_keystore_encryption():
password = self.window.password_dialog(msg)
if not password:
return
try:
self.wallet.sign_payment_request(addr, alias, alias_addr, password)
except Exception as e:
self.show_error(repr(e))
return
else:
return
def create_invoice(self):
amount_sat = self.receive_amount_e.get_amount()
message = self.receive_message_e.text()
expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
if amount_sat and amount_sat < self.wallet.dust_threshold():
address = None
if not self.wallet.has_lightning():
return
else:
address = self.get_bitcoin_address_for_request(amount_sat)
if not address:
return
self.window.address_list.update()
# generate even if we cannot receive
lightning = self.wallet.has_lightning()
try:
key = self.wallet.create_request(amount_sat, message, expiry, address, lightning=lightning)
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.window.address_list.refresh_all()
self.request_list.update()
self.request_list.set_current_key(key)
# clear request fields
self.receive_amount_e.setText('')
self.receive_message_e.setText('')
# copy to clipboard
r = self.wallet.get_request(key)
content = r.lightning_invoice if r.is_lightning() else r.get_address()
title = _('Invoice') if r.is_lightning() else _('Address')
self.window.do_copy(content, title=title)
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
msg = [
_('No more addresses in your wallet.'), ' ',
_('You are using a non-deterministic wallet, which cannot create new addresses.'), ' ',
_('If you want to create new addresses, use a deterministic wallet instead.'), '\n\n',
_('Creating a new payment request will reuse one of your addresses and overwrite an existing request. Continue anyway?'),
]
if not self.question(''.join(msg)):
return
addr = self.wallet.get_receiving_address()
else: # deterministic wallet
if not self.question(_("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?")):
return
addr = self.wallet.create_new_address(False)
return addr
def clear_receive_tab(self):
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 update_receive_address_styling(self):
addr = str(self.receive_address_e.text())
if is_address(addr) and self.wallet.adb.is_used(addr):
self.receive_address_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
self.receive_address_e.setToolTip(_("This address has already been used. "
"For better privacy, do not reuse it for new payments."))
else:
self.receive_address_e.setStyleSheet("")
self.receive_address_e.setToolTip("")
class ReceiveTabWidget(QWidget):
min_size = QSize(200, 200)
def __init__(self, receive_tab: 'ReceiveTab', textedit, qr, help_widget):
self.textedit = textedit
self.qr = qr
self.help_widget = help_widget
QWidget.__init__(self)
for w in [textedit, qr, help_widget]:
w.setMinimumSize(self.min_size)
for w in [textedit, qr]:
w.mousePressEvent = receive_tab.toggle_receive_qr
tooltip = _('Click to switch between text and QR code view')
w.setToolTip(tooltip)
textedit.setFocusPolicy(Qt.NoFocus)
hbox = QHBoxLayout()
hbox.setContentsMargins(0, 0, 0, 0)
hbox.addWidget(textedit)
hbox.addWidget(help_widget)
hbox.addWidget(qr)
self.setLayout(hbox)
def update_visibility(self, is_qr):
if str(self.textedit.text()):
self.help_widget.setVisible(False)
self.textedit.setVisible(not is_qr)
self.qr.setVisible(is_qr)
else:
self.help_widget.setVisible(True)
self.textedit.setVisible(False)
self.qr.setVisible(False)

29
electrum/gui/qt/request_list.py

@ -39,6 +39,7 @@ from .util import MyTreeView, pr_icons, read_QIcon, webopen, MySortModel
if TYPE_CHECKING:
from .main_window import ElectrumWindow
from .receive_tab import ReceiveTab
ROLE_REQUEST_TYPE = Qt.UserRole
@ -63,10 +64,12 @@ class RequestList(MyTreeView):
}
filter_columns = [Columns.DATE, Columns.DESCRIPTION, Columns.AMOUNT]
def __init__(self, parent: 'ElectrumWindow'):
super().__init__(parent, self.create_menu,
def __init__(self, receive_tab: 'ReceiveTab'):
window = receive_tab.window
super().__init__(window, self.create_menu,
stretch_column=self.Columns.DESCRIPTION)
self.wallet = self.parent.wallet
self.wallet = window.wallet
self.receive_tab = receive_tab
self.std_model = QStandardItemModel(self)
self.proxy = MySortModel(self, sort_role=ROLE_SORT_ORDER)
self.proxy.setSourceModel(self.std_model)
@ -88,7 +91,7 @@ class RequestList(MyTreeView):
def item_changed(self, idx: Optional[QModelIndex]):
if idx is None:
self.parent.update_current_request()
self.receive_tab.update_current_request()
return
if not idx.isValid():
return
@ -98,7 +101,7 @@ class RequestList(MyTreeView):
req = self.wallet.get_request(key)
if req is None:
self.update()
self.parent.update_current_request()
self.receive_tab.update_current_request()
def clearSelection(self):
super().clearSelection()
@ -111,7 +114,7 @@ class RequestList(MyTreeView):
if request is None:
return
status_item = model.item(row, self.Columns.STATUS)
status = self.parent.wallet.get_request_status(key)
status = self.wallet.get_request_status(key)
status_str = request.get_status_str(status)
status_item.setText(status_str)
status_item.setIcon(read_QIcon(pr_icons.get(status)))
@ -124,7 +127,7 @@ class RequestList(MyTreeView):
self.update_headers(self.__class__.headers)
for req in self.wallet.get_unpaid_requests():
key = self.wallet.get_key_for_receive_request(req)
status = self.parent.wallet.get_request_status(key)
status = self.wallet.get_request_status(key)
status_str = req.get_status_str(status)
timestamp = req.get_time()
amount = req.get_amount_sat()
@ -150,7 +153,7 @@ class RequestList(MyTreeView):
def hide_if_empty(self):
b = self.std_model.rowCount() > 0
self.setVisible(b)
self.parent.receive_requests_label.setVisible(b)
self.receive_tab.receive_requests_label.setVisible(b)
if not b:
# list got hidden, so selected item should also be cleared:
self.item_changed(None)
@ -160,7 +163,7 @@ class RequestList(MyTreeView):
if len(items)>1:
keys = [item.data(ROLE_KEY) for item in items]
menu = QMenu(self)
menu.addAction(_("Delete requests"), lambda: self.parent.delete_requests(keys))
menu.addAction(_("Delete requests"), lambda: self.delete_requests(keys))
menu.exec_(self.viewport().mapToGlobal(position))
return
idx = self.indexAt(position)
@ -183,6 +186,12 @@ class RequestList(MyTreeView):
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]))
menu.addAction(_("Delete"), lambda: self.delete_requests([key]))
run_hook('receive_list_menu', self.parent, menu, key)
menu.exec_(self.viewport().mapToGlobal(position))
def delete_requests(self, keys):
for key in keys:
self.wallet.delete_request(key)
self.delete_item(key)
self.receive_tab.clear_receive_tab()

2
electrum/gui/qt/send_tab.py

@ -115,6 +115,8 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
self.amount_e.frozen.connect(
lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))
self.window.connect_fields(self.amount_e, self.fiat_send_e)
self.max_button = EnterButton(_("Max"), self.spend_max)
self.max_button.setFixedWidth(100)
self.max_button.setCheckable(True)

2
electrum/plugins/hw_wallet/qt.py

@ -280,7 +280,7 @@ class QtPluginBase(object):
keystore: 'Hardware_KeyStore',
main_window: ElectrumWindow):
plugin = keystore.plugin
receive_address_e = main_window.receive_address_e
receive_address_e = main_window.receive_tab.receive_address_e
def show_address():
addr = str(receive_address_e.text())

Loading…
Cancel
Save