refactor qt.util.ChoiceWidget: introduce ChoiceItem
This commit is contained in:
@@ -55,7 +55,7 @@ from electrum.plugin import run_hook
|
||||
from electrum.i18n import _
|
||||
from electrum.util import (format_time, UserCancelled, profiler, bfh, InvalidPassword,
|
||||
UserFacingException, get_new_wallet_name, send_exception_to_crash_reporter,
|
||||
AddTransactionException, os_chmod, UI_UNIT_NAME_TXSIZE_VBYTES)
|
||||
AddTransactionException, os_chmod, UI_UNIT_NAME_TXSIZE_VBYTES, ChoiceItem)
|
||||
from electrum.bip21 import BITCOIN_BIP21_URI_SCHEME
|
||||
from electrum.payment_identifier import PaymentIdentifier
|
||||
from electrum.invoices import PR_PAID, Invoice
|
||||
@@ -1326,7 +1326,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
return (f"pubkey={x['pubkey'][0:10]}, "
|
||||
f"fee={x['percentage_fee']}% + {x['mining_fee']} sats, "
|
||||
f"last_seen: {last_seen}")
|
||||
server_keys = [(x['pubkey'], descr(x)) for x in recent_offers]
|
||||
server_keys = [ChoiceItem(key=x['pubkey'], label=descr(x)) for x in recent_offers]
|
||||
msg = '\n'.join([
|
||||
_("Please choose a server from this list."),
|
||||
_("Note that fees may be updated frequently.")
|
||||
|
||||
@@ -10,7 +10,7 @@ from PyQt6.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QTextEdit,
|
||||
QHBoxLayout, QPushButton, QWidget, QSizePolicy, QFrame)
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import InvoiceError
|
||||
from electrum.util import InvoiceError, ChoiceItem
|
||||
from electrum.invoices import pr_expiration_values
|
||||
from electrum.logging import Logger
|
||||
|
||||
@@ -195,7 +195,8 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
|
||||
_('For Lightning requests, payments will not be accepted after the expiration.'),
|
||||
])
|
||||
expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS
|
||||
choices = list(pr_expiration_values().items())
|
||||
choices = [ChoiceItem(key=exptime, label=label)
|
||||
for (exptime, label) in pr_expiration_values().items()]
|
||||
v = self.window.query_choice(msg, choices, title=_('Expiry'), default_choice=expiry)
|
||||
if v is None:
|
||||
return
|
||||
|
||||
@@ -35,9 +35,12 @@ from electrum.i18n import _
|
||||
from electrum.mnemonic import Mnemonic, calc_seed_type, is_any_2fa_seed_type
|
||||
from electrum import old_mnemonic
|
||||
from electrum import slip39
|
||||
from electrum.util import ChoiceItem
|
||||
|
||||
from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path,
|
||||
EnterButton, CloseButton, WindowModalDialog, ColorScheme, font_height, ChoiceWidget)
|
||||
from .util import (
|
||||
Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path, EnterButton,
|
||||
CloseButton, WindowModalDialog, ColorScheme, font_height, ChoiceWidget,
|
||||
)
|
||||
from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
|
||||
from .completion_text_edit import CompletionTextEdit
|
||||
|
||||
@@ -98,15 +101,15 @@ class SeedWidget(QWidget):
|
||||
|
||||
if options:
|
||||
self.seed_types = [
|
||||
(value, title) for value, title in (
|
||||
ChoiceItem(key=stype, label=label) for stype, label in (
|
||||
('electrum', _('Electrum')),
|
||||
('bip39', _('BIP39 seed')),
|
||||
('slip39', _('SLIP39 seed')),
|
||||
)
|
||||
if value in self.options
|
||||
if stype in self.options
|
||||
]
|
||||
assert len(self.seed_types)
|
||||
self.seed_type = self.seed_types[0][0]
|
||||
self.seed_type = self.seed_types[0].key
|
||||
else:
|
||||
self.seed_type = 'electrum'
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from electrum.i18n import _
|
||||
from electrum.logging import Logger
|
||||
from electrum.bitcoin import DummyAddress
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, parse_max_spend, UserCancelled
|
||||
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, parse_max_spend, UserCancelled, ChoiceItem
|
||||
from electrum.invoices import PR_PAID, Invoice, PR_BROADCASTING, PR_BROADCAST
|
||||
from electrum.transaction import Transaction, PartialTxInput, PartialTxOutput
|
||||
from electrum.network import TxBroadcastError, BestEffortRequestFailed
|
||||
@@ -672,25 +672,25 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
can_pay_with_swap = lnworker.suggest_swap_to_send(amount_sat, coins=coins)
|
||||
rebalance_suggestion = lnworker.suggest_rebalance_to_send(amount_sat)
|
||||
can_rebalance = bool(rebalance_suggestion) and self.window.num_tasks() == 0
|
||||
choices = []
|
||||
choices = [] # type: List[ChoiceItem]
|
||||
if can_rebalance:
|
||||
msg = ''.join([
|
||||
_('Rebalance existing channels'), '\n',
|
||||
_('Move funds between your channels in order to increase your sending capacity.')
|
||||
])
|
||||
choices.append(('rebalance', msg))
|
||||
choices.append(ChoiceItem(key='rebalance', label=msg))
|
||||
if can_pay_with_new_channel:
|
||||
msg = ''.join([
|
||||
_('Open a new channel'), '\n',
|
||||
_('You will be able to pay once the channel is open.')
|
||||
])
|
||||
choices.append(('new_channel', msg))
|
||||
choices.append(ChoiceItem(key='new_channel', label=msg))
|
||||
if can_pay_with_swap:
|
||||
msg = ''.join([
|
||||
_('Swap onchain funds for lightning funds'), '\n',
|
||||
_('You will be able to pay once the swap is confirmed.')
|
||||
])
|
||||
choices.append(('swap', msg))
|
||||
choices.append(ChoiceItem(key='swap', label=msg))
|
||||
msg = _('You cannot pay that invoice using Lightning.')
|
||||
if lnworker and lnworker.channels:
|
||||
num_sats_can_send = int(lnworker.num_sats_can_send())
|
||||
|
||||
@@ -21,7 +21,7 @@ from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBo
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import (FileImportFailed, FileExportFailed, resource_path, EventListener, event_listener,
|
||||
get_logger, UserCancelled, UserFacingException)
|
||||
get_logger, UserCancelled, UserFacingException, ChoiceItem)
|
||||
from electrum.invoices import (PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING,
|
||||
PR_UNCONFIRMED, PR_BROADCASTING, PR_BROADCAST)
|
||||
from electrum.qrreader import MissingQrDetectionLib, QrCodeResult
|
||||
@@ -315,12 +315,18 @@ class MessageBoxMixin(object):
|
||||
rich_text=rich_text, checkbox=checkbox
|
||||
)
|
||||
|
||||
def query_choice(self,
|
||||
msg: Optional[str],
|
||||
choices: Sequence[Tuple],
|
||||
title: Optional[str] = None,
|
||||
default_choice: Optional[Any] = None) -> Optional[Any]:
|
||||
# Needed by QtHandler for hardware wallets
|
||||
def query_choice(
|
||||
self,
|
||||
msg: Optional[str],
|
||||
choices: Sequence['ChoiceItem'],
|
||||
*,
|
||||
title: Optional[str] = None,
|
||||
default_choice: Optional[Any] = None,
|
||||
) -> Optional[Any]:
|
||||
"""Returns ChoiceItem.key (for selected item), or None if the user cancels the dialog.
|
||||
|
||||
Needed by QtHandler for hardware wallets.
|
||||
"""
|
||||
if title is None:
|
||||
title = _('Question')
|
||||
dialog = WindowModalDialog(self.top_level_window(), title=title)
|
||||
@@ -506,18 +512,21 @@ def text_dialog(
|
||||
|
||||
|
||||
class ChoiceWidget(QWidget):
|
||||
"""Renders a list of tuples as a radiobuttons group.
|
||||
The first element of each tuple is used as a key.
|
||||
The second element of each tuple is used as user facing string.
|
||||
The remainder of the tuple can be any additional data.
|
||||
"""Renders a list of ChoiceItems as a radiobuttons group.
|
||||
Callers can pre-select an item by key, through the 'selected' parameter.
|
||||
The selected item is made available by index (selected_index),
|
||||
by key (selected_key) and by whole tuple (selected_item).
|
||||
by key (selected_key) and by Choice (selected_item).
|
||||
"""
|
||||
|
||||
itemSelected = pyqtSignal([int], arguments=['index'])
|
||||
|
||||
def __init__(self, *, message: Optional[str] = None, choices: Sequence[Tuple] = None, selected: Optional[Any] = None):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
message: Optional[str] = None,
|
||||
choices: Sequence[ChoiceItem] = None,
|
||||
selected: Optional[Any] = None,
|
||||
):
|
||||
QWidget.__init__(self)
|
||||
vbox = QVBoxLayout()
|
||||
self.setLayout(vbox)
|
||||
@@ -525,11 +534,10 @@ class ChoiceWidget(QWidget):
|
||||
if choices is None:
|
||||
choices = []
|
||||
|
||||
self.selected_index = -1 # int
|
||||
self.selected_item = None # Optional[Tuple]
|
||||
self.selected_key = None # Optional[Any]
|
||||
|
||||
self.choices = choices
|
||||
self.selected_index = -1 # type: int
|
||||
self.selected_item = None # type: Optional[ChoiceItem]
|
||||
self.selected_key = None # type: Optional[Any]
|
||||
self.choices = choices # type: Sequence[ChoiceItem]
|
||||
|
||||
if message and len(message) > 50:
|
||||
vbox.addWidget(WWLabel(message))
|
||||
@@ -540,31 +548,29 @@ class ChoiceWidget(QWidget):
|
||||
gb2.setLayout(vbox2)
|
||||
self.group = group = QButtonGroup()
|
||||
assert isinstance(choices, list)
|
||||
iterator = enumerate(choices)
|
||||
for i, c in iterator:
|
||||
assert isinstance(c, tuple), f"{c=!r}"
|
||||
for i, c in enumerate(choices):
|
||||
assert isinstance(c, ChoiceItem), f"{c=!r}"
|
||||
button = QRadioButton(gb2)
|
||||
button.setText(c[1])
|
||||
button.setText(c.label)
|
||||
vbox2.addWidget(button)
|
||||
group.addButton(button)
|
||||
group.setId(button, i)
|
||||
if (i == 0 and selected is None) or c[0] == selected:
|
||||
if (i == 0 and selected is None) or c.key == selected:
|
||||
self.selected_index = i
|
||||
self.selected_item = c
|
||||
self.selected_key = c[0]
|
||||
self.selected_key = c.key
|
||||
button.setChecked(True)
|
||||
group.buttonClicked.connect(self.on_selected)
|
||||
|
||||
def on_selected(self, button):
|
||||
self.selected_index = self.group.id(button)
|
||||
self.selected_item = self.choices[self.selected_index]
|
||||
self.selected_key = self.choices[self.selected_index][0]
|
||||
self.selected_key = self.choices[self.selected_index].key
|
||||
self.itemSelected.emit(self.selected_index)
|
||||
|
||||
def select(self, key):
|
||||
iterator = enumerate(self.choices)
|
||||
for i, c in iterator:
|
||||
if key == c[0]:
|
||||
for i, c in enumerate(self.choices):
|
||||
if key == c.key:
|
||||
self.group.button(i).click()
|
||||
|
||||
|
||||
|
||||
@@ -12,10 +12,13 @@ from PyQt6.QtWidgets import (QLabel, QVBoxLayout, QGridLayout,
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.i18n import _
|
||||
from electrum.wallet import Multisig_Wallet
|
||||
from electrum.util import ChoiceItem
|
||||
|
||||
from .qrtextedit import ShowQRTextEdit
|
||||
from .util import (read_QIcon, WindowModalDialog, Buttons,
|
||||
WWLabel, CloseButton, HelpButton, font_height, ShowQRLineEdit, ChoiceWidget)
|
||||
from .util import (
|
||||
read_QIcon, WindowModalDialog, Buttons,
|
||||
WWLabel, CloseButton, HelpButton, font_height, ShowQRLineEdit, ChoiceWidget,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .main_window import ElectrumWindow
|
||||
@@ -117,7 +120,8 @@ class WalletInfoDialog(WindowModalDialog):
|
||||
else:
|
||||
return _("keystore") + f' {idx+1}'
|
||||
|
||||
labels = [(idx, label(idx, ks)) for idx, ks in enumerate(wallet.get_keystores())]
|
||||
labels = [ChoiceItem(key=idx, label=label(idx, ks))
|
||||
for idx, ks in enumerate(wallet.get_keystores())]
|
||||
|
||||
keystore_choice = ChoiceWidget(message=_("Select keystore"), choices=labels)
|
||||
keystore_choice.itemSelected.connect(lambda x: select_ks(x))
|
||||
|
||||
@@ -3,7 +3,7 @@ import os
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING, Optional, List, Tuple
|
||||
|
||||
from PyQt6.QtCore import Qt, QTimer, QRect, pyqtSignal
|
||||
from PyQt6.QtGui import QPen, QPainter, QPalette, QPixmap
|
||||
@@ -17,7 +17,7 @@ from electrum.keystore import bip44_derivation, bip39_to_seed, purpose48_derivat
|
||||
from electrum.plugin import run_hook, HardwarePluginLibraryUnavailable
|
||||
from electrum.storage import StorageReadWriteError
|
||||
from electrum.util import WalletFileException, get_new_wallet_name, UserFacingException, InvalidPassword
|
||||
from electrum.util import is_subpath
|
||||
from electrum.util import is_subpath, ChoiceItem
|
||||
from electrum.wallet import wallet_types
|
||||
from .wizard import QEAbstractWizard, WizardComponent
|
||||
from electrum.logging import get_logger, Logger
|
||||
@@ -34,7 +34,7 @@ from electrum.gui.qt.plugins_dialog import PluginsDialog
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum.simple_config import SimpleConfig
|
||||
from electrum.plugin import Plugins
|
||||
from electrum.plugin import Plugins, DeviceInfo
|
||||
from electrum.gui.qt import QElectrumApplication
|
||||
|
||||
WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\n\n' +
|
||||
@@ -399,12 +399,12 @@ class WCWalletType(WalletWizardComponent):
|
||||
WalletWizardComponent.__init__(self, parent, wizard, title=_('Create new wallet'))
|
||||
message = _('What kind of wallet do you want to create?')
|
||||
wallet_kinds = [
|
||||
('standard', _('Standard wallet')),
|
||||
('2fa', _('Wallet with two-factor authentication')),
|
||||
('multisig', _('Multi-signature wallet')),
|
||||
('imported', _('Import Bitcoin addresses or private keys')),
|
||||
ChoiceItem(key='standard', label=_('Standard wallet')),
|
||||
ChoiceItem(key='2fa', label=_('Wallet with two-factor authentication')),
|
||||
ChoiceItem(key='multisig', label=_('Multi-signature wallet')),
|
||||
ChoiceItem(key='imported', label=_('Import Bitcoin addresses or private keys')),
|
||||
]
|
||||
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
|
||||
choices = [c for c in wallet_kinds if c.key in wallet_types]
|
||||
|
||||
self.choice_w = ChoiceWidget(message=message, choices=choices, selected='standard')
|
||||
self.layout().addWidget(self.choice_w)
|
||||
@@ -420,10 +420,10 @@ class WCKeystoreType(WalletWizardComponent):
|
||||
WalletWizardComponent.__init__(self, parent, wizard, title=_('Keystore'))
|
||||
message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')
|
||||
choices = [
|
||||
('createseed', _('Create a new seed')),
|
||||
('haveseed', _('I already have a seed')),
|
||||
('masterkey', _('Use a master key')),
|
||||
('hardware', _('Use a hardware device'))
|
||||
ChoiceItem(key='createseed', label=_('Create a new seed')),
|
||||
ChoiceItem(key='haveseed', label=_('I already have a seed')),
|
||||
ChoiceItem(key='masterkey', label=_('Use a master key')),
|
||||
ChoiceItem(key='hardware', label=_('Use a hardware device')),
|
||||
]
|
||||
|
||||
self.choice_w = ChoiceWidget(message=message, choices=choices)
|
||||
@@ -658,7 +658,7 @@ class WCScriptAndDerivation(WalletWizardComponent, Logger):
|
||||
WalletWizardComponent.__init__(self, parent, wizard, title=_('Script type and Derivation path'))
|
||||
Logger.__init__(self)
|
||||
|
||||
self.choice_w = None
|
||||
self.choice_w = None # type: ChoiceWidget
|
||||
self.derivation_path_edit = None
|
||||
|
||||
self.warn_label = IconLabel(reverse=True, hide_if_empty=True)
|
||||
@@ -675,9 +675,12 @@ class WCScriptAndDerivation(WalletWizardComponent, Logger):
|
||||
if self.wizard_data['wallet_type'] == 'multisig':
|
||||
choices = [
|
||||
# TODO: nicer to refactor 'standard' to 'p2sh', but backend wallet still uses 'standard'
|
||||
('standard', 'legacy multisig (p2sh)', normalize_bip32_derivation("m/45'/0")),
|
||||
('p2wsh-p2sh', 'p2sh-segwit multisig (p2wsh-p2sh)', purpose48_derivation(0, xtype='p2wsh-p2sh')),
|
||||
('p2wsh', 'native segwit multisig (p2wsh)', purpose48_derivation(0, xtype='p2wsh')),
|
||||
ChoiceItem(key='standard', label='legacy multisig (p2sh)',
|
||||
extra_data=normalize_bip32_derivation("m/45'/0")),
|
||||
ChoiceItem(key='p2wsh-p2sh', label='p2sh-segwit multisig (p2wsh-p2sh)',
|
||||
extra_data=purpose48_derivation(0, xtype='p2wsh-p2sh')),
|
||||
ChoiceItem(key='p2wsh', label='native segwit multisig (p2wsh)',
|
||||
extra_data=purpose48_derivation(0, xtype='p2wsh')),
|
||||
]
|
||||
if 'multisig_current_cosigner' in self.wizard_data:
|
||||
# get script type of first cosigner
|
||||
@@ -690,9 +693,12 @@ class WCScriptAndDerivation(WalletWizardComponent, Logger):
|
||||
default_choice = 'p2wpkh'
|
||||
choices = [
|
||||
# TODO: nicer to refactor 'standard' to 'p2pkh', but backend wallet still uses 'standard'
|
||||
('standard', 'legacy (p2pkh)', bip44_derivation(0, bip43_purpose=44)),
|
||||
('p2wpkh-p2sh', 'p2sh-segwit (p2wpkh-p2sh)', bip44_derivation(0, bip43_purpose=49)),
|
||||
('p2wpkh', 'native segwit (p2wpkh)', bip44_derivation(0, bip43_purpose=84)),
|
||||
ChoiceItem(key='standard', label='legacy (p2pkh)',
|
||||
extra_data=bip44_derivation(0, bip43_purpose=44)),
|
||||
ChoiceItem(key='p2wpkh-p2sh', label='p2sh-segwit (p2wpkh-p2sh)',
|
||||
extra_data=bip44_derivation(0, bip43_purpose=49)),
|
||||
ChoiceItem(key='p2wpkh', label='native segwit (p2wpkh)',
|
||||
extra_data=bip44_derivation(0, bip43_purpose=84)),
|
||||
]
|
||||
|
||||
if self.wizard_data['wallet_type'] == 'standard' and not self.wizard_data['keystore_type'] == 'hardware':
|
||||
@@ -722,7 +728,7 @@ class WCScriptAndDerivation(WalletWizardComponent, Logger):
|
||||
self.layout().addWidget(QLabel(_("Or")))
|
||||
|
||||
def on_choice_click(index):
|
||||
self.derivation_path_edit.setText(self.choice_w.selected_item[2])
|
||||
self.derivation_path_edit.setText(self.choice_w.selected_item.extra_data)
|
||||
self.choice_w = ChoiceWidget(message=message1, choices=choices, selected=default_choice)
|
||||
self.choice_w.itemSelected.connect(on_choice_click)
|
||||
|
||||
@@ -768,9 +774,9 @@ class WCCosignerKeystore(WalletWizardComponent):
|
||||
|
||||
message = _('Add a cosigner to your multi-sig wallet')
|
||||
choices = [
|
||||
('masterkey', _('Enter cosigner key')),
|
||||
('haveseed', _('Enter cosigner seed')),
|
||||
('hardware', _('Cosign with hardware device'))
|
||||
ChoiceItem(key='masterkey', label=_('Enter cosigner key')),
|
||||
ChoiceItem(key='haveseed', label=_('Enter cosigner seed')),
|
||||
ChoiceItem(key='hardware', label=_('Cosign with hardware device')),
|
||||
]
|
||||
|
||||
self.choice_w = ChoiceWidget(message=message, choices=choices)
|
||||
@@ -1090,7 +1096,7 @@ class WCChooseHWDevice(WalletWizardComponent, Logger):
|
||||
self.device_list = QWidget()
|
||||
self.device_list_layout = QVBoxLayout()
|
||||
self.device_list.setLayout(self.device_list_layout)
|
||||
self.choice_w = None
|
||||
self.choice_w = None # type: ChoiceWidget
|
||||
|
||||
self.rescan_button = QPushButton(_('Rescan devices'))
|
||||
self.rescan_button.clicked.connect(self.on_rescan)
|
||||
@@ -1132,7 +1138,7 @@ class WCChooseHWDevice(WalletWizardComponent, Logger):
|
||||
self.error_l.setVisible(False)
|
||||
self.device_list.setVisible(True)
|
||||
|
||||
choices = []
|
||||
choices = [] # type: List[ChoiceItem]
|
||||
for name, info in self.devices:
|
||||
state = _("initialized") if info.initialized else _("wiped")
|
||||
label = info.label or _("An unnamed {}").format(name)
|
||||
@@ -1141,7 +1147,7 @@ class WCChooseHWDevice(WalletWizardComponent, Logger):
|
||||
except Exception:
|
||||
transport_str = 'unknown transport'
|
||||
descr = f"{label} [{info.model_name or name}, {state}, {transport_str}]"
|
||||
choices.append(((name, info), descr))
|
||||
choices.append(ChoiceItem(key=(name, info), label=descr))
|
||||
msg = _('Select a device') + ':'
|
||||
|
||||
if self.choice_w:
|
||||
|
||||
Reference in New Issue
Block a user