Browse Source

Merge pull request #9194 from accumulator/factor_out_choicelayout

qt: factor out remaining ChoicesLayout uses
master
ghost43 1 year ago committed by GitHub
parent
commit
f7749d62aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 22
      electrum/gui/qt/main_window.py
  2. 3
      electrum/gui/qt/receive_tab.py
  3. 18
      electrum/gui/qt/seed_dialog.py
  4. 18
      electrum/gui/qt/send_tab.py
  5. 71
      electrum/gui/qt/util.py
  6. 13
      electrum/gui/qt/wallet_info_dialog.py
  7. 18
      electrum/gui/qt/wizard/wallet.py
  8. 4
      electrum/plugins/digitalbitbox/qt.py
  9. 6
      electrum/plugins/hw_wallet/qt.py

22
electrum/gui/qt/main_window.py

@ -34,7 +34,7 @@ import base64
from functools import partial from functools import partial
import queue import queue
import asyncio import asyncio
from typing import Optional, TYPE_CHECKING, Sequence, List, Union, Dict, Set, Mapping from typing import Optional, TYPE_CHECKING, Sequence, Union, Dict, Mapping
import concurrent.futures import concurrent.futures
from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics
@ -45,7 +45,7 @@ from PyQt5.QtWidgets import (QMessageBox, QSystemTrayIcon, QTabWidget,
QHBoxLayout, QPushButton, QScrollArea, QTextEdit, QHBoxLayout, QPushButton, QScrollArea, QTextEdit,
QShortcut, QMainWindow, QInputDialog, QShortcut, QMainWindow, QInputDialog,
QWidget, QSizePolicy, QStatusBar, QToolTip, QWidget, QSizePolicy, QStatusBar, QToolTip,
QMenu, QAction, QStackedWidget, QToolButton) QMenu, QAction, QToolButton)
import electrum import electrum
from electrum.gui import messages from electrum.gui import messages
@ -82,7 +82,7 @@ from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit, ScanShowQRTextEdit
from .transaction_dialog import show_transaction from .transaction_dialog import show_transaction
from .fee_slider import FeeSlider, FeeComboBox from .fee_slider import FeeSlider, FeeComboBox
from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialog, from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialog,
WindowModalDialog, ChoicesLayout, HelpLabel, Buttons, WindowModalDialog, HelpLabel, Buttons,
OkButton, InfoButton, WWLabel, TaskThread, CancelButton, OkButton, InfoButton, WWLabel, TaskThread, CancelButton,
CloseButton, HelpButton, MessageBoxMixin, EnterButton, CloseButton, HelpButton, MessageBoxMixin, EnterButton,
import_meta_gui, export_meta_gui, import_meta_gui, export_meta_gui,
@ -1370,22 +1370,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
else: else:
self.show_message(message) self.show_message(message)
def query_choice(self, msg, choices, title=None, default_choice=None):
# Needed by QtHandler for hardware wallets
if title is None:
title = _('Question')
dialog = WindowModalDialog(self.top_level_window(), title=title)
dialog.setMinimumWidth(400)
clayout = ChoicesLayout(msg, choices, checked_index=default_choice)
vbox = QVBoxLayout(dialog)
vbox.addLayout(clayout.layout())
cancel_button = CancelButton(dialog)
vbox.addLayout(Buttons(cancel_button, OkButton(dialog)))
cancel_button.setFocus()
if not dialog.exec_():
return None
return clayout.selected_index()
def handle_payment_identifier(self, text: str): def handle_payment_identifier(self, text: str):
pi = PaymentIdentifier(self.wallet, text) pi = PaymentIdentifier(self.wallet, text)
if pi.is_valid(): if pi.is_valid():

3
electrum/gui/qt/receive_tab.py

