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.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user