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
import queue
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
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,
QShortcut, QMainWindow, QInputDialog,
QWidget, QSizePolicy, QStatusBar, QToolTip,
QMenu, QAction, QStackedWidget, QToolButton)
QMenu, QAction, QToolButton)
import electrum
from electrum.gui import messages
@ -82,7 +82,7 @@ from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit, ScanShowQRTextEdit
from .transaction_dialog import show_transaction
from .fee_slider import FeeSlider, FeeComboBox
from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialog,
WindowModalDialog, ChoicesLayout, HelpLabel, Buttons,
WindowModalDialog, HelpLabel, Buttons,
OkButton, InfoButton, WWLabel, TaskThread, CancelButton,
CloseButton, HelpButton, MessageBoxMixin, EnterButton,
import_meta_gui, export_meta_gui,
@ -1370,22 +1370,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
else:
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):
pi = PaymentIdentifier(self.wallet, text)
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.'),
])
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:
return
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 .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path,
EnterButton, CloseButton, WindowModalDialog, ColorScheme,
ChoicesLayout, font_height)
EnterButton, CloseButton, WindowModalDialog, ColorScheme, font_height, ChoiceWidget)
from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
from .completion_text_edit import CompletionTextEdit
@ -87,15 +86,15 @@ class SeedLayout(QVBoxLayout):
)
if value in self.options or value == 'electrum'
]
seed_type_values = [t[0] for t in seed_types]
if 'ext' in self.options:
cb_ext = QCheckBox(_('Extend this seed with custom words'))
cb_ext.setChecked(self.is_ext)
vbox.addWidget(cb_ext)
if len(seed_types) >= 2:
def f(choices_layout):
self.seed_type = seed_type_values[choices_layout.selected_index()]
def on_selected(idx):
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.slip39_current_mnemonic_invalid = None
self.seed_status.setText('')
@ -120,16 +119,15 @@ class SeedLayout(QVBoxLayout):
self.initialize_completer()
self.seed_warning.setText(msg)
checked_index = seed_type_values.index(self.seed_type)
titles = [t[1] for t in seed_types]
clayout = ChoicesLayout(_('Seed type'), titles, on_clicked=f, checked_index=checked_index)
vbox.addLayout(clayout.layout())
seed_type_choice = ChoiceWidget(message=_('Seed type'), choices=seed_types, selected=self.seed_type)
seed_type_choice.itemSelected.connect(on_selected)
vbox.addWidget(seed_type_choice)
vbox.addLayout(Buttons(OkButton(dialog)))
if not dialog.exec_():
return None
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()
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)
rebalance_suggestion = lnworker.suggest_rebalance_to_send(amount_sat)
can_rebalance = bool(rebalance_suggestion) and self.window.num_tasks() == 0
choices = {}
choices = []
if can_rebalance:
msg = ''.join([
_('Rebalance existing channels'), '\n',
_('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:
msg = ''.join([
_('Open a new channel'), '\n',
_('You will be able to pay once the channel is open.')
])
choices[1] = msg
choices.append(('new_channel', 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[2] = msg
choices.append(('swap', msg))
if can_pay_onchain:
msg = ''.join([
_('Pay onchain'), '\n',
_('Funds will be sent to the invoice fallback address.')
])
choices[3] = msg
choices.append(('onchain', msg))
msg = _('You cannot pay that invoice using Lightning.')
if lnworker and lnworker.channels:
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)
if r is not None:
self.save_pending_invoice()
if r == 0:
if r == 'rebalance':
chan1, chan2, delta = rebalance_suggestion
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
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
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)
return

71
electrum/gui/qt/util.py

@ -283,6 +283,26 @@ 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
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,
defaultButton=QMessageBox.NoButton, rich_text=False,
@ -422,44 +442,19 @@ def text_dialog(
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):
"""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'])
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)
vbox = QVBoxLayout()
self.setLayout(vbox)
@ -467,9 +462,9 @@ class ChoiceWidget(QWidget):
if choices is None:
choices = []
self.selected_index = -1
self.selected_item = None
self.selected_key = None
self.selected_index = -1 # int
self.selected_item = None # Optional[Tuple]
self.selected_key = None # Optional[Any]
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,
QHBoxLayout, QPushButton, QWidget, QStackedWidget)
from electrum import keystore
from electrum.plugin import run_hook
from electrum.i18n import _
from electrum.wallet import Multisig_Wallet
from .qrtextedit import ShowQRTextEdit
from .util import (read_QIcon, WindowModalDialog, ChoicesLayout, Buttons,
WWLabel, CloseButton, HelpButton, font_height, ShowQRLineEdit)
from .util import (read_QIcon, WindowModalDialog, Buttons,
WWLabel, CloseButton, HelpButton, font_height, ShowQRLineEdit, ChoiceWidget)
if TYPE_CHECKING:
from .main_window import ElectrumWindow
@ -118,11 +117,11 @@ class WalletInfoDialog(WindowModalDialog):
else:
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())
labels_clayout = ChoicesLayout(_("Select keystore"), labels, on_click)
vbox.addLayout(labels_clayout.layout())
keystore_choice = ChoiceWidget(message=_("Select keystore"), choices=labels)
keystore_choice.itemSelected.connect(lambda x: select_ks(x))
vbox.addWidget(keystore_choice)
for ks in keystores:
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.seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout
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)
if TYPE_CHECKING:
@ -237,22 +237,6 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard, MessageBoxMixin):
if 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):
# ^ 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):
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):
requestRecheck = pyqtSignal()

6
electrum/plugins/hw_wallet/qt.py

@ -26,7 +26,7 @@
import threading
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.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
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.query_signal.emit(msg, labels)
self.done.wait()
@ -194,7 +194,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger):
self.dialog.accept()
self.dialog = None
def win_query_choice(self, msg, labels):
def win_query_choice(self, msg: str, labels: Sequence[Tuple]):
try:
self.choice = self.win.query_choice(msg, labels)
except UserCancelled:

Loading…
Cancel
Save