diff --git a/electrum/gui/qt/address_list.py b/electrum/gui/qt/address_list.py index cbcf3c47b..69868a4a0 100644 --- a/electrum/gui/qt/address_list.py +++ b/electrum/gui/qt/address_list.py @@ -25,6 +25,7 @@ import enum from enum import IntEnum +from typing import TYPE_CHECKING from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont @@ -38,6 +39,9 @@ from electrum.wallet import InternalAddressCorruption from .util import MyTreeView, MONOSPACE_FONT, ColorScheme, webopen, MySortModel +if TYPE_CHECKING: + from .main_window import ElectrumWindow + class AddressUsageStateFilter(IntEnum): ALL = 0 @@ -85,12 +89,13 @@ class AddressList(MyTreeView): ROLE_ADDRESS_STR = Qt.UserRole + 1001 key_role = ROLE_ADDRESS_STR - def __init__(self, parent): - super().__init__(parent, self.create_menu, - stretch_column=self.Columns.LABEL, - editable_columns=[self.Columns.LABEL]) - self.main_window = parent - self.wallet = self.parent.wallet + def __init__(self, main_window: 'ElectrumWindow'): + super().__init__( + main_window=main_window, + stretch_column=self.Columns.LABEL, + editable_columns=[self.Columns.LABEL], + ) + self.wallet = self.main_window.wallet self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) self.show_change = AddressTypeFilter.ALL # type: AddressTypeFilter @@ -120,7 +125,7 @@ class AddressList(MyTreeView): return toolbar def should_show_fiat(self): - return self.parent.fx and self.parent.fx.is_enabled() and self.config.get('fiat_address', False) + return self.main_window.fx and self.main_window.fx.is_enabled() and self.config.get('fiat_address', False) def get_toolbar_buttons(self): return self.change_button, self.used_button @@ -132,7 +137,7 @@ class AddressList(MyTreeView): def refresh_headers(self): if self.should_show_fiat(): - ccy = self.parent.fx.get_currency() + ccy = self.main_window.fx.get_currency() else: ccy = _('Fiat') headers = { @@ -171,7 +176,7 @@ class AddressList(MyTreeView): self.proxy.setDynamicSortFilter(False) # temp. disable re-sorting after every change self.std_model.clear() self.refresh_headers() - fx = self.parent.fx + fx = self.main_window.fx set_address = None num_shown = 0 self.addresses_beyond_gap_limit = self.wallet.get_all_known_addresses_beyond_gap_limit() @@ -236,9 +241,9 @@ class AddressList(MyTreeView): num = self.wallet.adb.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) + balance_text = self.main_window.format_amount(balance, whitespaces=True) # create item - fx = self.parent.fx + fx = self.main_window.fx if self.should_show_fiat(): rate = fx.exchange_rate() fiat_balance_str = fx.value_str(balance, rate) @@ -276,37 +281,37 @@ class AddressList(MyTreeView): addr_column_title = self.std_model.horizontalHeaderItem(self.Columns.LABEL).text() addr_idx = idx.sibling(idx.row(), self.Columns.LABEL) self.add_copy_menu(menu, idx) - menu.addAction(_('Details'), lambda: self.parent.show_address(addr)) + menu.addAction(_('Details'), lambda: self.main_window.show_address(addr)) persistent = QPersistentModelIndex(addr_idx) menu.addAction(_("Edit {}").format(addr_column_title), lambda p=persistent: self.edit(QModelIndex(p))) - #menu.addAction(_("Request payment"), lambda: self.parent.receive_at(addr)) + #menu.addAction(_("Request payment"), lambda: self.main_window.receive_at(addr)) if self.wallet.can_export(): - menu.addAction(_("Private key"), lambda: self.parent.show_private_key(addr)) + menu.addAction(_("Private key"), lambda: self.main_window.show_private_key(addr)) if not is_multisig and not self.wallet.is_watching_only(): - menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr)) - menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr)) + menu.addAction(_("Sign/verify message"), lambda: self.main_window.sign_verify_message(addr)) + menu.addAction(_("Encrypt/decrypt message"), lambda: self.main_window.encrypt_message(addr)) if can_delete: - menu.addAction(_("Remove from wallet"), lambda: self.parent.remove_address(addr)) + menu.addAction(_("Remove from wallet"), lambda: self.main_window.remove_address(addr)) addr_URL = block_explorer_URL(self.config, 'addr', addr) if addr_URL: menu.addAction(_("View on block explorer"), lambda: webopen(addr_URL)) if not self.wallet.is_frozen_address(addr): - menu.addAction(_("Freeze"), lambda: self.parent.set_frozen_state_of_addresses([addr], True)) + menu.addAction(_("Freeze"), lambda: self.main_window.set_frozen_state_of_addresses([addr], True)) else: - menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state_of_addresses([addr], False)) + menu.addAction(_("Unfreeze"), lambda: self.main_window.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)) + menu.addAction(_("Freeze"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, True)) + menu.addAction(_("Unfreeze"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, False)) coins = self.wallet.get_spendable_coins(addrs) if coins: - if self.parent.utxo_list.are_in_coincontrol(coins): - menu.addAction(_("Remove from coin control"), lambda: self.parent.utxo_list.remove_from_coincontrol(coins)) + if self.main_window.utxo_list.are_in_coincontrol(coins): + menu.addAction(_("Remove from coin control"), lambda: self.main_window.utxo_list.remove_from_coincontrol(coins)) else: - menu.addAction(_("Add to coin control"), lambda: self.parent.utxo_list.add_to_coincontrol(coins)) + menu.addAction(_("Add to coin control"), lambda: self.main_window.utxo_list.add_to_coincontrol(coins)) run_hook('receive_menu', menu, addrs, self.wallet) menu.exec_(self.viewport().mapToGlobal(position)) @@ -316,7 +321,7 @@ class AddressList(MyTreeView): try: self.wallet.check_address_for_corruption(text) except InternalAddressCorruption as e: - self.parent.show_error(str(e)) + self.main_window.show_error(str(e)) raise super().place_text_on_clipboard(text, title=title) @@ -326,7 +331,7 @@ class AddressList(MyTreeView): 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) - self.parent.history_model.refresh('address label edited') - self.parent.utxo_list.update() - self.parent.update_completions() + self.wallet.set_label(edit_key, text) + self.main_window.history_model.refresh('address label edited') + self.main_window.utxo_list.update() + self.main_window.update_completions() diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py index 2fca083ff..fe4f90708 100644 --- a/electrum/gui/qt/channels_list.py +++ b/electrum/gui/qt/channels_list.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import traceback import enum -from typing import Sequence, Optional, Dict +from typing import Sequence, Optional, Dict, TYPE_CHECKING from abc import abstractmethod, ABC from PyQt5 import QtCore, QtGui @@ -24,6 +24,9 @@ from .util import (MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButto from .amountedit import BTCAmountEdit, FreezableLineEdit from .util import read_QIcon, font_height +if TYPE_CHECKING: + from .main_window import ElectrumWindow + ROLE_CHANNEL_ID = Qt.UserRole @@ -63,16 +66,18 @@ class ChannelsList(MyTreeView): _default_item_bg_brush = None # type: Optional[QBrush] - def __init__(self, parent): - super().__init__(parent, self.create_menu, stretch_column=self.Columns.NODE_ALIAS) + def __init__(self, main_window: 'ElectrumWindow'): + super().__init__( + main_window=main_window, + stretch_column=self.Columns.NODE_ALIAS, + ) self.setModel(QtGui.QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.main_window = parent self.gossip_db_loaded.connect(self.on_gossip_db) self.update_rows.connect(self.do_update_rows) self.update_single_row.connect(self.do_update_single_row) - self.network = self.parent.network - self.wallet = self.parent.wallet + self.network = self.main_window.network + self.wallet = self.main_window.wallet self.setSortingEnabled(True) @property @@ -85,12 +90,12 @@ class ChannelsList(MyTreeView): for subject in (REMOTE, LOCAL): if isinstance(chan, Channel): can_send = chan.available_to_spend(subject) / 1000 - label = self.parent.format_amount(can_send, whitespaces=True) + label = self.main_window.format_amount(can_send, whitespaces=True) other = subject.inverted() bal_other = chan.balance(other)//1000 bal_minus_htlcs_other = chan.balance_minus_outgoing_htlcs(other)//1000 if bal_other != bal_minus_htlcs_other: - label += ' (+' + self.parent.format_amount(bal_other - bal_minus_htlcs_other, whitespaces=False) + ')' + label += ' (+' + self.main_window.format_amount(bal_other - bal_minus_htlcs_other, whitespaces=False) + ')' else: assert isinstance(chan, ChannelBackup) label = '' @@ -98,7 +103,7 @@ class ChannelsList(MyTreeView): status = chan.get_state_for_GUI() closed = chan.is_closed() node_alias = self.lnworker.get_node_alias(chan.node_id) or chan.node_id.hex() - capacity_str = self.parent.format_amount(chan.get_capacity(), whitespaces=True) + capacity_str = self.main_window.format_amount(chan.get_capacity(), whitespaces=True) return { self.Columns.SHORT_CHANID: chan.short_id_for_GUI(), self.Columns.LONG_CHANID: chan.channel_id.hex(), @@ -125,7 +130,7 @@ class ChannelsList(MyTreeView): self.is_force_close = False msg = _('Cooperative close?') msg += '\n' + _(messages.MSG_COOPERATIVE_CLOSE) - if not self.parent.question(msg): + if not self.main_window.question(msg): return coro = self.lnworker.close_channel(channel_id) on_success = self.on_channel_closed @@ -147,10 +152,10 @@ class ChannelsList(MyTreeView): + '' + _('Please create a backup of your wallet file!') + ' '\ + '

