tqt coin selection: allow selecting an empty set - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 6f246a83b307a63b80b0d98c1f0f3c606e45170a DIR parent 11f54aee60d6de3413812318b8cc826365a43051 HTML Author: SomberNight <somber.night@protonmail.com> Date: Thu, 21 Nov 2019 17:46:00 +0100 qt coin selection: allow selecting an empty set Using this, the user can force "bump fee" not to add new inputs. closes #5719 Diffstat: M electrum/gui/qt/main_window.py | 15 +++++++++++---- M electrum/gui/qt/transaction_dialog… | 6 +++--- M electrum/gui/qt/utxo_list.py | 49 ++++++++++++++++++------------- 3 files changed, 42 insertions(+), 28 deletions(-) --- DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py t@@ -1475,11 +1475,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): else: raise Exception('unknown invoice type') - def get_coins(self, *, nonlocal_only=False): + def get_coins(self, *, nonlocal_only=False) -> Sequence[PartialTxInput]: coins = self.get_manually_selected_coins() - return coins or self.wallet.get_spendable_coins(None, nonlocal_only=nonlocal_only) + if coins is not None: + return coins + else: + return self.wallet.get_spendable_coins(None, nonlocal_only=nonlocal_only) - def get_manually_selected_coins(self) -> Sequence[PartialTxInput]: + def get_manually_selected_coins(self) -> Optional[Sequence[PartialTxInput]]: + """Return a list of selected coins or None. + Note: None means selection is not being used, + while an empty sequence means the user specifically selected that. + """ return self.utxo_list.get_spend_list() def pay_onchain_dialog(self, inputs: Sequence[PartialTxInput], t@@ -2009,7 +2016,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): self.coincontrol_label.setTextInteractionFlags(Qt.TextSelectableByMouse) sb.addWidget(self.coincontrol_label) - clear_cc_button = EnterButton(_('Reset'), lambda: self.utxo_list.set_spend_list([])) + clear_cc_button = EnterButton(_('Reset'), lambda: self.utxo_list.set_spend_list(None)) clear_cc_button.setStyleSheet("margin-right: 5px;") sb.addPermanentWidget(clear_cc_button) DIR diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py t@@ -450,9 +450,9 @@ class BaseTxDialog(QDialog, MessageBoxMixin): def update_io(self): inputs_header_text = _("Inputs") + ' (%d)'%len(self.tx.inputs()) if not self.finalized: - num_utxos = len(self.main_window.get_manually_selected_coins()) - if num_utxos > 0: - inputs_header_text += f" - " + _("Coin selection active ({} UTXOs selected)").format(num_utxos) + selected_coins = self.main_window.get_manually_selected_coins() + if selected_coins is not None: + inputs_header_text += f" - " + _("Coin selection active ({} UTXOs selected)").format(len(selected_coins)) self.inputs_header.setText(inputs_header_text) ext = QTextCharFormat() rec = QTextCharFormat() DIR diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py t@@ -58,7 +58,7 @@ class UTXOList(MyTreeView): super().__init__(parent, self.create_menu, stretch_column=self.Columns.LABEL, editable_columns=[]) - self._spend_set = set() # type: Set[str] # coins selected by the user to spend from + self._spend_set = None # type: Optional[Set[str]] # coins selected by the user to spend from self.setModel(QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) t@@ -75,12 +75,12 @@ class UTXOList(MyTreeView): self.insert_utxo(idx, utxo) self.filter() # update coincontrol status bar - coins = [self.utxo_dict[x] for x in self._spend_set] or utxos - coins = self._filter_frozen_coins(coins) - amount = sum(x.value_sats() for x in coins) - amount_str = self.parent.format_amount_and_units(amount) - num_outputs_str = _("{} outputs available ({} total)").format(len(coins), len(utxos)) - if self._spend_set: + if self._spend_set is not None: + coins = [self.utxo_dict[x] for x in self._spend_set] + coins = self._filter_frozen_coins(coins) + amount = sum(x.value_sats() for x in coins) + amount_str = self.parent.format_amount_and_units(amount) + num_outputs_str = _("{} outputs available ({} total)").format(len(coins), len(utxos)) self.parent.set_coincontrol_msg(_("Coin control active") + f': {num_outputs_str}, {amount_str}') else: self.parent.set_coincontrol_msg(None) t@@ -101,7 +101,7 @@ class UTXOList(MyTreeView): utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT)) utxo_item[self.Columns.ADDRESS].setData(name, Qt.UserRole) SELECTED_TO_SPEND_TOOLTIP = _('Coin selected to be spent') - if name in self._spend_set: + if name in (self._spend_set or set()): for col in utxo_item: col.setBackground(ColorScheme.GREEN.as_color(True)) if col != self.Columns.OUTPOINT: t@@ -113,7 +113,7 @@ class UTXOList(MyTreeView): utxo_item[self.Columns.OUTPOINT].setBackground(ColorScheme.BLUE.as_color(True)) utxo_item[self.Columns.OUTPOINT].setToolTip(f"{name}\n{_('Coin is frozen')}") else: - tooltip = ("\n" + SELECTED_TO_SPEND_TOOLTIP) if name in self._spend_set else "" + tooltip = ("\n" + SELECTED_TO_SPEND_TOOLTIP) if name in (self._spend_set or set()) else "" utxo_item[self.Columns.OUTPOINT].setToolTip(name + tooltip) self.model().insertRow(idx, utxo_item) t@@ -121,8 +121,6 @@ class UTXOList(MyTreeView): if not self.model(): return None items = self.selected_in_column(self.Columns.ADDRESS) - if not items: - return None return [x.data(Qt.UserRole) for x in items] def _filter_frozen_coins(self, coins: List[PartialTxInput]) -> List[PartialTxInput]: t@@ -131,29 +129,39 @@ class UTXOList(MyTreeView): not self.wallet.is_frozen_coin(utxo))] return coins - def set_spend_list(self, coins: List[PartialTxInput]): - coins = self._filter_frozen_coins(coins) - self._spend_set = {utxo.prevout.to_str() for utxo in coins} + def set_spend_list(self, coins: Optional[List[PartialTxInput]]): + if coins is not None: + coins = self._filter_frozen_coins(coins) + self._spend_set = {utxo.prevout.to_str() for utxo in coins} + else: + self._spend_set = None self.update() - def get_spend_list(self) -> Sequence[PartialTxInput]: + def get_spend_list(self) -> Optional[Sequence[PartialTxInput]]: + if self._spend_set is None: + return None return [self.utxo_dict[x] for x in self._spend_set] def _maybe_reset_spend_list(self, current_wallet_utxos: Sequence[PartialTxInput]) -> None: + if self._spend_set is None: + return # if we spent one of the selected UTXOs, just reset selection utxo_set = {utxo.prevout.to_str() for utxo in current_wallet_utxos} if not all([prevout_str in utxo_set for prevout_str in self._spend_set]): - self._spend_set = set() + self._spend_set = None def create_menu(self, position): selected = self.get_selected_outpoints() - if not selected: + if selected is None: return menu = QMenu() menu.setSeparatorsCollapsible(True) # consecutive separators are merged together coins = [self.utxo_dict[name] for name in selected] - menu.addAction(_("Spend"), lambda: self.set_spend_list(coins)) - assert len(coins) >= 1, len(coins) + if len(coins) == 0: + menu.addAction(_("Spend (select none)"), lambda: self.set_spend_list(coins)) + else: + menu.addAction(_("Spend"), lambda: self.set_spend_list(coins)) + if len(coins) == 1: utxo = coins[0] addr = utxo.address t@@ -184,8 +192,7 @@ class UTXOList(MyTreeView): menu.addAction(_("Address is frozen"), lambda: None).setEnabled(False) menu.addAction(_("Unfreeze Address"), lambda: self.parent.set_frozen_state_of_addresses([addr], False)) menu.addSeparator() - else: - # multiple items selected + elif len(coins) > 1: # multiple items selected menu.addSeparator() addrs = [utxo.address for utxo in coins] is_coin_frozen = [self.wallet.is_frozen_coin(utxo) for utxo in coins]