Browse Source

Qt: make display of lists more stable.

Use refresh_row() for address, utxo and contact lists.
Replace unneeded calls to update_tabs() with refresh_tabs()

Fix right-click menu after selecting multiple addresses.
patch-4
ThomasV 3 years ago
parent
commit
e362d1aac4
  1. 51
      electrum/gui/qt/address_list.py
  2. 6
      electrum/gui/qt/contact_list.py
  3. 33
      electrum/gui/qt/main_window.py
  4. 6
      electrum/gui/qt/settings_dialog.py
  5. 57
      electrum/gui/qt/utxo_list.py

51
electrum/gui/qt/address_list.py

@ -82,6 +82,7 @@ class AddressList(MyTreeView):
ROLE_SORT_ORDER = Qt.UserRole + 1000
ROLE_ADDRESS_STR = Qt.UserRole + 1001
key_role = ROLE_ADDRESS_STR
def __init__(self, parent):
super().__init__(parent, self.create_menu,
@ -150,7 +151,7 @@ class AddressList(MyTreeView):
def update(self):
if self.maybe_defer_update():
return
current_address = self.get_role_data_for_current_item(col=self.Columns.LABEL, role=self.ROLE_ADDRESS_STR)
current_address = self.get_role_data_for_current_item(col=0, role=self.ROLE_ADDRESS_STR)
if self.show_change == AddressTypeFilter.RECEIVING:
addr_list = self.wallet.get_receiving_addresses()
elif self.show_change == AddressTypeFilter.CHANGE:
@ -164,8 +165,6 @@ class AddressList(MyTreeView):
set_address = None
addresses_beyond_gap_limit = self.wallet.get_all_known_addresses_beyond_gap_limit()
for address in addr_list:
num = self.wallet.get_address_history_len(address)
label = self.wallet.get_label(address)
c, u, x = self.wallet.get_addr_balance(address)
balance = c + u + x
is_used_and_empty = self.wallet.is_used(address) and balance == 0
@ -177,14 +176,7 @@ class AddressList(MyTreeView):
continue
if self.show_used == AddressUsageStateFilter.FUNDED_OR_UNUSED and is_used_and_empty:
continue
balance_text = self.parent.format_amount(balance, whitespaces=True)
# create item
if fx and fx.get_fiat_address_config():
rate = fx.exchange_rate()
fiat_balance = fx.value_str(balance, rate)
else:
fiat_balance = ''
labels = ['', address, label, balance_text, fiat_balance, "%d"%num]
labels = ['', address, '', '', '', '']
address_item = [QStandardItem(e) for e in labels]
# align text and set fonts
for i, item in enumerate(address_item):
@ -200,21 +192,18 @@ class AddressList(MyTreeView):
else:
address_item[self.Columns.TYPE].setText(_('receiving'))
address_item[self.Columns.TYPE].setBackground(ColorScheme.GREEN.as_color(True))
address_item[self.Columns.LABEL].setData(address, self.ROLE_ADDRESS_STR)
address_item[0].setData(address, self.ROLE_ADDRESS_STR)
address_path = self.wallet.get_address_index(address)
address_item[self.Columns.TYPE].setData(address_path, self.ROLE_SORT_ORDER)
address_path_str = self.wallet.get_address_path_str(address)
if address_path_str is not None:
address_item[self.Columns.TYPE].setToolTip(address_path_str)
address_item[self.Columns.FIAT_BALANCE].setData(balance, self.ROLE_SORT_ORDER)
# setup column 1
if self.wallet.is_frozen_address(address):
address_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True))
if address in addresses_beyond_gap_limit:
address_item[self.Columns.ADDRESS].setBackground(ColorScheme.RED.as_color(True))
# add item
count = self.std_model.rowCount()
self.std_model.insertRow(count, address_item)
self.refresh_row(address, count)
address_idx = self.std_model.index(count, self.Columns.LABEL)
if address == current_address:
set_address = QPersistentModelIndex(address_idx)
@ -227,6 +216,29 @@ class AddressList(MyTreeView):
self.filter()
self.proxy.setDynamicSortFilter(True)
def refresh_row(self, key, row):
address = key
label = self.wallet.get_label(address)
num = self.wallet.get_address_history_len(address)
c, u, x = self.wallet.get_addr_balance(address)
balance = c + u + x
balance_text = self.parent.format_amount(balance, whitespaces=True)
# create item
fx = self.parent.fx
if fx and fx.get_fiat_address_config():
rate = fx.exchange_rate()
fiat_balance_str = fx.value_str(balance, rate)
else:
fiat_balance_str = ''
address_item = [self.std_model.item(row, col) for col in self.Columns]
address_item[self.Columns.LABEL].setText(label)
address_item[self.Columns.COIN_BALANCE].setText(balance_text)
address_item[self.Columns.COIN_BALANCE].setData(balance, self.ROLE_SORT_ORDER)
address_item[self.Columns.FIAT_BALANCE].setText(fiat_balance_str)
address_item[self.Columns.NUM_TXS].setText("%d"%num)
c = ColorScheme.BLUE if self.wallet.is_frozen_address(address) else ColorScheme.DEFAULT
address_item[self.Columns.ADDRESS].setBackground(c.as_color(True))
def create_menu(self, position):
from electrum.wallet import Multisig_Wallet
is_multisig = isinstance(self.wallet, Multisig_Wallet)
@ -268,6 +280,11 @@ class AddressList(MyTreeView):
else:
menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state_of_addresses([addr], False))
else:
# multiple items selected
menu.addAction(_("Freeze"), lambda: self.parent.set_frozen_state_of_addresses(addrs, True))
menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state_of_addresses(addrs, False))
coins = self.wallet.get_spendable_coins(addrs)
if coins:
menu.addAction(_("Spend from"), lambda: self.parent.utxo_list.set_spend_list(coins))
@ -287,7 +304,7 @@ class AddressList(MyTreeView):
def get_edit_key_from_coordinate(self, row, col):
if col != self.Columns.LABEL:
return None
return self.get_role_data_from_coordinate(row, col, role=self.ROLE_ADDRESS_STR)
return self.get_role_data_from_coordinate(row, 0, role=self.ROLE_ADDRESS_STR)
def on_edited(self, idx, edit_key, *, text):
self.parent.wallet.set_label(edit_key, text)

