Browse Source

qt coin selection: allow selecting an empty set

Using this, the user can force "bump fee" not to add new inputs.

closes #5719
master
SomberNight 6 years ago
parent
commit
6f246a83b3
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 15
      electrum/gui/qt/main_window.py
  2. 6
      electrum/gui/qt/transaction_dialog.py
  3. 49
      electrum/gui/qt/utxo_list.py

15
electrum/gui/qt/main_window.py

@ -1475,11 +1475,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
else: else:
raise Exception('unknown invoice type') 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() 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() return self.utxo_list.get_spend_list()
def pay_onchain_dialog(self, inputs: Sequence[PartialTxInput], def pay_onchain_dialog(self, inputs: Sequence[PartialTxInput],
@ -2009,7 +2016,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.coincontrol_label.setTextInteractionFlags(Qt.TextSelectableByMouse) self.coincontrol_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
sb.addWidget(self.coincontrol_label) 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;") clear_cc_button.setStyleSheet("margin-right: 5px;")
sb.addPermanentWidget(clear_cc_button) sb.addPermanentWidget(clear_cc_button)

6
electrum/gui/qt/transaction_dialog.py

@ -450,9 +450,9 @@ class BaseTxDialog(QDialog, MessageBoxMixin):
def update_io(self): def update_io(self):
inputs_header_text = _("Inputs") + ' (%d)'%len(self.tx.inputs()) inputs_header_text = _("Inputs") + ' (%d)'%len(self.tx.inputs())
if not self.finalized: if not self.finalized:
num_utxos = len(self.main_window.get_manually_selected_coins()) selected_coins = self.main_window.get_manually_selected_coins()
if num_utxos > 0: if selected_coins is not None:
inputs_header_text += f" - " + _("Coin selection active ({} UTXOs selected)").format(num_utxos) inputs_header_text += f" - " + _("Coin selection active ({} UTXOs selected)").format(len(selected_coins))
self.inputs_header.setText(inputs_header_text) self.inputs_header.setText(inputs_header_text)
ext = QTextCharFormat() ext = QTextCharFormat()
rec = QTextCharFormat() rec = QTextCharFormat()

49
electrum/gui/qt/utxo_list.py

@ -58,7 +58,7 @@ class UTXOList(MyTreeView):
super().__init__(parent, self.create_menu, super().__init__(parent, self.create_menu,
stretch_column=self.Columns.LABEL, stretch_column=self.Columns.LABEL,
editable_columns=[]) 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.setModel(QStandardItemModel(self))
self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setSortingEnabled(True) self.setSortingEnabled(True)
@ -75,12 +75,12 @@ class UTXOList(MyTreeView):
self.insert_utxo(idx, utxo) self.insert_utxo(idx, utxo)
self.filter() self.filter()
# update coincontrol status bar # update coincontrol status bar
coins = [self.utxo_dict[x] for x in self._spend_set] or utxos if self._spend_set is not None:
coins = self._filter_frozen_coins(coins) coins = [self.utxo_dict[x] for x in self._spend_set]
amount = sum(x.value_sats() for x in coins) coins = self._filter_frozen_coins(coins)
amount_str = self.parent.format_amount_and_units(amount) amount = sum(x.value_sats() for x in coins)
num_outputs_str = _("{} outputs available ({} total)").format(len(coins), len(utxos)) amount_str = self.parent.format_amount_and_units(amount)
if self._spend_set: 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}') self.parent.set_coincontrol_msg(_("Coin control active") + f': {num_outputs_str}, {amount_str}')
else: else:
self.parent.set_coincontrol_msg(None) self.parent.set_coincontrol_msg(None)
@ -101,7 +101,7 @@ class UTXOList(MyTreeView):
utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT)) utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT))
utxo_item[self.Columns.ADDRESS].setData(name, Qt.UserRole) utxo_item[self.Columns.ADDRESS].setData(name, Qt.UserRole)
SELECTED_TO_SPEND_TOOLTIP = _('Coin selected to be spent') 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: for col in utxo_item:
col.setBackground(ColorScheme.GREEN.as_color(True)) col.setBackground(ColorScheme.GREEN.as_color(True))
if col != self.Columns.OUTPOINT: if col != self.Columns.OUTPOINT:
@ -113,7 +113,7 @@ class UTXOList(MyTreeView):
utxo_item[self.Columns.OUTPOINT].setBackground(ColorScheme.BLUE.as_color(True)) utxo_item[self.Columns.OUTPOINT].setBackground(ColorScheme.BLUE.as_color(True))
utxo_item[self.Columns.OUTPOINT].setToolTip(f"{name}\n{_('Coin is frozen')}") utxo_item[self.Columns.OUTPOINT].setToolTip(f"{name}\n{_('Coin is frozen')}")
else: 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) utxo_item[self.Columns.OUTPOINT].setToolTip(name + tooltip)
self.model().insertRow(idx, utxo_item) self.model().insertRow(idx, utxo_item)
@ -121,8 +121,6 @@ class UTXOList(MyTreeView):
if not self.model(): if not self.model():
return None return None
items = self.selected_in_column(self.Columns.ADDRESS) items = self.selected_in_column(self.Columns.ADDRESS)
if not items:
return None
return [x.data(Qt.UserRole) for x in items] return [x.data(Qt.UserRole) for x in items]
def _filter_frozen_coins(self, coins: List[PartialTxInput]) -> List[PartialTxInput]: def _filter_frozen_coins(self, coins: List[PartialTxInput]) -> List[PartialTxInput]:
@ -131,29 +129,39 @@ class UTXOList(MyTreeView):
not self.wallet.is_frozen_coin(utxo))] not self.wallet.is_frozen_coin(utxo))]
return coins return coins
def set_spend_list(self, coins: List[PartialTxInput]): def set_spend_list(self, coins: Optional[List[PartialTxInput]]):
coins = self._filter_frozen_coins(coins) if coins is not None:
self._spend_set = {utxo.prevout.to_str() for utxo in coins} coins = self._filter_frozen_coins(coins)
self._spend_set = {utxo.prevout.to_str() for utxo in coins}
else:
self._spend_set = None
self.update() 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] return [self.utxo_dict[x] for x in self._spend_set]
def _maybe_reset_spend_list(self, current_wallet_utxos: Sequence[PartialTxInput]) -> None: 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 # if we spent one of the selected UTXOs, just reset selection
utxo_set = {utxo.prevout.to_str() for utxo in current_wallet_utxos} 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]): 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): def create_menu(self, position):
selected = self.get_selected_outpoints() selected = self.get_selected_outpoints()
if not selected: if selected is None:
return return
menu = QMenu() menu = QMenu()
menu.setSeparatorsCollapsible(True) # consecutive separators are merged together menu.setSeparatorsCollapsible(True) # consecutive separators are merged together
coins = [self.utxo_dict[name] for name in selected] coins = [self.utxo_dict[name] for name in selected]
menu.addAction(_("Spend"), lambda: self.set_spend_list(coins)) if len(coins) == 0:
assert len(coins) >= 1, len(coins) 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: if len(coins) == 1:
utxo = coins[0] utxo = coins[0]
addr = utxo.address addr = utxo.address
@ -184,8 +192,7 @@ class UTXOList(MyTreeView):
menu.addAction(_("Address is frozen"), lambda: None).setEnabled(False) menu.addAction(_("Address is frozen"), lambda: None).setEnabled(False)
menu.addAction(_("Unfreeze Address"), lambda: self.parent.set_frozen_state_of_addresses([addr], False)) menu.addAction(_("Unfreeze Address"), lambda: self.parent.set_frozen_state_of_addresses([addr], False))
menu.addSeparator() menu.addSeparator()
else: elif len(coins) > 1: # multiple items selected
# multiple items selected
menu.addSeparator() menu.addSeparator()
addrs = [utxo.address for utxo in coins] addrs = [utxo.address for utxo in coins]
is_coin_frozen = [self.wallet.is_frozen_coin(utxo) for utxo in coins] is_coin_frozen = [self.wallet.is_frozen_coin(utxo) for utxo in coins]

Loading…
Cancel
Save