@ -194,7 +194,8 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
_('For Lightning requests, payments will not be accepted after the expiration.'), _('For Lightning requests, payments will not be accepted after the expiration.'),
]) ])
expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS
v = self.window.query_choice(msg, pr_expiration_values(), title=_('Expiry'), default_choice=expiry) choices = list(pr_expiration_values().items())
v = self.window.query_choice(msg, choices, title=_('Expiry'), default_choice=expiry)
if v is None: if v is None:
return return
self.config.WALLET_PAYREQ_EXPIRY_SECONDS = v self.config.WALLET_PAYREQ_EXPIRY_SECONDS = v

18
electrum/gui/qt/seed_dialog.py

@ -37,8 +37,7 @@ from electrum import old_mnemonic
from electrum import slip39 from electrum import slip39
from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path, from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path,
EnterButton, CloseButton, WindowModalDialog, ColorScheme, EnterButton, CloseButton, WindowModalDialog, ColorScheme, font_height, ChoiceWidget)
ChoicesLayout, font_height)
from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
from .completion_text_edit import CompletionTextEdit from .completion_text_edit import CompletionTextEdit
@ -87,15 +86,15 @@ class SeedLayout(QVBoxLayout):
) )
if value in self.options or value == 'electrum' if value in self.options or value == 'electrum'
] ]
seed_type_values = [t[0] for t in seed_types]
if 'ext' in self.options: if 'ext' in self.options:
cb_ext = QCheckBox(_('Extend this seed with custom words')) cb_ext = QCheckBox(_('Extend this seed with custom words'))
cb_ext.setChecked(self.is_ext) cb_ext.setChecked(self.is_ext)
vbox.addWidget(cb_ext) vbox.addWidget(cb_ext)
if len(seed_types) >= 2: if len(seed_types) >= 2:
def f(choices_layout): def on_selected(idx):
self.seed_type = seed_type_values[choices_layout.selected_index()] self.seed_type = seed_type_choice.selected_key
self.is_seed = (lambda x: bool(x)) if self.seed_type != 'electrum' else self.saved_is_seed self.is_seed = (lambda x: bool(x)) if self.seed_type != 'electrum' else self.saved_is_seed
self.slip39_current_mnemonic_invalid = None self.slip39_current_mnemonic_invalid = None
self.seed_status.setText('') self.seed_status.setText('')
@ -120,16 +119,15 @@ class SeedLayout(QVBoxLayout):
self.initialize_completer() self.initialize_completer()
self.seed_warning.setText(msg) self.seed_warning.setText(msg)
checked_index = seed_type_values.index(self.seed_type) seed_type_choice = ChoiceWidget(message=_('Seed type'), choices=seed_types, selected=self.seed_type)
titles = [t[1] for t in seed_types] seed_type_choice.itemSelected.connect(on_selected)
clayout = ChoicesLayout(_('Seed type'), titles, on_clicked=f, checked_index=checked_index) vbox.addWidget(seed_type_choice)
vbox.addLayout(clayout.layout())
vbox.addLayout(Buttons(OkButton(dialog))) vbox.addLayout(Buttons(OkButton(dialog)))
if not dialog.exec_(): if not dialog.exec_():
return None return None
self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False
self.seed_type = seed_type_values[clayout.selected_index()] if len(seed_types) >= 2 else 'electrum' self.seed_type = seed_type_choice.selected_key if len(seed_types) >= 2 else 'electrum'
self.updated.emit() self.updated.emit()
def __init__( def __init__(

18
electrum/gui/qt/send_tab.py

@ -672,31 +672,31 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
can_pay_with_swap = lnworker.suggest_swap_to_send(amount_sat, coins=coins) can_pay_with_swap = lnworker.suggest_swap_to_send(amount_sat, coins=coins)
rebalance_suggestion = lnworker.suggest_rebalance_to_send(amount_sat) rebalance_suggestion = lnworker.suggest_rebalance_to_send(amount_sat)
can_rebalance = bool(rebalance_suggestion) and self.window.num_tasks() == 0 can_rebalance = bool(rebalance_suggestion) and self.window.num_tasks() == 0
choices = {} choices = []
if can_rebalance: if can_rebalance:
msg = ''.join([ msg = ''.join([
_('Rebalance existing channels'), '\n', _('Rebalance existing channels'), '\n',
_('Move funds between your channels in order to increase your sending capacity.') _('Move funds between your channels in order to increase your sending capacity.')
]) ])
choices[0] = msg choices.append(('rebalance', msg))
if can_pay_with_new_channel: if can_pay_with_new_channel:
msg = ''.join([ msg = ''.join([
_('Open a new channel'), '\n', _('Open a new channel'), '\n',
_('You will be able to pay once the channel is open.') _('You will be able to pay once the channel is open.')
]) ])
choices[1] = msg choices.append(('new_channel', msg))
if can_pay_with_swap: if can_pay_with_swap:
msg = ''.join([ msg = ''.join([
_('Swap onchain funds for lightning funds'), '\n', _('Swap onchain funds for lightning funds'), '\n',
_('You will be able to pay once the swap is confirmed.') _('You will be able to pay once the swap is confirmed.')
]) ])
choices[2] = msg choices.append(('swap', msg))
if can_pay_onchain: if can_pay_onchain:
msg = ''.join([ msg = ''.join([
_('Pay onchain'), '\n', _('Pay onchain'), '\n',
_('Funds will be sent to the invoice fallback address.') _('Funds will be sent to the invoice fallback address.')
]) ])
choices[3] = msg choices.append(('onchain', msg))
msg = _('You cannot pay that invoice using Lightning.') msg = _('You cannot pay that invoice using Lightning.')
if lnworker and lnworker.channels: if lnworker and lnworker.channels:
num_sats_can_send = int(lnworker.num_sats_can_send()) num_sats_can_send = int(lnworker.num_sats_can_send())
@ -709,16 +709,16 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
r = self.window.query_choice(msg, choices) r = self.window.query_choice(msg, choices)
if r is not None: if r is not None:
self.save_pending_invoice() self.save_pending_invoice()
if r == 0: if r == 'rebalance':
chan1, chan2, delta = rebalance_suggestion chan1, chan2, delta = rebalance_suggestion
self.window.rebalance_dialog(chan1, chan2, amount_sat=delta) self.window.rebalance_dialog(chan1, chan2, amount_sat=delta)
elif r == 1: elif r == 'new_channel':
amount_sat, min_amount_sat = can_pay_with_new_channel amount_sat, min_amount_sat = can_pay_with_new_channel
self.window.new_channel_dialog(amount_sat=amount_sat, min_amount_sat=min_amount_sat) self.window.new_channel_dialog(amount_sat=amount_sat, min_amount_sat=min_amount_sat)
elif r == 2: elif r == 'swap':
chan, swap_recv_amount_sat = can_pay_with_swap chan, swap_recv_amount_sat = can_pay_with_swap
self.window.run_swap_dialog(is_reverse=False, recv_amount_sat=swap_recv_amount_sat, channels=[chan]) self.window.run_swap_dialog(is_reverse=False, recv_amount_sat=swap_recv_amount_sat, channels=[chan])
elif r == 3: elif r == 'onchain':
self.pay_onchain_dialog(invoice.get_outputs(), nonlocal_only=True) self.pay_onchain_dialog(invoice.get_outputs(), nonlocal_only=True)
return return

71
electrum/gui/qt/util.py

@ -283,6 +283,26 @@ class MessageBoxMixin(object):
rich_text=rich_text, rich_text=rich_text,
checkbox=checkbox) 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
if title is None:
title = _('Question')
dialog = WindowModalDialog(self.top_level_window(), title=title)
dialog.setMinimumWidth(400)
choice_widget = ChoiceWidget(message=msg, choices=choices, selected=default_choice)
vbox = QVBoxLayout(dialog)
vbox.addWidget(choice_widget)
cancel_button = CancelButton(dialog)
vbox.addLayout(Buttons(cancel_button, OkButton(dialog)))
cancel_button.setFocus()
if not dialog.exec_():
return None
return choice_widget.selected_key
def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.Ok, def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.Ok,
defaultButton=QMessageBox.NoButton, rich_text=False, defaultButton=QMessageBox.NoButton, rich_text=False,
@ -422,44 +442,19 @@ def text_dialog(
return txt.toPlainText() return txt.toPlainText()
class ChoicesLayout(object):
def __init__(self, msg, choices, on_clicked=None, checked_index=0):
vbox = QVBoxLayout()
if len(msg) > 50:
vbox.addWidget(WWLabel(msg))
msg = ""
gb2 = QGroupBox(msg)
vbox.addWidget(gb2)
vbox2 = QVBoxLayout()
gb2.setLayout(vbox2)
self.group = group = QButtonGroup(gb2)
if isinstance(choices, list):
iterator = enumerate(choices)
else:
iterator = choices.items()
for i, c in iterator:
button = QRadioButton(gb2)
button.setText(c)
vbox2.addWidget(button)
group.addButton(button)
group.setId(button, i)
if i == checked_index:
button.setChecked(True)
if on_clicked:
group.buttonClicked.connect(partial(on_clicked, self))
self.vbox = vbox
def layout(self):
return self.vbox
def selected_index(self):
return self.group.checkedId()
class ChoiceWidget(QWidget): 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.
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).
"""
itemSelected = pyqtSignal([int], arguments=['index']) itemSelected = pyqtSignal([int], arguments=['index'])
def __init__(self, *, message=None, choices=None, selected=None): def __init__(self, *, message: Optional[str] = None, choices: Sequence[Tuple] = None, selected: Optional[Any] = None):
QWidget.__init__(self) QWidget.__init__(self)
vbox = QVBoxLayout() vbox = QVBoxLayout()
self.setLayout(vbox) self.setLayout(vbox)
@ -467,9 +462,9 @@ class ChoiceWidget(QWidget):
if choices is None: if choices is None:
choices = [] choices = []
self.selected_index = -1 self.selected_index = -1 # int
self.selected_item = None self.selected_item = None # Optional[Tuple]
self.selected_key = None self.selected_key = None # Optional[Any]
self.choices = choices self.choices = choices

13
electrum/gui/qt/wallet_info_dialog.py

@ -9,14 +9,13 @@ from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout,
QHBoxLayout, QPushButton, QWidget, QStackedWidget) QHBoxLayout, QPushButton, QWidget, QStackedWidget)
from electrum import keystore
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum.i18n import _ from electrum.i18n import _
from electrum.wallet import Multisig_Wallet from electrum.wallet import Multisig_Wallet
from .qrtextedit import ShowQRTextEdit from .qrtextedit import ShowQRTextEdit
from .util import (read_QIcon, WindowModalDialog, ChoicesLayout, Buttons, from .util import (read_QIcon, WindowModalDialog, Buttons,
WWLabel, CloseButton, HelpButton, font_height, ShowQRLineEdit) WWLabel, CloseButton, HelpButton, font_height, ShowQRLineEdit, ChoiceWidget)
if TYPE_CHECKING: if TYPE_CHECKING:
from .main_window import ElectrumWindow from .main_window import ElectrumWindow
@ -118,11 +117,11 @@ class WalletInfoDialog(WindowModalDialog):
else: else:
return _("keystore") + f' {idx+1}' return _("keystore") + f' {idx+1}'
labels = [label(idx, ks) for idx, ks in enumerate(wallet.get_keystores())] labels = [(idx, label(idx, ks)) for idx, ks in enumerate(wallet.get_keystores())]
on_click = lambda clayout: select_ks(clayout.selected_index()) keystore_choice = ChoiceWidget(message=_("Select keystore"), choices=labels)
labels_clayout = ChoicesLayout(_("Select keystore"), labels, on_click) keystore_choice.itemSelected.connect(lambda x: select_ks(x))
vbox.addLayout(labels_clayout.layout()) vbox.addWidget(keystore_choice)
for ks in keystores: for ks in keystores:
ks_w = QWidget() ks_w = QWidget()

18
electrum/gui/qt/wizard/wallet.py

@ -29,7 +29,7 @@ from electrum.gui.qt.bip39_recovery_dialog import Bip39RecoveryDialog
from electrum.gui.qt.password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD, PasswordLayoutForHW from electrum.gui.qt.password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD, PasswordLayoutForHW
from electrum.gui.qt.seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout from electrum.gui.qt.seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout
from electrum.gui.qt.util import (PasswordLineEdit, char_width_in_lineedit, WWLabel, InfoButton, font_height, from electrum.gui.qt.util import (PasswordLineEdit, char_width_in_lineedit, WWLabel, InfoButton, font_height,
ChoiceWidget, MessageBoxMixin, WindowModalDialog, ChoicesLayout, CancelButton, ChoiceWidget, MessageBoxMixin, WindowModalDialog, CancelButton,
Buttons, OkButton, icon_path) Buttons, OkButton, icon_path)
if TYPE_CHECKING: if TYPE_CHECKING:
@ -237,22 +237,6 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard, MessageBoxMixin):
if on_finished: if on_finished:
on_finished() on_finished()
def query_choice(self, msg, choices, title=None, default_choice=None):
# Needed by QtHandler for hardware wallets
if title is None:
title = _('Question')
dialog = WindowModalDialog(self.top_level_window(), title=title)
dialog.setMinimumWidth(400)
clayout = ChoicesLayout(msg, choices, checked_index=default_choice)
vbox = QVBoxLayout(dialog)
vbox.addLayout(clayout.layout())
cancel_button = CancelButton(dialog)
vbox.addLayout(Buttons(cancel_button, OkButton(dialog)))
cancel_button.setFocus()
if not dialog.exec_():
return None
return clayout.selected_index()
class WalletWizardComponent(WizardComponent, ABC): class WalletWizardComponent(WizardComponent, ABC):
# ^ this class only exists to help with typing # ^ this class only exists to help with typing

4
electrum/plugins/digitalbitbox/qt.py

@ -69,6 +69,10 @@ class DigitalBitbox_Handler(QtHandlerBase):
def __init__(self, win): def __init__(self, win):
super(DigitalBitbox_Handler, self).__init__(win, 'Digital Bitbox') super(DigitalBitbox_Handler, self).__init__(win, 'Digital Bitbox')
def query_choice(self, msg, labels):
choices = [(i, v) for i, v in enumerate(labels)]
return QtHandlerBase.query_choice(self, msg, choices)
class WCDigitalBitboxScriptAndDerivation(WCScriptAndDerivation): class WCDigitalBitboxScriptAndDerivation(WCScriptAndDerivation):
requestRecheck = pyqtSignal() requestRecheck = pyqtSignal()

6
electrum/plugins/hw_wallet/qt.py

@ -26,7 +26,7 @@
import threading import threading
from functools import partial from functools import partial
from typing import TYPE_CHECKING, Union, Optional from typing import TYPE_CHECKING, Union, Optional, Sequence, Tuple
from PyQt5.QtCore import QObject, pyqtSignal, Qt from PyQt5.QtCore import QObject, pyqtSignal, Qt
from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel
@ -95,7 +95,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger):
icon_name = button.icon_paired if paired else button.icon_unpaired icon_name = button.icon_paired if paired else button.icon_unpaired
button.setIcon(read_QIcon(icon_name)) button.setIcon(read_QIcon(icon_name))
def query_choice(self, msg, labels): def query_choice(self, msg: str, labels: Sequence[Tuple]):
self.done.clear() self.done.clear()
self.query_signal.emit(msg, labels) self.query_signal.emit(msg, labels)
self.done.wait() self.done.wait()
@ -194,7 +194,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger):
self.dialog.accept() self.dialog.accept()
self.dialog = None self.dialog = None
def win_query_choice(self, msg, labels): def win_query_choice(self, msg: str, labels: Sequence[Tuple]):
try: try:
self.choice = self.win.query_choice(msg, labels) self.choice = self.win.query_choice(msg, labels)
except UserCancelled: except UserCancelled:

Loading…
Cancel
Save