6
electrum/gui/qt/contact_list.py

@ -50,6 +50,7 @@ class ContactList(MyTreeView):
filter_columns = [Columns.NAME, Columns.ADDRESS]
ROLE_CONTACT_KEY = Qt.UserRole + 1000
key_role = ROLE_CONTACT_KEY
def __init__(self, parent):
super().__init__(parent, self.create_menu,
@ -58,6 +59,7 @@ class ContactList(MyTreeView):
self.setModel(QStandardItemModel(self))
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setSortingEnabled(True)
self.std_model = self.model()
self.update()
def on_edited(self, idx, edit_key, *, text):
@ -121,6 +123,10 @@ class ContactList(MyTreeView):
self.filter()
run_hook('update_contacts_tab', self)
def refresh_row(self, key):
# nothing to update here
pass
def get_edit_key_from_coordinate(self, row, col):
if col != self.Columns.NAME:
return None

33
electrum/gui/qt/main_window.py

@ -331,7 +331,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
def on_fx_history(self):
self.history_model.refresh('fx_history')
self.address_list.update()
self.address_list.refresh_all()
def on_fx_quotes(self):
self.update_status()
@ -343,7 +343,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
# History tab needs updating if it used spot
if self.fx.history_used_spot:
self.history_model.refresh('fx_quotes')
self.address_list.update()
self.address_list.refresh_all()
def toggle_tab(self, tab):
show = not self.config.get('show_{}_tab'.format(tab.tab_name), False)
@ -431,7 +431,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.network_signal.emit('status', None)
elif event == 'blockchain_updated':
# to update number of confirmations in history
self.need_update.set()
self.refresh_tabs()
elif event == 'new_transaction':
wallet, tx = args
if wallet == self.wallet:
@ -456,6 +456,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
elif event == 'invoice_status':
self.on_invoice_status(*args)
elif event == 'payment_succeeded':
# sent by lnworker, redundant with invoice_status
wallet = args[0]
if wallet == self.wallet:
self.on_payment_succeeded(*args)
@ -875,6 +876,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
def timer_actions(self):
# refresh invoices and requests because they show ETA
self.request_list.refresh_all()
self.invoice_list.refresh_all()
# Note this runs in the GUI thread
@ -1029,14 +1031,23 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
if wallet != self.wallet:
return
self.history_model.refresh('update_tabs')
self.request_list.refresh_all()
self.invoice_list.refresh_all()
self.request_list.update()
self.invoice_list.update()
self.address_list.update()
self.utxo_list.update()
self.contact_list.update()
self.channels_list.update_rows.emit(wallet)
self.update_completions()
def refresh_tabs(self, wallet=None):
self.history_model.refresh('refresh_tabs')
self.request_list.refresh_all()
self.invoice_list.refresh_all()
self.address_list.refresh_all()
self.utxo_list.refresh_all()
self.contact_list.refresh_all()
self.channels_list.update_rows.emit(self.wallet)
def create_channels_tab(self):
self.channels_list = ChannelsList(self)
t = self.channels_list.get_toolbar()
@ -1256,7 +1267,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
key = self.create_bitcoin_request(amount, message, expiry)
if not key:
return
self.address_list.update()
self.address_list.refresh_all()
except InvoiceError as e:
self.show_error(_('Error creating payment request') + ':\n' + str(e))
return
@ -2064,13 +2075,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
def set_frozen_state_of_addresses(self, addrs, freeze: bool):
self.wallet.set_frozen_state_of_addresses(addrs, freeze)
self.address_list.update()
self.utxo_list.update()
self.address_list.refresh_all()
self.utxo_list.refresh_all()
self.address_list.selectionModel().clearSelection()
def set_frozen_state_of_coins(self, utxos: Sequence[PartialTxInput], freeze: bool):
utxos_str = {utxo.prevout.to_str() for utxo in utxos}
self.wallet.set_frozen_state_of_coins(utxos_str, freeze)
self.utxo_list.update()
self.utxo_list.refresh_all()
self.utxo_list.selectionModel().clearSelection()
def create_list_tab(self, l, toolbar=None):
w = QWidget()
@ -3197,7 +3210,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.fiat_receive_e.setVisible(b)
self.history_list.update()
self.address_list.refresh_headers()
self.address_list.update()
self.address_list.refresh_all()
self.update_status()
def settings_dialog(self):

6
electrum/gui/qt/settings_dialog.py

@ -98,7 +98,7 @@ class SettingsDialog(WindowModalDialog):
if self.config.num_zeros != value:
self.config.num_zeros = value
self.config.set_key('num_zeros', value, True)
self.window.need_update.set()
self.window.refresh_tabs()
nz.valueChanged.connect(on_nz)
gui_widgets.append((nz_label, nz))
@ -197,7 +197,7 @@ class SettingsDialog(WindowModalDialog):
if self.config.amt_precision_post_satoshi != prec:
self.config.amt_precision_post_satoshi = prec
self.config.set_key('amt_precision_post_satoshi', prec)
self.window.need_update.set()
self.window.refresh_tabs()
msat_cb.stateChanged.connect(on_msat_checked)
lightning_widgets.append((msat_cb, None))
@ -232,7 +232,7 @@ class SettingsDialog(WindowModalDialog):
if self.config.amt_add_thousands_sep != checked:
self.config.amt_add_thousands_sep = checked
self.config.set_key('amt_add_thousands_sep', checked)
self.window.need_update.set()
self.window.refresh_tabs()
thousandsep_cb.stateChanged.connect(on_set_thousandsep)
gui_widgets.append((thousandsep_cb, None))

57
electrum/gui/qt/utxo_list.py

@ -59,6 +59,7 @@ class UTXOList(MyTreeView):
stretch_column = Columns.LABEL
ROLE_PREVOUT_STR = Qt.UserRole + 1000
key_role = ROLE_PREVOUT_STR
def __init__(self, parent):
super().__init__(parent, self.create_menu,
@ -67,7 +68,8 @@ class UTXOList(MyTreeView):
self._utxo_dict = {}
self.wallet = self.parent.wallet
self.setModel(QStandardItemModel(self))
self.std_model = QStandardItemModel(self)
self.setModel(self.std_model)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setSortingEnabled(True)
self.update()
@ -80,37 +82,44 @@ class UTXOList(MyTreeView):
self.model().clear()
self.update_headers(self.__class__.headers)
for idx, utxo in enumerate(utxos):
self.insert_utxo(idx, utxo)
name = utxo.prevout.to_str()
self._utxo_dict[name] = utxo
address = utxo.address
height = utxo.block_height
name_short = utxo.prevout.txid.hex()[:16] + '...' + ":%d" % utxo.prevout.out_idx
amount = self.parent.format_amount(utxo.value_sats(), whitespaces=True)
labels = [name_short, address, '', amount, '%d'%height]
utxo_item = [QStandardItem(x) for x in labels]
self.set_editability(utxo_item)
utxo_item[self.Columns.OUTPOINT].setData(name, self.ROLE_CLIPBOARD_DATA)
utxo_item[self.Columns.OUTPOINT].setData(name, self.ROLE_PREVOUT_STR)
utxo_item[self.Columns.ADDRESS].setFont(QFont(MONOSPACE_FONT))
utxo_item[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT))
utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT))
self.model().insertRow(idx, utxo_item)
self.refresh_row(name, idx)
self.filter()
self.update_coincontrol_bar()
def update_coincontrol_bar(self):
# update coincontrol status bar
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))
num_outputs_str = _("{} outputs available ({} total)").format(len(coins), len(self._utxo_dict))
self.parent.set_coincontrol_msg(_("Coin control active") + f': {num_outputs_str}, {amount_str}')
else:
self.parent.set_coincontrol_msg(None)
def insert_utxo(self, idx, utxo: PartialTxInput):
def refresh_row(self, key, row):
utxo = self._utxo_dict[key]
utxo_item = [self.std_model.item(row, col) for col in self.Columns]
address = utxo.address
height = utxo.block_height
name = utxo.prevout.to_str()
name_short = utxo.prevout.txid.hex()[:16] + '...' + ":%d" % utxo.prevout.out_idx
self._utxo_dict[name] = utxo
label = self.wallet.get_label_for_txid(utxo.prevout.txid.hex()) or self.wallet.get_label(address)
amount = self.parent.format_amount(utxo.value_sats(), whitespaces=True)
labels = [name_short, address, label, amount, '%d'%height]
utxo_item = [QStandardItem(x) for x in labels]
self.set_editability(utxo_item)
utxo_item[self.Columns.OUTPOINT].setData(name, self.ROLE_CLIPBOARD_DATA)
utxo_item[self.Columns.OUTPOINT].setData(name, self.ROLE_PREVOUT_STR)
utxo_item[self.Columns.ADDRESS].setFont(QFont(MONOSPACE_FONT))
utxo_item[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT))
utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT))
SELECTED_TO_SPEND_TOOLTIP = _('Coin selected to be spent')
if name in (self._spend_set or set()):
if key in (self._spend_set or set()):
for col in utxo_item:
col.setBackground(ColorScheme.GREEN.as_color(True))
if col != self.Columns.OUTPOINT:
@ -120,11 +129,11 @@ class UTXOList(MyTreeView):
utxo_item[self.Columns.ADDRESS].setToolTip(_('Address is frozen'))
if self.wallet.is_frozen_coin(utxo):
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"{key}\n{_('Coin is frozen')}")
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)
tooltip = ("\n" + SELECTED_TO_SPEND_TOOLTIP) if key in (self._spend_set or set()) else ""
utxo_item[self.Columns.OUTPOINT].setBackground(ColorScheme.DEFAULT.as_color(True))
utxo_item[self.Columns.OUTPOINT].setToolTip(key + tooltip)
def get_selected_outpoints(self) -> Optional[List[str]]:
if not self.model():
@ -144,7 +153,9 @@ class UTXOList(MyTreeView):
self._spend_set = {utxo.prevout.to_str() for utxo in coins}
else:
self._spend_set = None
self.update()
self.refresh_all()
self.update_coincontrol_bar()
self.selectionModel().clearSelection()
def get_spend_list(self) -> Optional[Sequence[PartialTxInput]]:
if self._spend_set is None:

Loading…
Cancel
Save