' + _('Funds in this channel will not be recoverable from seed until they are swept back into your wallet, and might be lost if you lose your wallet file.') + ' '\ + _('To prevent that, you should save a backup of your wallet on another device.') + '

' - if not self.parent.question(msg, title=_('Force-close channel'), rich_text=True, checkbox=backup_cb): + if not self.main_window.question(msg, title=_('Force-close channel'), rich_text=True, checkbox=backup_cb): return if self.save_backup: - if not self.parent.backup_wallet(): + if not self.main_window.backup_wallet(): return def task(): coro = self.lnworker.force_close_channel(channel_id) @@ -178,7 +183,7 @@ class ChannelsList(MyTreeView): def request_force_close(self, channel_id): msg = _('Request force-close from remote peer?') msg += '\n' + _(messages.MSG_REQUEST_FORCE_CLOSE) - if not self.parent.question(msg): + if not self.main_window.question(msg): return def task(): coro = self.lnworker.request_force_close(channel_id) @@ -208,9 +213,9 @@ class ChannelsList(MyTreeView): def on_rebalance(self): chan1, chan2 = self.get_rebalance_pair() if chan1 is None: - self.parent.show_error("Select two active channels to rebalance.") + self.main_window.show_error("Select two active channels to rebalance.") return - self.parent.rebalance_dialog(chan1, chan2) + self.main_window.rebalance_dialog(chan1, chan2) def create_menu(self, position): menu = QMenu() @@ -222,7 +227,7 @@ class ChannelsList(MyTreeView): if len(selected) == 2: chan1, chan2 = self.get_rebalance_pair() if chan1 and chan2: - menu.addAction(_("Rebalance channels"), lambda: self.parent.rebalance_dialog(chan1, chan2)) + menu.addAction(_("Rebalance channels"), lambda: self.main_window.rebalance_dialog(chan1, chan2)) menu.exec_(self.viewport().mapToGlobal(position)) return elif len(selected) > 2: @@ -235,7 +240,7 @@ class ChannelsList(MyTreeView): return channel_id = idx.sibling(idx.row(), self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID) chan = self.lnworker.get_channel_by_id(channel_id) or self.lnworker.channel_backups[channel_id] - menu.addAction(_("Details..."), lambda: self.parent.show_channel_details(chan)) + menu.addAction(_("Details..."), lambda: self.main_window.show_channel_details(chan)) menu.addSeparator() cc = self.add_copy_menu(menu, idx) cc.addAction(_("Node ID"), lambda: self.place_text_on_clipboard( @@ -272,7 +277,7 @@ class ChannelsList(MyTreeView): @QtCore.pyqtSlot(Abstract_Wallet, AbstractChannel) def do_update_single_row(self, wallet: Abstract_Wallet, chan: AbstractChannel): - if wallet != self.parent.wallet: + if wallet != self.wallet: return for row in range(self.model().rowCount()): item = self.model().item(row, self.Columns.NODE_ALIAS) @@ -287,11 +292,11 @@ class ChannelsList(MyTreeView): @QtCore.pyqtSlot() def on_gossip_db(self): - self.do_update_rows(self.parent.wallet) + self.do_update_rows(self.wallet) @QtCore.pyqtSlot(Abstract_Wallet) def do_update_rows(self, wallet): - if wallet != self.parent.wallet: + if wallet != self.wallet: return self.model().clear() self.update_headers(self.headers) @@ -337,29 +342,29 @@ class ChannelsList(MyTreeView): item.setToolTip("") def update_can_send(self, lnworker: LNWallet): - msg = _('Can send') + ' ' + self.parent.format_amount(lnworker.num_sats_can_send())\ - + ' ' + self.parent.base_unit() + '; '\ - + _('can receive') + ' ' + self.parent.format_amount(lnworker.num_sats_can_receive())\ - + ' ' + self.parent.base_unit() + msg = _('Can send') + ' ' + self.main_window.format_amount(lnworker.num_sats_can_send())\ + + ' ' + self.main_window.base_unit() + '; '\ + + _('can receive') + ' ' + self.main_window.format_amount(lnworker.num_sats_can_receive())\ + + ' ' + self.main_window.base_unit() self.can_send_label.setText(msg) def create_toolbar(self, config): toolbar, menu = self.create_toolbar_with_menu('') self.can_send_label = toolbar.itemAt(0).widget() menu.addAction(_('Rebalance channels'), lambda: self.on_rebalance()) - menu.addAction(_('Submarine swap'), lambda: self.parent.run_swap_dialog()) + menu.addAction(_('Submarine swap'), lambda: self.main_window.run_swap_dialog()) menu.addSeparator() - menu.addAction(_("Import channel backup"), lambda: self.parent.do_process_from_text_channel_backup()) + menu.addAction(_("Import channel backup"), lambda: self.main_window.do_process_from_text_channel_backup()) self.new_channel_button = EnterButton(_('New Channel'), self.new_channel_with_warning) - self.new_channel_button.setEnabled(self.parent.wallet.has_lightning()) + self.new_channel_button.setEnabled(self.wallet.has_lightning()) toolbar.insertWidget(2, self.new_channel_button) return toolbar def new_channel_with_warning(self): - lnworker = self.parent.wallet.lnworker + lnworker = self.wallet.lnworker if not lnworker.channels and not lnworker.channel_backups: warning = _(messages.MSG_LIGHTNING_WARNING) - answer = self.parent.question( + answer = self.main_window.question( _('Do you want to create your first channel?') + '\n\n' + warning) if answer: self.new_channel_dialog() @@ -367,9 +372,9 @@ class ChannelsList(MyTreeView): self.new_channel_dialog() def statistics_dialog(self): - channel_db = self.parent.network.channel_db - capacity = self.parent.format_amount(channel_db.capacity()) + ' '+ self.parent.base_unit() - d = WindowModalDialog(self.parent, _('Lightning Network Statistics')) + channel_db = self.network.channel_db + capacity = self.main_window.format_amount(channel_db.capacity()) + ' '+ self.main_window.base_unit() + d = WindowModalDialog(self.main_window, _('Lightning Network Statistics')) d.setMinimumWidth(400) vbox = QVBoxLayout(d) h = QGridLayout() @@ -385,7 +390,7 @@ class ChannelsList(MyTreeView): def new_channel_dialog(self, *, amount_sat=None, min_amount_sat=None): from .new_channel_dialog import NewChannelDialog - d = NewChannelDialog(self.parent, amount_sat, min_amount_sat) + d = NewChannelDialog(self.main_window, amount_sat, min_amount_sat) return d.run() def set_visibility_of_columns(self): diff --git a/electrum/gui/qt/contact_list.py b/electrum/gui/qt/contact_list.py index 291461f66..ff805feab 100644 --- a/electrum/gui/qt/contact_list.py +++ b/electrum/gui/qt/contact_list.py @@ -24,6 +24,7 @@ # SOFTWARE. import enum +from typing import TYPE_CHECKING from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex @@ -36,6 +37,9 @@ from electrum.plugin import run_hook from .util import MyTreeView, webopen +if TYPE_CHECKING: + from .main_window import ElectrumWindow + class ContactList(MyTreeView): @@ -52,10 +56,12 @@ class ContactList(MyTreeView): ROLE_CONTACT_KEY = Qt.UserRole + 1000 key_role = ROLE_CONTACT_KEY - def __init__(self, parent): - super().__init__(parent, self.create_menu, - stretch_column=self.Columns.NAME, - editable_columns=[self.Columns.NAME]) + def __init__(self, main_window: 'ElectrumWindow'): + super().__init__( + main_window=main_window, + stretch_column=self.Columns.NAME, + editable_columns=[self.Columns.NAME], + ) self.setModel(QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) @@ -63,8 +69,8 @@ class ContactList(MyTreeView): self.update() def on_edited(self, idx, edit_key, *, text): - _type, prior_name = self.parent.contacts.pop(edit_key) - self.parent.set_contact(text, edit_key) + _type, prior_name = self.main_window.contacts.pop(edit_key) + self.main_window.set_contact(text, edit_key) self.update() def create_menu(self, position): @@ -86,8 +92,8 @@ class ContactList(MyTreeView): # would not be editable if openalias persistent = QPersistentModelIndex(idx) menu.addAction(_("Edit {}").format(column_title), lambda p=persistent: self.edit(QModelIndex(p))) - menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(selected_keys)) - menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(selected_keys)) + menu.addAction(_("Pay to"), lambda: self.main_window.payto_contacts(selected_keys)) + menu.addAction(_("Delete"), lambda: self.main_window.delete_contacts(selected_keys)) URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, selected_keys)] if URLs: menu.addAction(_("View on block explorer"), lambda: [webopen(u) for u in URLs]) @@ -102,8 +108,8 @@ class ContactList(MyTreeView): self.model().clear() self.update_headers(self.__class__.headers) set_current = None - for key in sorted(self.parent.contacts.keys()): - contact_type, name = self.parent.contacts[key] + for key in sorted(self.main_window.contacts.keys()): + contact_type, name = self.main_window.contacts[key] items = [QStandardItem(x) for x in (name, key)] items[self.Columns.NAME].setEditable(contact_type != 'openalias') items[self.Columns.ADDRESS].setEditable(False) @@ -130,7 +136,7 @@ class ContactList(MyTreeView): def create_toolbar(self, config): toolbar, menu = self.create_toolbar_with_menu('') - menu.addAction(_("&New contact"), self.parent.new_contact_dialog) - menu.addAction(_("Import"), lambda: self.parent.import_contacts()) - menu.addAction(_("Export"), lambda: self.parent.export_contacts()) + menu.addAction(_("&New contact"), self.main_window.new_contact_dialog) + menu.addAction(_("Import"), lambda: self.main_window.import_contacts()) + menu.addAction(_("Export"), lambda: self.main_window.export_contacts()) return toolbar diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py index ab89a8685..dd4a76056 100644 --- a/electrum/gui/qt/history_list.py +++ b/electrum/gui/qt/history_list.py @@ -486,12 +486,12 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): return True return False - def __init__(self, parent, model: HistoryModel): - super().__init__(parent, self.create_menu, - stretch_column=HistoryColumns.DESCRIPTION, - editable_columns=[HistoryColumns.DESCRIPTION, HistoryColumns.FIAT_VALUE]) - self.main_window = parent - self.config = parent.config + def __init__(self, main_window: 'ElectrumWindow', model: HistoryModel): + super().__init__( + main_window=main_window, + stretch_column=HistoryColumns.DESCRIPTION, + editable_columns=[HistoryColumns.DESCRIPTION, HistoryColumns.FIAT_VALUE], + ) self.hm = model self.proxy = HistorySortModel(self) self.proxy.setSourceModel(model) @@ -510,7 +510,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): self.end_button.setEnabled(False) self.period_combo.addItems([_('All'), _('Custom')]) self.period_combo.activated.connect(self.on_combo) - self.wallet = self.parent.wallet # type: Abstract_Wallet + self.wallet = self.main_window.wallet # type: Abstract_Wallet self.sortByColumn(HistoryColumns.STATUS, Qt.AscendingOrder) self.setRootIsDecorated(True) self.header().setStretchLastSection(False) @@ -603,24 +603,24 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): def show_summary(self): if not self.hm.should_show_fiat(): - self.parent.show_message(_("Enable fiat exchange rate with history.")) + self.main_window.show_message(_("Enable fiat exchange rate with history.")) return - fx = self.parent.fx + fx = self.main_window.fx h = self.wallet.get_detailed_history( from_timestamp = time.mktime(self.start_date.timetuple()) if self.start_date else None, to_timestamp = time.mktime(self.end_date.timetuple()) if self.end_date else None, fx=fx) summary = h['summary'] if not summary: - self.parent.show_message(_("Nothing to summarize.")) + self.main_window.show_message(_("Nothing to summarize.")) return start = summary['begin'] end = summary['end'] flow = summary['flow'] start_date = start.get('date') end_date = end.get('date') - format_amount = lambda x: self.parent.format_amount(x.value) + ' ' + self.parent.base_unit() - format_fiat = lambda x: str(x) + ' ' + self.parent.fx.ccy + format_amount = lambda x: self.main_window.format_amount(x.value) + ' ' + self.main_window.base_unit() + format_fiat = lambda x: str(x) + ' ' + self.main_window.fx.ccy d = WindowModalDialog(self, _("Summary")) d.setMinimumSize(600, 150) @@ -679,7 +679,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): from electrum.plot import plot_history, NothingToPlotException except Exception as e: _logger.error(f"could not import electrum.plot. This feature needs matplotlib to be installed. exc={e!r}") - self.parent.show_message( + self.main_window.show_message( _("Can't plot history.") + '\n' + _("Perhaps some dependencies are missing...") + " (matplotlib?)" + '\n' + f"Error: {e!r}" @@ -689,7 +689,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): plt = plot_history(list(self.hm.transactions.values())) plt.show() except NothingToPlotException as e: - self.parent.show_message(str(e)) + self.main_window.show_message(str(e)) def on_edited(self, idx, edit_key, *, text): index = self.model().mapToSource(idx) @@ -699,9 +699,9 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): if column == HistoryColumns.DESCRIPTION: if self.wallet.set_label(key, text): #changed self.hm.update_label(index) - self.parent.update_completions() + self.main_window.update_completions() elif column == HistoryColumns.FIAT_VALUE: - self.wallet.set_fiat_value(key, self.parent.fx.ccy, text, self.parent.fx, tx_item['value'].value) + self.wallet.set_fiat_value(key, self.main_window.fx.ccy, text, self.main_window.fx, tx_item['value'].value) value = tx_item['value'].value if value is not None: self.hm.update_fiat(index) @@ -720,13 +720,13 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): else: if tx_item.get('lightning'): if tx_item['type'] == 'payment': - self.parent.show_lightning_transaction(tx_item) + self.main_window.show_lightning_transaction(tx_item) return tx_hash = tx_item['txid'] tx = self.wallet.adb.get_transaction(tx_hash) if not tx: return - self.parent.show_transaction(tx) + self.main_window.show_transaction(tx) def add_copy_menu(self, menu, idx): cc = menu.addMenu(_("Copy")) @@ -751,14 +751,14 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): tx_item = idx.internalPointer().get_data() if tx_item.get('lightning') and tx_item['type'] == 'payment': menu = QMenu() - menu.addAction(_("View Payment"), lambda: self.parent.show_lightning_transaction(tx_item)) + menu.addAction(_("View Payment"), lambda: self.main_window.show_lightning_transaction(tx_item)) cc = self.add_copy_menu(menu, idx) cc.addAction(_("Payment Hash"), lambda: self.place_text_on_clipboard(tx_item['payment_hash'], title="Payment Hash")) cc.addAction(_("Preimage"), lambda: self.place_text_on_clipboard(tx_item['preimage'], title="Preimage")) key = tx_item['payment_hash'] log = self.wallet.lnworker.logs.get(key) if log: - menu.addAction(_("View log"), lambda: self.parent.send_tab.invoice_list.show_log(key, log)) + menu.addAction(_("View log"), lambda: self.main_window.send_tab.invoice_list.show_log(key, log)) menu.exec_(self.viewport().mapToGlobal(position)) return tx_hash = tx_item['txid'] @@ -780,25 +780,25 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): # TODO use siblingAtColumn when min Qt version is >=5.11 persistent = QPersistentModelIndex(org_idx.sibling(org_idx.row(), c)) menu_edit.addAction(_("{}").format(label), lambda p=persistent: self.edit(QModelIndex(p))) - menu.addAction(_("View Transaction"), lambda: self.parent.show_transaction(tx)) + menu.addAction(_("View Transaction"), lambda: self.main_window.show_transaction(tx)) channel_id = tx_item.get('channel_id') if channel_id and self.wallet.lnworker and (chan := self.wallet.lnworker.get_channel_by_id(bytes.fromhex(channel_id))): - menu.addAction(_("View Channel"), lambda: self.parent.show_channel_details(chan)) + menu.addAction(_("View Channel"), lambda: self.main_window.show_channel_details(chan)) if is_unconfirmed and tx: if tx_details.can_bump: - menu.addAction(_("Increase fee"), lambda: self.parent.bump_fee_dialog(tx)) + menu.addAction(_("Increase fee"), lambda: self.main_window.bump_fee_dialog(tx)) else: if tx_details.can_cpfp: - menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp_dialog(tx)) + menu.addAction(_("Child pays for parent"), lambda: self.main_window.cpfp_dialog(tx)) if tx_details.can_dscancel: - menu.addAction(_("Cancel (double-spend)"), lambda: self.parent.dscancel_dialog(tx)) + menu.addAction(_("Cancel (double-spend)"), lambda: self.main_window.dscancel_dialog(tx)) invoices = self.wallet.get_relevant_invoices_for_tx(tx_hash) if len(invoices) == 1: - menu.addAction(_("View invoice"), lambda inv=invoices[0]: self.parent.show_onchain_invoice(inv)) + menu.addAction(_("View invoice"), lambda inv=invoices[0]: self.main_window.show_onchain_invoice(inv)) elif len(invoices) > 1: menu_invs = menu.addMenu(_("Related invoices")) for inv in invoices: - menu_invs.addAction(_("View invoice"), lambda inv=inv: self.parent.show_onchain_invoice(inv)) + menu_invs.addAction(_("View invoice"), lambda inv=inv: self.main_window.show_onchain_invoice(inv)) if tx_URL: menu.addAction(_("View on block explorer"), lambda: webopen(tx_URL)) menu.exec_(self.viewport().mapToGlobal(position)) @@ -809,24 +809,24 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): if num_child_txs > 0: question = (_("Are you sure you want to remove this transaction and {} child transactions?") .format(num_child_txs)) - if not self.parent.question(msg=question, + if not self.main_window.question(msg=question, title=_("Please confirm")): return self.wallet.adb.remove_transaction(tx_hash) self.wallet.save_db() # need to update at least: history_list, utxo_list, address_list - self.parent.need_update.set() + self.main_window.need_update.set() def onFileAdded(self, fn): try: with open(fn) as f: - tx = self.parent.tx_from_text(f.read()) + tx = self.main_window.tx_from_text(f.read()) except IOError as e: - self.parent.show_error(e) + self.main_window.show_error(e) return if not tx: return - self.parent.save_transaction_into_wallet(tx) + self.main_window.save_transaction_into_wallet(tx) def export_history_dialog(self): d = WindowModalDialog(self, _('Export History')) @@ -850,12 +850,12 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): self.do_export_history(filename, csv_button.isChecked()) except (IOError, os.error) as reason: export_error_label = _("Electrum was unable to produce a transaction export.") - self.parent.show_critical(export_error_label + "\n" + str(reason), title=_("Unable to export history")) + self.main_window.show_critical(export_error_label + "\n" + str(reason), title=_("Unable to export history")) return - self.parent.show_message(_("Your wallet history has been successfully exported.")) + self.main_window.show_message(_("Your wallet history has been successfully exported.")) def do_export_history(self, file_name, is_csv): - hist = self.wallet.get_detailed_history(fx=self.parent.fx) + hist = self.wallet.get_detailed_history(fx=self.main_window.fx) txns = hist['transactions'] lines = [] if is_csv: diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py index f1d6078eb..0ef2b8095 100644 --- a/electrum/gui/qt/invoice_list.py +++ b/electrum/gui/qt/invoice_list.py @@ -69,8 +69,10 @@ class InvoiceList(MyTreeView): def __init__(self, send_tab: 'SendTab'): window = send_tab.window - super().__init__(window, self.create_menu, - stretch_column=self.Columns.DESCRIPTION) + super().__init__( + main_window=window, + stretch_column=self.Columns.DESCRIPTION, + ) self.wallet = window.wallet self.send_tab = send_tab self.std_model = QStandardItemModel(self) @@ -115,7 +117,7 @@ class InvoiceList(MyTreeView): labels = [""] * len(self.Columns) labels[self.Columns.DATE] = format_time(timestamp) if timestamp else _('Unknown') labels[self.Columns.DESCRIPTION] = item.message - labels[self.Columns.AMOUNT] = self.parent.format_amount(amount, whitespaces=True) + labels[self.Columns.AMOUNT] = self.main_window.format_amount(amount, whitespaces=True) labels[self.Columns.STATUS] = item.get_status_str(status) items = [QStandardItem(e) for e in labels] self.set_editability(items) @@ -160,11 +162,11 @@ class InvoiceList(MyTreeView): copy_menu = self.add_copy_menu(menu, idx) address = invoice.get_address() if address: - copy_menu.addAction(_("Address"), lambda: self.parent.do_copy(invoice.get_address(), title='Bitcoin Address')) + copy_menu.addAction(_("Address"), lambda: self.main_window.do_copy(invoice.get_address(), title='Bitcoin Address')) if invoice.is_lightning(): - menu.addAction(_("Details"), lambda: self.parent.show_lightning_invoice(invoice)) + menu.addAction(_("Details"), lambda: self.main_window.show_lightning_invoice(invoice)) else: - menu.addAction(_("Details"), lambda: self.parent.show_onchain_invoice(invoice)) + menu.addAction(_("Details"), lambda: self.main_window.show_onchain_invoice(invoice)) status = wallet.get_invoice_status(invoice) if status == PR_UNPAID: menu.addAction(_("Pay") + "...", lambda: self.send_tab.do_pay_invoice(invoice)) diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py index 8fe5a7898..f9fb2ff14 100644 --- a/electrum/gui/qt/request_list.py +++ b/electrum/gui/qt/request_list.py @@ -73,8 +73,10 @@ class RequestList(MyTreeView): def __init__(self, receive_tab: 'ReceiveTab'): window = receive_tab.window - super().__init__(window, self.create_menu, - stretch_column=self.Columns.DESCRIPTION) + super().__init__( + main_window=window, + stretch_column=self.Columns.DESCRIPTION, + ) self.wallet = window.wallet self.receive_tab = receive_tab self.std_model = QStandardItemModel(self) @@ -141,7 +143,7 @@ class RequestList(MyTreeView): amount = req.get_amount_sat() message = req.get_message() date = format_time(timestamp) - amount_str = self.parent.format_amount(amount) if amount else "" + amount_str = self.main_window.format_amount(amount) if amount else "" labels = [""] * len(self.Columns) labels[self.Columns.DATE] = date labels[self.Columns.DESCRIPTION] = message @@ -193,15 +195,15 @@ class RequestList(MyTreeView): menu = QMenu(self) copy_menu = self.add_copy_menu(menu, idx) if req.get_address(): - copy_menu.addAction(_("Address"), lambda: self.parent.do_copy(req.get_address(), title='Bitcoin Address')) + copy_menu.addAction(_("Address"), lambda: self.main_window.do_copy(req.get_address(), title='Bitcoin Address')) if URI := self.wallet.get_request_URI(req): - copy_menu.addAction(_("Bitcoin URI"), lambda: self.parent.do_copy(URI, title='Bitcoin URI')) + copy_menu.addAction(_("Bitcoin URI"), lambda: self.main_window.do_copy(URI, title='Bitcoin URI')) if req.is_lightning(): - copy_menu.addAction(_("Lightning Request"), lambda: self.parent.do_copy(self.wallet.get_bolt11_invoice(req), title='Lightning Request')) + copy_menu.addAction(_("Lightning Request"), lambda: self.main_window.do_copy(self.wallet.get_bolt11_invoice(req), title='Lightning Request')) #if 'view_url' in req: # menu.addAction(_("View in web browser"), lambda: webopen(req['view_url'])) menu.addAction(_("Delete"), lambda: self.delete_requests([key])) - run_hook('receive_list_menu', self.parent, menu, key) + run_hook('receive_list_menu', self.main_window, menu, key) menu.exec_(self.viewport().mapToGlobal(position)) def delete_requests(self, keys): diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 26d15a0c3..b82b060bd 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -626,14 +626,21 @@ class MyTreeView(QTreeView): Columns: Type[BaseColumnsEnum] - def __init__(self, parent: 'ElectrumWindow', create_menu, *, - stretch_column=None, editable_columns=None): + def __init__( + self, + *, + parent: Optional[QWidget] = None, + main_window: Optional['ElectrumWindow'] = None, + stretch_column: Optional[int] = None, + editable_columns: Optional[Sequence[int]] = None, + ): + parent = parent or main_window super().__init__(parent) - self.parent = parent - self.config = self.parent.config + self.main_window = main_window + self.config = self.main_window.config if self.main_window else None self.stretch_column = stretch_column self.setContextMenuPolicy(Qt.CustomContextMenu) - self.customContextMenuRequested.connect(create_menu) + self.customContextMenuRequested.connect(self.create_menu) self.setUniformRowHeights(True) # Control which columns are editable @@ -659,6 +666,9 @@ class MyTreeView(QTreeView): self._default_bg_brush = QStandardItem().background() + def create_menu(self, position: QPoint) -> None: + pass + def set_editability(self, items): for idx, i in enumerate(items): i.setEditable(idx in self.editable_columns) @@ -836,7 +846,7 @@ class MyTreeView(QTreeView): return cc def place_text_on_clipboard(self, text: str, *, title: str = None) -> None: - self.parent.do_copy(text, title=title) + self.main_window.do_copy(text, title=title) def showEvent(self, e: 'QShowEvent'): super().showEvent(e) diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py index 4039928da..e4361394b 100644 --- a/electrum/gui/qt/utxo_list.py +++ b/electrum/gui/qt/utxo_list.py @@ -23,7 +23,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from typing import Optional, List, Dict, Sequence, Set +from typing import Optional, List, Dict, Sequence, Set, TYPE_CHECKING import enum import copy @@ -39,6 +39,9 @@ from electrum.lnutil import LN_MAX_FUNDING_SAT, MIN_FUNDING_SAT from .util import MyTreeView, ColorScheme, MONOSPACE_FONT, EnterButton from .new_channel_dialog import NewChannelDialog +if TYPE_CHECKING: + from .main_window import ElectrumWindow + class UTXOList(MyTreeView): _spend_set: Set[str] # coins selected by the user to spend from @@ -64,12 +67,14 @@ class UTXOList(MyTreeView): ROLE_PREVOUT_STR = Qt.UserRole + 1000 key_role = ROLE_PREVOUT_STR - def __init__(self, parent): - super().__init__(parent, self.create_menu, - stretch_column=self.stretch_column) + def __init__(self, main_window: 'ElectrumWindow'): + super().__init__( + main_window=main_window, + stretch_column=self.stretch_column, + ) self._spend_set = set() self._utxo_dict = {} - self.wallet = self.parent.wallet + self.wallet = self.main_window.wallet self.std_model = QStandardItemModel(self) self.setModel(self.std_model) self.setSelectionMode(QAbstractItemView.ExtendedSelection) @@ -95,7 +100,7 @@ class UTXOList(MyTreeView): labels = [""] * len(self.Columns) labels[self.Columns.OUTPOINT] = str(utxo.short_id) labels[self.Columns.ADDRESS] = utxo.address - labels[self.Columns.AMOUNT] = self.parent.format_amount(utxo.value_sats(), whitespaces=True) + labels[self.Columns.AMOUNT] = self.main_window.format_amount(utxo.value_sats(), whitespaces=True) utxo_item = [QStandardItem(x) for x in labels] self.set_editability(utxo_item) utxo_item[self.Columns.OUTPOINT].setData(name, self.ROLE_PREVOUT_STR) @@ -115,11 +120,11 @@ class UTXOList(MyTreeView): 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) + amount_str = self.main_window.format_amount_and_units(amount) 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}') + self.main_window.set_coincontrol_msg(_("Coin control active") + f': {num_outputs_str}, {amount_str}') else: - self.parent.set_coincontrol_msg(None) + self.main_window.set_coincontrol_msg(None) def refresh_row(self, key, row): assert row is not None @@ -184,7 +189,7 @@ class UTXOList(MyTreeView): selected = self.get_selected_outpoints() coins = [self._utxo_dict[name] for name in selected] if not coins: - self.parent.show_error(_('You need to select coins from the list first.\nUse ctrl+left mouse button to select multiple items')) + self.main_window.show_error(_('You need to select coins from the list first.\nUse ctrl+left mouse button to select multiple items')) return self.add_to_coincontrol(coins) @@ -223,7 +228,7 @@ class UTXOList(MyTreeView): def swap_coins(self, coins): #self.clear_coincontrol() self.add_to_coincontrol(coins) - self.parent.run_swap_dialog(is_reverse=False, recv_amount_sat='!') + self.main_window.run_swap_dialog(is_reverse=False, recv_amount_sat='!') self.clear_coincontrol() def can_open_channel(self, coins): @@ -236,7 +241,7 @@ class UTXOList(MyTreeView): # todo : use a single dialog in new flow #self.clear_coincontrol() self.add_to_coincontrol(coins) - d = NewChannelDialog(self.parent) + d = NewChannelDialog(self.main_window) d.max_button.setChecked(True) d.max_button.setEnabled(False) d.min_button.setEnabled(False) @@ -247,15 +252,15 @@ class UTXOList(MyTreeView): self.clear_coincontrol() def clipboard_contains_address(self): - text = self.parent.app.clipboard().text() + text = self.main_window.app.clipboard().text() return is_address(text) def pay_to_clipboard_address(self, coins): - addr = self.parent.app.clipboard().text() + addr = self.main_window.app.clipboard().text() outputs = [PartialTxOutput.from_address_and_value(addr, '!')] #self.clear_coincontrol() self.add_to_coincontrol(coins) - self.parent.send_tab.pay_onchain_dialog(outputs) + self.main_window.send_tab.pay_onchain_dialog(outputs) self.clear_coincontrol() def create_menu(self, position): @@ -277,7 +282,7 @@ class UTXOList(MyTreeView): tx = self.wallet.adb.get_transaction(txid) if tx: label = self.wallet.get_label_for_txid(txid) - menu.addAction(_("Privacy analysis"), lambda: self.parent.show_utxo(utxo)) + menu.addAction(_("Privacy analysis"), lambda: self.main_window.show_utxo(utxo)) # fully spend menu_spend = menu.addMenu(_("Fully spend") + '…') m = menu_spend.addAction(_("send to address in clipboard"), lambda: self.pay_to_clipboard_address(coins)) @@ -297,13 +302,13 @@ class UTXOList(MyTreeView): addr = utxo.address menu_freeze = menu.addMenu(_("Freeze")) if not self.wallet.is_frozen_coin(utxo): - menu_freeze.addAction(_("Freeze Coin"), lambda: self.parent.set_frozen_state_of_coins([utxo], True)) + menu_freeze.addAction(_("Freeze Coin"), lambda: self.main_window.set_frozen_state_of_coins([utxo], True)) else: - menu_freeze.addAction(_("Unfreeze Coin"), lambda: self.parent.set_frozen_state_of_coins([utxo], False)) + menu_freeze.addAction(_("Unfreeze Coin"), lambda: self.main_window.set_frozen_state_of_coins([utxo], False)) if not self.wallet.is_frozen_address(addr): - menu_freeze.addAction(_("Freeze Address"), lambda: self.parent.set_frozen_state_of_addresses([addr], True)) + menu_freeze.addAction(_("Freeze Address"), lambda: self.main_window.set_frozen_state_of_addresses([addr], True)) else: - menu_freeze.addAction(_("Unfreeze Address"), lambda: self.parent.set_frozen_state_of_addresses([addr], False)) + menu_freeze.addAction(_("Unfreeze Address"), lambda: self.main_window.set_frozen_state_of_addresses([addr], False)) elif len(coins) > 1: # multiple items selected menu.addSeparator() addrs = [utxo.address for utxo in coins] @@ -311,13 +316,13 @@ class UTXOList(MyTreeView): is_addr_frozen = [self.wallet.is_frozen_address(utxo.address) for utxo in coins] menu_freeze = menu.addMenu(_("Freeze")) if not all(is_coin_frozen): - menu_freeze.addAction(_("Freeze Coins"), lambda: self.parent.set_frozen_state_of_coins(coins, True)) + menu_freeze.addAction(_("Freeze Coins"), lambda: self.main_window.set_frozen_state_of_coins(coins, True)) if any(is_coin_frozen): - menu_freeze.addAction(_("Unfreeze Coins"), lambda: self.parent.set_frozen_state_of_coins(coins, False)) + menu_freeze.addAction(_("Unfreeze Coins"), lambda: self.main_window.set_frozen_state_of_coins(coins, False)) if not all(is_addr_frozen): - menu_freeze.addAction(_("Freeze Addresses"), lambda: self.parent.set_frozen_state_of_addresses(addrs, True)) + menu_freeze.addAction(_("Freeze Addresses"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, True)) if any(is_addr_frozen): - menu_freeze.addAction(_("Unfreeze Addresses"), lambda: self.parent.set_frozen_state_of_addresses(addrs, False)) + menu_freeze.addAction(_("Unfreeze Addresses"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, False)) menu.exec_(self.viewport().mapToGlobal(position)) diff --git a/electrum/gui/qt/watchtower_dialog.py b/electrum/gui/qt/watchtower_dialog.py index 89033ae9b..a1083b5d9 100644 --- a/electrum/gui/qt/watchtower_dialog.py +++ b/electrum/gui/qt/watchtower_dialog.py @@ -32,15 +32,16 @@ from .util import MyTreeView, Buttons class WatcherList(MyTreeView): - def __init__(self, parent): - super().__init__(parent, self.create_menu, stretch_column=0) + def __init__(self, parent: 'WatchtowerDialog'): + super().__init__( + parent=parent, + stretch_column=0, + ) + self.parent = parent self.setModel(QStandardItemModel(self)) self.setSortingEnabled(True) self.update() - def create_menu(self, x): - pass - def update(self): if self.parent.lnwatcher is None: return