Browse Source

whitespace, imports, code style

master
Sander van Grieken 2 years ago
parent
commit
190c19d48c
  1. 9
      electrum/gui/qml/__init__.py
  2. 10
      electrum/gui/qml/auth.py
  3. 44
      electrum/gui/qml/qeaddresslistmodel.py
  4. 3
      electrum/gui/qml/qebip39recovery.py
  5. 5
      electrum/gui/qml/qebitcoin.py
  6. 2
      electrum/gui/qml/qechanneldetails.py
  7. 50
      electrum/gui/qml/qechannellistmodel.py
  8. 28
      electrum/gui/qml/qechannelopener.py
  9. 11
      electrum/gui/qml/qeconfig.py
  10. 28
      electrum/gui/qml/qedaemon.py
  11. 3
      electrum/gui/qml/qefx.py
  12. 13
      electrum/gui/qml/qeinvoice.py
  13. 34
      electrum/gui/qml/qeinvoicelistmodel.py
  14. 4
      electrum/gui/qml/qelnpaymentdetails.py
  15. 14
      electrum/gui/qml/qenetwork.py
  16. 16
      electrum/gui/qml/qeqr.py
  17. 3
      electrum/gui/qml/qerequestdetails.py
  18. 45
      electrum/gui/qml/qeserverlistmodel.py
  19. 25
      electrum/gui/qml/qetransactionlistmodel.py
  20. 9
      electrum/gui/qml/qetxdetails.py
  21. 32
      electrum/gui/qml/qetxfinalizer.py
  22. 9
      electrum/gui/qml/qetypes.py
  23. 43
      electrum/gui/qml/qewallet.py
  24. 3
      electrum/gui/qml/qewalletdb.py
  25. 50
      electrum/gui/qml/qewizard.py
  26. 6
      electrum/gui/qml/util.py
  27. 4
      electrum/gui/qt/wizard/wizard.py

9
electrum/gui/qml/__init__.py

@ -2,7 +2,6 @@ import os
import signal
import sys
import threading
import traceback
from typing import TYPE_CHECKING
try:
@ -15,11 +14,10 @@ try:
except Exception:
sys.exit("Error: Could not import PyQt5.QtQml on Linux systems, you may try 'sudo apt-get install python3-pyqt5.qtquick'")
from PyQt5.QtCore import (Qt, QCoreApplication, QObject, QLocale, QTranslator, QTimer, pyqtSignal,
QT_VERSION_STR, PYQT_VERSION_STR)
from PyQt5.QtCore import (Qt, QCoreApplication, QLocale, QTranslator, QTimer, QT_VERSION_STR, PYQT_VERSION_STR)
from PyQt5.QtGui import QGuiApplication
from electrum.i18n import _, set_language, languages
from electrum.i18n import _
from electrum.plugin import run_hook
from electrum.util import profiler
from electrum.logging import Logger
@ -29,7 +27,6 @@ if TYPE_CHECKING:
from electrum.daemon import Daemon
from electrum.simple_config import SimpleConfig
from electrum.plugin import Plugins
from electrum.wallet import Abstract_Wallet
from .qeapp import ElectrumQmlApplication, Exception_Hook
@ -86,7 +83,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
self.timer = QTimer(self.app)
self.timer.setSingleShot(False)
self.timer.setInterval(500) # msec
self.timer.timeout.connect(lambda: None) # periodically enter python scope
self.timer.timeout.connect(lambda: None) # periodically enter python scope
# hook for crash reporter
Exception_Hook.maybe_setup(config=config, slot=self.app.appController.crash)

10
electrum/gui/qml/auth.py

@ -1,9 +1,10 @@
from functools import wraps, partial
from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from electrum.logging import get_logger
def auth_protect(func=None, reject=None, method='pin', message=''):
if func is None:
return partial(auth_protect, reject=reject, method=method, message=message)
@ -20,6 +21,7 @@ def auth_protect(func=None, reject=None, method='pin', message=''):
return wrapper
class AuthMixin:
_auth_logger = get_logger(__name__)
authRequired = pyqtSignal([str, str], arguments=['method', 'authMessage'])
@ -29,14 +31,14 @@ class AuthMixin:
self._auth_logger.debug('Proceeding with authed fn()')
try:
self._auth_logger.debug(str(getattr(self, '__auth_fcall')))
(func,args,kwargs,reject) = getattr(self, '__auth_fcall')
(func, args, kwargs, reject) = getattr(self, '__auth_fcall')
r = func(self, *args, **kwargs)
return r
except Exception as e:
self._auth_logger.error(f'Error executing wrapped fn(): {repr(e)}')
raise e
finally:
delattr(self,'__auth_fcall')
delattr(self, '__auth_fcall')
@pyqtSlot()
def authCancel(self):
@ -45,7 +47,7 @@ class AuthMixin:
return
try:
(func,args,kwargs,reject) = getattr(self, '__auth_fcall')
(func, args, kwargs, reject) = getattr(self, '__auth_fcall')
if reject is not None:
if hasattr(self, reject):
getattr(self, reject)()

44
electrum/gui/qml/qeaddresslistmodel.py

@ -1,7 +1,7 @@
import itertools
from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
from electrum.logging import get_logger
@ -17,27 +17,30 @@ class QEAddressListModel(QAbstractListModel):
_logger = get_logger(__name__)
# define listmodel rolemap
_ROLE_NAMES=('type','iaddr','address','label','balance','numtx', 'held')
_ROLE_NAMES=('type', 'iaddr', 'address', 'label', 'balance', 'numtx', 'held')
_ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
def __init__(self, wallet: 'Abstract_Wallet', parent=None):
super().__init__(parent)
self.wallet = wallet
self.setDirty()
self._receive_addresses = []
self._change_addresses = []
self._dirty = True
self.initModel()
def rowCount(self, index):
return len(self.receive_addresses) + len(self.change_addresses)
return len(self._receive_addresses) + len(self._change_addresses)
def roleNames(self):
return self._ROLE_MAP
def data(self, index, role):
if index.row() > len(self.receive_addresses) - 1:
address = self.change_addresses[index.row() - len(self.receive_addresses)]
if index.row() > len(self._receive_addresses) - 1:
address = self._change_addresses[index.row() - len(self._receive_addresses)]
else:
address = self.receive_addresses[index.row()]
address = self._receive_addresses[index.row()]
role_index = role - Qt.UserRole
value = address[self._ROLE_NAMES[role_index]]
if isinstance(value, (bool, list, int, str, QEAmount)) or value is None:
@ -48,18 +51,19 @@ class QEAddressListModel(QAbstractListModel):
def clear(self):
self.beginResetModel()
self.receive_addresses = []
self.change_addresses = []
self._receive_addresses = []
self._change_addresses = []
self.endResetModel()
def addr_to_model(self, address):
item = {}
item['address'] = address
item['numtx'] = self.wallet.adb.get_address_history_len(address)
item['label'] = self.wallet.get_label_for_address(address)
c, u, x = self.wallet.get_addr_balance(address)
item['balance'] = QEAmount(amount_sat=c + u + x)
item['held'] = self.wallet.is_frozen_address(address)
item = {
'address': address,
'numtx': self.wallet.adb.get_address_history_len(address),
'label': self.wallet.get_label_for_address(address),
'balance': QEAmount(amount_sat=c + u + x),
'held': self.wallet.is_frozen_address(address)
}
return item
@pyqtSlot()
@ -86,21 +90,21 @@ class QEAddressListModel(QAbstractListModel):
self.beginInsertRows(QModelIndex(), 0, n_addresses - 1)
if self.wallet.wallet_type != 'imported':
for i, address in enumerate(r_addresses):
insert_row('receive', self.receive_addresses, address, i)
insert_row('receive', self._receive_addresses, address, i)
for i, address in enumerate(c_addresses):
insert_row('change', self.change_addresses, address, i)
insert_row('change', self._change_addresses, address, i)
else:
for i, address in enumerate(r_addresses):
insert_row('imported', self.receive_addresses, address, i)
insert_row('imported', self._receive_addresses, address, i)
self.endInsertRows()
self._dirty = False
@pyqtSlot(str)
def updateAddress(self, address):
for i, a in enumerate(itertools.chain(self.receive_addresses, self.change_addresses)):
for i, a in enumerate(itertools.chain(self._receive_addresses, self._change_addresses)):
if a['address'] == address:
self.do_update(i,a)
self.do_update(i, a)
return
def do_update(self, modelindex, modelitem):

3
electrum/gui/qml/qebip39recovery.py

@ -26,7 +26,6 @@ class QEBip39RecoveryListModel(QAbstractListModel):
recoveryFailed = pyqtSignal()
stateChanged = pyqtSignal()
# userinfoChanged = pyqtSignal()
# define listmodel rolemap
_ROLE_NAMES=('description', 'derivation_path', 'script_type')
@ -112,7 +111,7 @@ class QEBip39RecoveryListModel(QAbstractListModel):
if isinstance(e, concurrent.futures.CancelledError):
self.state = QEBip39RecoveryListModel.State.Cancelled
return
self._logger.error(f"recovery error", exc_info=exc_info)
self._logger.error(f'recovery error', exc_info=exc_info)
self.state = QEBip39RecoveryListModel.State.Failed
self._thread.stop()

5
electrum/gui/qml/qebitcoin.py

@ -5,7 +5,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from electrum import mnemonic
from electrum import keystore
from electrum.i18n import _
from electrum.bip32 import is_bip32_derivation, normalize_bip32_derivation, xpub_type
from electrum.bip32 import is_bip32_derivation, xpub_type
from electrum.logging import get_logger
from electrum.slip39 import decode_mnemonic, Slip39Error
from electrum.util import get_asyncio_loop
@ -13,7 +13,6 @@ from electrum.transaction import tx_from_any
from electrum.mnemonic import Mnemonic, is_any_2fa_seed_type
from electrum.old_mnemonic import wordlist as old_wordlist
from .qetypes import QEAmount
class QEBitcoin(QObject):
_logger = get_logger(__name__)
@ -79,7 +78,7 @@ class QEBitcoin(QObject):
if is_checksum:
seed_type = 'bip39'
seed_valid = True
elif seed_variant == 'slip39': # TODO: incomplete impl, this code only validates a single share.
elif seed_variant == 'slip39': # TODO: incomplete impl, this code only validates a single share.
try:
share = decode_mnemonic(seed)
seed_type = 'slip39'

2
electrum/gui/qml/qechanneldetails.py

@ -17,7 +17,7 @@ from .util import QtEventListener, event_listener
class QEChannelDetails(AuthMixin, QObject, QtEventListener):
_logger = get_logger(__name__)
class State: # subset, only ones we currently need in UI
class State: # subset, only ones we currently need in UI
Closed = ChannelState.CLOSED
Redeemed = ChannelState.REDEEMED

50
electrum/gui/qml/qechannellistmodel.py

@ -29,6 +29,11 @@ class QEChannelListModel(QAbstractListModel, QtEventListener):
def __init__(self, wallet, parent=None):
super().__init__(parent)
self.wallet = wallet
self._channels = []
self._fm_backups = None
self._fm_nobackups = None
self.initModel()
# To avoid leaking references to "self" that prevent the
@ -52,19 +57,19 @@ class QEChannelListModel(QAbstractListModel, QtEventListener):
self.unregister_callbacks()
def rowCount(self, index):
return len(self.channels)
return len(self._channels)
# also expose rowCount as a property
countChanged = pyqtSignal()
@pyqtProperty(int, notify=countChanged)
def count(self):
return len(self.channels)
return len(self._channels)
def roleNames(self):
return self._ROLE_MAP
def data(self, index, role):
tx = self.channels[index.row()]
tx = self._channels[index.row()]
role_index = role - Qt.UserRole
value = tx[self._ROLE_NAMES[role_index]]
if isinstance(value, (bool, list, int, str, QEAmount)) or value is None:
@ -75,21 +80,22 @@ class QEChannelListModel(QAbstractListModel, QtEventListener):
def clear(self):
self.beginResetModel()
self.channels = []
self._channels = []
self.endResetModel()
def channel_to_model(self, lnc):
lnworker = self.wallet.lnworker
item = {}
item['cid'] = lnc.channel_id.hex()
item['node_id'] = lnc.node_id.hex()
item['node_alias'] = lnworker.get_node_alias(lnc.node_id) or ''
item['short_cid'] = lnc.short_id_for_GUI()
item['state'] = lnc.get_state_for_GUI()
item['state_code'] = int(lnc.get_state())
item['is_backup'] = lnc.is_backup()
item['is_trampoline'] = lnworker.is_trampoline_peer(lnc.node_id)
item['capacity'] = QEAmount(amount_sat=lnc.get_capacity())
item = {
'cid': lnc.channel_id.hex(),
'node_id': lnc.node_id.hex(),
'node_alias': lnworker.get_node_alias(lnc.node_id) or '',
'short_cid': lnc.short_id_for_GUI(),
'state': lnc.get_state_for_GUI(),
'state_code': int(lnc.get_state()),
'is_backup': lnc.is_backup(),
'is_trampoline': lnworker.is_trampoline_peer(lnc.node_id),
'capacity': QEAmount(amount_sat=lnc.get_capacity())
}
if lnc.is_backup():
item['can_send'] = QEAmount()
item['can_receive'] = QEAmount()
@ -111,7 +117,7 @@ class QEChannelListModel(QAbstractListModel, QtEventListener):
numOpenChannelsChanged = pyqtSignal()
@pyqtProperty(int, notify=numOpenChannelsChanged)
def numOpenChannels(self):
return sum([1 if x['state_code'] == ChannelState.OPEN else 0 for x in self.channels])
return sum([1 if x['state_code'] == ChannelState.OPEN else 0 for x in self._channels])
@pyqtSlot()
def initModel(self):
@ -134,20 +140,20 @@ class QEChannelListModel(QAbstractListModel, QtEventListener):
self.clear()
self.beginInsertRows(QModelIndex(), 0, len(channels) - 1)
self.channels = channels
self._channels = channels
self.endInsertRows()
self.countChanged.emit()
def on_channel_updated(self, channel):
for i, c in enumerate(self.channels):
for i, c in enumerate(self._channels):
if c['cid'] == channel.channel_id.hex():
self.do_update(i,channel)
self.do_update(i, channel)
break
def do_update(self, modelindex, channel):
self._logger.debug(f'updating our channel {channel.short_id_for_GUI()}')
modelitem = self.channels[modelindex]
modelitem = self._channels[modelindex]
modelitem.update(self.channel_to_model(channel))
mi = self.createIndex(modelindex, 0)
@ -163,7 +169,7 @@ class QEChannelListModel(QAbstractListModel, QtEventListener):
item = self.channel_to_model(channel)
self._logger.debug(item)
self.beginInsertRows(QModelIndex(), 0, 0)
self.channels.insert(0,item)
self._channels.insert(0, item)
self.endInsertRows()
self.countChanged.emit()
return
@ -171,11 +177,11 @@ class QEChannelListModel(QAbstractListModel, QtEventListener):
@pyqtSlot(str)
def removeChannel(self, cid):
self._logger.debug('remove channel with cid %s' % cid)
for i, channel in enumerate(self.channels):
for i, channel in enumerate(self._channels):
if cid == channel['cid']:
self._logger.debug(cid)
self.beginRemoveRows(QModelIndex(), i, i)
self.channels.remove(channel)
self._channels.remove(channel)
self.endRemoveRows()
self.countChanged.emit()
return

28
electrum/gui/qml/qechannelopener.py

@ -27,9 +27,10 @@ class QEChannelOpener(QObject, AuthMixin):
conflictingBackup = pyqtSignal([str], arguments=['message'])
channelOpening = pyqtSignal([str], arguments=['peer'])
channelOpenError = pyqtSignal([str], arguments=['message'])
channelOpenSuccess = pyqtSignal([str,bool,int,bool], arguments=['cid','has_onchain_backup','min_depth','tx_complete'])
channelOpenSuccess = pyqtSignal([str, bool, int, bool],
arguments=['cid', 'has_onchain_backup', 'min_depth', 'tx_complete'])
dataChanged = pyqtSignal() # generic notify signal
dataChanged = pyqtSignal() # generic notify signal
def __init__(self, parent=None):
super().__init__(parent)
@ -41,6 +42,10 @@ class QEChannelOpener(QObject, AuthMixin):
self._opentx = None
self._txdetails = None
self._finalizer = None
self._node_pubkey = None
self._connect_str_resolved = None
walletChanged = pyqtSignal()
@pyqtProperty(QEWallet, notify=walletChanged)
def wallet(self):
@ -124,7 +129,7 @@ class QEChannelOpener(QObject, AuthMixin):
self.validChanged.emit()
return
self._logger.debug('amount=%s' % str(self._amount))
self._logger.debug(f'amount={self._amount}')
if not self._amount or not (self._amount.satsInt > 0 or self._amount.isMax):
self._valid = False
self.validChanged.emit()
@ -136,9 +141,9 @@ class QEChannelOpener(QObject, AuthMixin):
@pyqtSlot(str, result=bool)
def validateConnectString(self, connect_str):
try:
node_id, rest = extract_nodeid(connect_str)
extract_nodeid(connect_str)
except ConnStringFormatError as e:
self._logger.debug(f"invalid connect_str. {e!r}")
self._logger.debug(f'invalid connect_str. {e!r}')
return False
return True
@ -199,13 +204,13 @@ class QEChannelOpener(QObject, AuthMixin):
chan.constraints.funding_txn_minimum_depth, funding_tx.is_complete())
# TODO: handle incomplete TX
#if not funding_tx.is_complete():
#self._txdetails = QETxDetails(self)
#self._txdetails.rawTx = funding_tx
#self._txdetails.wallet = self._wallet
#self.txDetailsChanged.emit()
# if not funding_tx.is_complete():
# self._txdetails = QETxDetails(self)
# self._txdetails.rawTx = funding_tx
# self._txdetails.wallet = self._wallet
# self.txDetailsChanged.emit()
except (CancelledError,TimeoutError):
except (CancelledError, TimeoutError):
error = _('Could not connect to channel peer')
except Exception as e:
error = str(e)
@ -216,7 +221,6 @@ class QEChannelOpener(QObject, AuthMixin):
self._logger.exception("Problem opening channel: %s", error)
self.channelOpenError.emit(error)
self._logger.debug('starting open thread')
self.channelOpening.emit(conn_str)
threading.Thread(target=open_thread, daemon=True).start()

11
electrum/gui/qml/qeconfig.py

@ -7,13 +7,14 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QRegularEx
from electrum.bitcoin import TOTAL_COIN_SUPPLY_LIMIT_IN_BTC
from electrum.i18n import set_language, languages
from electrum.logging import get_logger
from electrum.util import DECIMAL_POINT_DEFAULT, base_unit_name_to_decimal_point
from electrum.invoices import PR_DEFAULT_EXPIRATION_WHEN_CREATING
from electrum.simple_config import SimpleConfig
from electrum.util import base_unit_name_to_decimal_point
from .qetypes import QEAmount
from .auth import AuthMixin, auth_protect
if TYPE_CHECKING:
from electrum.simple_config import SimpleConfig
class QEConfig(AuthMixin, QObject):
_logger = get_logger(__name__)
@ -251,7 +252,7 @@ class QEConfig(AuthMixin, QObject):
return self.config.BTC_AMOUNTS_DECIMAL_POINT
def max_precision(self):
return self.decimal_point() + 0 #self.extra_precision
return self.decimal_point() + 0 # self.extra_precision
@pyqtSlot(str, result=QEAmount)
def unitsToSats(self, unitAmount):
@ -275,4 +276,4 @@ class QEConfig(AuthMixin, QObject):
@pyqtSlot('quint64', result=float)
def satsToUnits(self, satoshis):
return satoshis / pow(10,self.config.decimal_point)
return satoshis / pow(10, self.config.decimal_point)

28
electrum/gui/qml/qedaemon.py

@ -28,23 +28,24 @@ class QEWalletListModel(QAbstractListModel):
_logger = get_logger(__name__)
# define listmodel rolemap
_ROLE_NAMES= ('name','path','active')
_ROLE_NAMES= ('name', 'path', 'active')
_ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
def __init__(self, daemon, parent=None):
QAbstractListModel.__init__(self, parent)
self.daemon = daemon
self._wallets = []
self.reload()
def rowCount(self, index):
return len(self.wallets)
return len(self._wallets)
def roleNames(self):
return self._ROLE_MAP
def data(self, index, role):
(wallet_name, wallet_path) = self.wallets[index.row()]
(wallet_name, wallet_path) = self._wallets[index.row()]
role_index = role - Qt.UserRole
role_name = self._ROLE_NAMES[role_index]
if role_name == 'name':
@ -58,7 +59,7 @@ class QEWalletListModel(QAbstractListModel):
def reload(self):
self._logger.debug('enumerating available wallets')
self.beginResetModel()
self.wallets = []
self._wallets = []
self.endResetModel()
available = []
@ -72,18 +73,18 @@ class QEWalletListModel(QAbstractListModel):
self.add_wallet(wallet_path = path)
def add_wallet(self, wallet_path):
self.beginInsertRows(QModelIndex(), len(self.wallets), len(self.wallets))
self.beginInsertRows(QModelIndex(), len(self._wallets), len(self._wallets))
wallet_name = os.path.basename(wallet_path)
wallet_path = standardize_path(wallet_path)
item = (wallet_name, wallet_path)
self.wallets.append(item)
self._wallets.append(item)
self.endInsertRows()
def remove_wallet(self, path):
i = 0
wallets = []
remove = -1
for wallet_name, wallet_path in self.wallets:
for wallet_name, wallet_path in self._wallets:
if wallet_path == path:
remove = i
else:
@ -92,12 +93,12 @@ class QEWalletListModel(QAbstractListModel):
if remove >= 0:
self.beginRemoveRows(QModelIndex(), i, i)
self.wallets = wallets
self._wallets = wallets
self.endRemoveRows()
@pyqtSlot(str, result=bool)
def wallet_name_exists(self, name):
for wallet_name, wallet_path in self.wallets:
for wallet_name, wallet_path in self._wallets:
if name == wallet_name:
return True
return False
@ -105,7 +106,7 @@ class QEWalletListModel(QAbstractListModel):
@pyqtSlot(str)
def updateWallet(self, path):
i = 0
for wallet_name, wallet_path in self.wallets:
for wallet_name, wallet_path in self._wallets:
if wallet_path == path:
mi = self.createIndex(i, i)
self.dataChanged.emit(mi, mi, self._ROLE_KEYS)
@ -166,7 +167,7 @@ class QEDaemon(AuthMixin, QObject):
@pyqtSlot(str, str)
def loadWallet(self, path=None, password=None):
if path is None:
self._path = self.daemon.config.get('wallet_path') # command line -w option
self._path = self.daemon.config.get('wallet_path') # command line -w option
if self._path is None:
self._path = self.daemon.config.GUI_LAST_WALLET
else:
@ -201,7 +202,7 @@ class QEDaemon(AuthMixin, QObject):
self.loadingChanged.emit()
try:
local_password = password # need this in local scope
local_password = password # need this in local scope
wallet = self.daemon.load_wallet(self._path, local_password)
if wallet is None:
@ -238,7 +239,7 @@ class QEDaemon(AuthMixin, QObject):
@pyqtSlot()
@pyqtSlot(str)
def _on_backend_wallet_loaded(self, password = None):
def _on_backend_wallet_loaded(self, password=None):
self._logger.debug('_on_backend_wallet_loaded')
wallet = self.daemon.get_wallet(self._path)
assert wallet is not None
@ -247,7 +248,6 @@ class QEDaemon(AuthMixin, QObject):
self._current_wallet.password = password if password else None
self.walletLoaded.emit(self._name, self._path)
@pyqtSlot(QEWallet)
@pyqtSlot(QEWallet, bool)
@pyqtSlot(QEWallet, bool, bool)

3
electrum/gui/qml/qefx.py

@ -11,6 +11,7 @@ from electrum.simple_config import SimpleConfig
from .qetypes import QEAmount
from .util import QtEventListener, event_listener
class QEFX(QObject, QtEventListener):
_logger = get_logger(__name__)
@ -94,7 +95,7 @@ class QEFX(QObject, QtEventListener):
self.fx.set_exchange(source)
self.rateSourceChanged.emit()
enabledUpdated = pyqtSignal() # curiously, enabledChanged is clashing, so name it enabledUpdated
enabledUpdated = pyqtSignal() # curiously, enabledChanged is clashing, so name it enabledUpdated
@pyqtProperty(bool, notify=enabledUpdated)
def enabled(self):
return self.fx.is_enabled()

13
electrum/gui/qml/qeinvoice.py

@ -275,7 +275,7 @@ class QEInvoice(QObject, QtEventListener):
self._timer.start()
else:
self.update_userinfo()
self.determine_can_pay() # status went to PR_EXPIRED
self.determine_can_pay() # status went to PR_EXPIRED
@pyqtSlot()
def updateStatusString(self):
@ -293,7 +293,7 @@ class QEInvoice(QObject, QtEventListener):
if self.amount.isEmpty:
self.userinfo = _('Enter the amount you want to send')
if amount.isEmpty and self.status == PR_UNPAID: # unspecified amount
if amount.isEmpty and self.status == PR_UNPAID: # unspecified amount
return
if self.invoiceType == QEInvoice.Type.LightningInvoice:
@ -403,6 +403,7 @@ class QEInvoiceParser(QEInvoice):
self._recipient = ''
self._pi = None
self._lnurlData = None
self.clear()
@ -476,7 +477,7 @@ class QEInvoiceParser(QEInvoice):
self.setValidOnchainInvoice(invoice)
self.validationSuccess.emit()
else:
self.validationError.emit('unknown', f"invoice error:\n{pr.error}")
self.validationError.emit('unknown', f'invoice error:\n{pr.error}')
def validateRecipient(self, recipient):
if not recipient:
@ -485,7 +486,8 @@ class QEInvoiceParser(QEInvoice):
self._pi = PaymentIdentifier(self._wallet.wallet, recipient)
if not self._pi.is_valid() or self._pi.type not in [PaymentIdentifierType.SPK, PaymentIdentifierType.BIP21,
PaymentIdentifierType.BIP70, PaymentIdentifierType.BOLT11, PaymentIdentifierType.LNURLP]:
PaymentIdentifierType.BIP70, PaymentIdentifierType.BOLT11,
PaymentIdentifierType.LNURLP]:
self.validationError.emit('unknown', _('Unknown invoice'))
return
@ -552,6 +554,7 @@ class QEInvoiceParser(QEInvoice):
def resolve_pi(self):
assert self._pi.need_resolve()
def on_finished(pi):
if pi.is_error():
pass
@ -604,7 +607,7 @@ class QEInvoiceParser(QEInvoice):
# assure no shenanigans with the bolt11 invoice we get back
lninvoice = Invoice.from_bech32(invoice)
if orig_amount * 1000 != lninvoice.amount_msat: # TODO msat precision can cause trouble here
if orig_amount * 1000 != lninvoice.amount_msat: # TODO msat precision can cause trouble here
raise Exception('Unexpected amount in invoice, differs from lnurl-pay specified amount')
self.recipient = invoice

34
electrum/gui/qml/qeinvoicelistmodel.py

@ -1,7 +1,7 @@
from abc import abstractmethod
from typing import TYPE_CHECKING, List, Dict, Any
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer
from PyQt5.QtCore import pyqtSlot, QTimer
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
from electrum.logging import get_logger
@ -29,6 +29,7 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
def __init__(self, wallet: 'Abstract_Wallet', parent=None):
super().__init__(parent)
self.wallet = wallet
self._invoices = []
self._timer = QTimer(self)
self._timer.setSingleShot(True)
@ -41,13 +42,13 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
raise e
def rowCount(self, index):
return len(self.invoices)
return len(self._invoices)
def roleNames(self):
return self._ROLE_MAP
def data(self, index, role):
invoice = self.invoices[index.row()]
invoice = self._invoices[index.row()]
role_index = role - Qt.UserRole
value = invoice[self._ROLE_NAMES[role_index]]
@ -59,7 +60,7 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
def clear(self):
self.beginResetModel()
self.invoices = []
self._invoices = []
self.endResetModel()
@pyqtSlot()
@ -71,7 +72,7 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
self.clear()
self.beginInsertRows(QModelIndex(), 0, len(invoices) - 1)
self.invoices = invoices
self._invoices = invoices
self.endInsertRows()
self.set_status_timer()
@ -79,7 +80,7 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
def add_invoice(self, invoice: BaseInvoice):
# skip if already in list
key = invoice.get_id()
for x in self.invoices:
for x in self._invoices:
if x['key'] == key:
return
@ -87,7 +88,7 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
self._logger.debug(str(item))
self.beginInsertRows(QModelIndex(), 0, 0)
self.invoices.insert(0, item)
self._invoices.insert(0, item)
self.endInsertRows()
self.set_status_timer()
@ -97,29 +98,29 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
self.add_invoice(self.get_invoice_for_key(key))
def delete_invoice(self, key: str):
for i, invoice in enumerate(self.invoices):
for i, invoice in enumerate(self._invoices):
if invoice['key'] == key:
self.beginRemoveRows(QModelIndex(), i, i)
self.invoices.pop(i)
self._invoices.pop(i)
self.endRemoveRows()
break
self.set_status_timer()
def get_model_invoice(self, key: str):
for invoice in self.invoices:
for invoice in self._invoices:
if invoice['key'] == key:
return invoice
return None
@pyqtSlot(str, int)
def updateInvoice(self, key, status):
self._logger.debug('updating invoice for %s to %d' % (key,status))
for i, item in enumerate(self.invoices):
self._logger.debug(f'updating invoice for {key} to {status}')
for i, item in enumerate(self._invoices):
if item['key'] == key:
invoice = self.get_invoice_for_key(key)
item['status'] = status
item['status_str'] = invoice.get_status_str(status)
index = self.index(i,0)
index = self.index(i, 0)
self.dataChanged.emit(index, index, [self._ROLE_RMAP['status'], self._ROLE_RMAP['status_str']])
return
@ -137,7 +138,7 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
def set_status_timer(self):
nearest_interval = LN_EXPIRY_NEVER
for invoice in self.invoices:
for invoice in self._invoices:
if invoice['status'] != PR_EXPIRED:
if invoice['expiry'] > 0 and invoice['expiry'] != LN_EXPIRY_NEVER:
interval = status_update_timer_interval(invoice['timestamp'] + invoice['expiry'])
@ -150,11 +151,11 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
@pyqtSlot()
def updateStatusStrings(self):
for i, item in enumerate(self.invoices):
for i, item in enumerate(self._invoices):
invoice = self.get_invoice_for_key(item['key'])
item['status'] = self.wallet.get_invoice_status(invoice)
item['status_str'] = invoice.get_status_str(item['status'])
index = self.index(i,0)
index = self.index(i, 0)
self.dataChanged.emit(index, index, [self._ROLE_RMAP['status'], self._ROLE_RMAP['status_str']])
self.set_status_timer()
@ -206,6 +207,7 @@ class QEInvoiceListModel(QEAbstractInvoiceListModel, QtEventListener):
def get_invoice_as_dict(self, invoice: Invoice):
return self.wallet.export_invoice(invoice)
class QERequestListModel(QEAbstractInvoiceListModel, QtEventListener):
def __init__(self, wallet, parent=None):
super().__init__(wallet, parent)

4
electrum/gui/qml/qelnpaymentdetails.py

@ -45,7 +45,7 @@ class QELnPaymentDetails(QObject):
@key.setter
def key(self, key: str):
if self._key != key:
self._logger.debug('key set -> %s' % key)
self._logger.debug(f'key set -> {key}')
self._key = key
self.keyChanged.emit()
self.update()
@ -104,7 +104,7 @@ class QELnPaymentDetails(QObject):
self._label = tx['label']
self._date = format_time(tx['timestamp'])
self._timestamp = tx['timestamp']
self._status = 'settled' # TODO: other states? get_lightning_history is deciding the filter for us :(
self._status = 'settled' # TODO: other states? get_lightning_history is deciding the filter for us :(
self._phash = tx['payment_hash']
self._preimage = tx['preimage']

14
electrum/gui/qml/qenetwork.py

@ -1,6 +1,6 @@
from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
from electrum.logging import get_logger
from electrum import constants
@ -173,12 +173,11 @@ class QENetwork(QObject, QtEventListener):
@event_listener
def on_event_unknown_channels(self, unknown):
if unknown == 0 and self._gossipUnknownChannels == 0: # TODO: backend sends a lot of unknown=0 events
if unknown == 0 and self._gossipUnknownChannels == 0: # TODO: backend sends a lot of unknown=0 events
return
self._logger.debug(f'unknown channels {unknown}')
self._gossipUnknownChannels = unknown
self.gossipUpdated.emit()
#self.lightning_gossip_num_queries = unknown
def on_gossip_setting_changed(self):
if not self.network:
@ -205,7 +204,8 @@ class QENetwork(QObject, QtEventListener):
net_params = self.network.get_parameters()
try:
server = ServerAddr.from_str_with_inference(server)
if not server: raise Exception("failed to parse")
if not server:
raise Exception('failed to parse')
except Exception:
return
net_params = net_params._replace(server=server, auto_connect=self._qeconfig.autoConnect)
@ -215,7 +215,7 @@ class QENetwork(QObject, QtEventListener):
def serverWithStatus(self):
server = self._server
if not self.network.is_connected(): # connecting or disconnected
return f"{server} (connecting...)"
return f'{server} (connecting...)'
return server
@pyqtProperty(str, notify=statusChanged)
@ -244,7 +244,7 @@ class QENetwork(QObject, QtEventListener):
@pyqtProperty(str, notify=dataChanged)
def networkName(self):
return constants.net.__name__.replace('Bitcoin','')
return constants.net.__name__.replace('Bitcoin', '')
@pyqtProperty('QVariantMap', notify=proxyChanged)
def proxy(self):
@ -275,7 +275,7 @@ class QENetwork(QObject, QtEventListener):
'peers': self._gossipPeers,
'unknown_channels': self._gossipUnknownChannels,
'db_nodes': self._gossipDbNodes,
'db_channels': self._gossipDbChannels ,
'db_channels': self._gossipDbChannels,
'db_policies': self._gossipDbPolicies
}

16
electrum/gui/qml/qeqr.py

@ -16,6 +16,7 @@ from electrum.qrreader import get_qr_reader
from electrum.i18n import _
from electrum.util import profiler, get_asyncio_loop
class QEQRParser(QObject):
_logger = get_logger(__name__)
@ -28,6 +29,7 @@ class QEQRParser(QObject):
self._busy = False
self._image = None
self._data = None
self._text = text
self.qrreader = get_qr_reader()
@ -118,10 +120,12 @@ class QEQRParser(QObject):
result.append(QPoint(x+self.scan_pos_x, y+self.scan_pos_y))
return result
class QEQRImageProvider(QQuickImageProvider):
def __init__(self, max_size, parent=None):
super().__init__(QQuickImageProvider.Image)
self._max_size = max_size
self.qimg = None
_logger = get_logger(__name__)
@ -161,6 +165,7 @@ class QEQRImageProvider(QQuickImageProvider):
self.qimg.fill(QColor('gray'))
return self.qimg, self.qimg.size()
# helper for placing icon exactly where it should go on the QR code
# pyqt5 is unwilling to accept slots on QEQRImageProvider, so we need to define
# a separate class (sigh)
@ -187,6 +192,11 @@ class QEQRImageProviderHelper(QObject):
qr.box_size = math.floor(pixelsize/modules)
# calculate icon width in modules
icon_modules = int(modules / 5)
icon_modules += (icon_modules+1)%2 # force odd
return { 'modules': modules, 'box_size': qr.box_size, 'icon_modules': icon_modules, 'valid' : valid }
icon_modules += (icon_modules+1) % 2 # force odd
return {
'modules': modules,
'box_size': qr.box_size,
'icon_modules': icon_modules,
'valid': valid
}

3
electrum/gui/qml/qerequestdetails.py

@ -83,7 +83,6 @@ class QERequestDetails(QObject, QtEventListener):
self.keyChanged.emit()
self.initRequest()
statusChanged = pyqtSignal()
@pyqtProperty(int, notify=statusChanged)
def status(self):
return self._wallet.wallet.get_invoice_status(self._req)
@ -133,7 +132,6 @@ class QERequestDetails(QObject, QtEventListener):
def bip21(self):
return self._req.get_bip21_URI() if self._req else ''
def initRequest(self):
if self._wallet is None or self._key is None:
return
@ -160,7 +158,6 @@ class QERequestDetails(QObject, QtEventListener):
self._timer.setInterval(interval) # msec
self._timer.start()
@pyqtSlot()
def updateStatusString(self):
self.statusChanged.emit()

45
electrum/gui/qml/qeserverlistmodel.py

@ -1,8 +1,8 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
from electrum.logging import get_logger
from electrum.util import Satoshis, format_time
from electrum.util import Satoshis
from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL
from electrum import blockchain
@ -22,6 +22,7 @@ class QEServerListModel(QAbstractListModel, QtEventListener):
super().__init__(parent)
self._chaintips = 0
self._servers = []
self.network = network
self.initModel()
@ -44,13 +45,13 @@ class QEServerListModel(QAbstractListModel, QtEventListener):
self.initModel()
def rowCount(self, index):
return len(self.servers)
return len(self._servers)
def roleNames(self):
return self._ROLE_MAP
def data(self, index, role):
server = self.servers[index.row()]
server = self._servers[index.row()]
role_index = role - Qt.UserRole
value = server[self._ROLE_NAMES[role_index]]
@ -62,7 +63,7 @@ class QEServerListModel(QAbstractListModel, QtEventListener):
def clear(self):
self.beginResetModel()
self.servers = []
self._servers = []
self.endResetModel()
chaintipsChanged = pyqtSignal()
@ -97,14 +98,15 @@ class QEServerListModel(QAbstractListModel, QtEventListener):
self._logger.debug(f'chain {chain_id} has name={name}, max_forkpoint=@{b.get_max_forkpoint()}, height={b.height()}')
for i in interfaces:
server = {}
server['chain'] = name
server['chain_height'] = b.height()
server['is_primary'] = i == self.network.interface
server['is_connected'] = True
server['name'] = str(i.server)
server['address'] = i.server.to_friendly_name()
server['height'] = i.tip
server = {
'chain': name,
'chain_height': b.height(),
'is_primary': i == self.network.interface,
'is_connected': True,
'name': str(i.server),
'address': i.server.to_friendly_name(),
'height': i.tip
}
servers.append(server)
@ -120,17 +122,18 @@ class QEServerListModel(QAbstractListModel, QtEventListener):
port = d.get(protocol)
if port:
s = ServerAddr(_host, port, protocol=protocol)
server = {}
server['chain'] = ''
server['chain_height'] = 0
server['height'] = 0
server['is_primary'] = False
server['is_connected'] = False
server['name'] = s.net_addr_str()
server = {
'chain': '',
'chain_height': 0,
'height': 0,
'is_primary': False,
'is_connected': False,
'name': s.net_addr_str()
}
server['address'] = server['name']
servers.append(server)
self.beginInsertRows(QModelIndex(), 0, len(servers) - 1)
self.servers = servers
self._servers = servers
self.endInsertRows()

25
electrum/gui/qml/qetransactionlistmodel.py

@ -1,7 +1,7 @@
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Dict, Any
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
from electrum.logging import get_logger
@ -19,9 +19,9 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
_logger = get_logger(__name__)
# define listmodel rolemap
_ROLE_NAMES=('txid','fee_sat','height','confirmations','timestamp','monotonic_timestamp',
'incoming','value','date','label','txpos_in_block','fee',
'inputs','outputs','section','type','lightning','payment_hash','key','complete')
_ROLE_NAMES=('txid', 'fee_sat', 'height', 'confirmations', 'timestamp', 'monotonic_timestamp',
'incoming', 'value', 'date', 'label', 'txpos_in_block', 'fee',
'inputs', 'outputs', 'section', 'type', 'lightning', 'payment_hash', 'key', 'complete')
_ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
_ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS))
@ -34,11 +34,13 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
self.onchain_domain = onchain_domain
self.include_lightning = include_lightning
self.tx_history = []
self.register_callbacks()
self.destroyed.connect(lambda: self.on_destroy())
self.requestRefresh.connect(lambda: self.initModel())
self.setDirty()
self._dirty = True
self.initModel()
def on_destroy(self):
@ -159,19 +161,19 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
txts = datetime.fromtimestamp(timestamp)
today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
if (txts > today):
if txts > today:
return 'today'
elif (txts > today - timedelta(days=1)):
elif txts > today - timedelta(days=1):
return 'yesterday'
elif (txts > today - timedelta(days=7)):
elif txts > today - timedelta(days=7):
return 'lastweek'
elif (txts > today - timedelta(days=31)):
elif txts > today - timedelta(days=31):
return 'lastmonth'
else:
return 'older'
def format_date_by_section(self, section, date):
#TODO: l10n
# TODO: l10n
dfmt = {
'today': '%H:%M:%S',
'yesterday': '%H:%M:%S',
@ -194,7 +196,6 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
)
return tx_mined_info
# initial model data
@pyqtSlot()
@pyqtSlot(bool)
def initModel(self, force: bool = False):
@ -231,7 +232,7 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
tx['section'] = self.get_section_by_timestamp(info.timestamp)
tx['date'] = self.format_date_by_section(tx['section'], datetime.fromtimestamp(info.timestamp))
index = self.index(i,0)
roles = [self._ROLE_RMAP[x] for x in ['section','height','confirmations','timestamp','date']]
roles = [self._ROLE_RMAP[x] for x in ['section', 'height', 'confirmations', 'timestamp', 'date']]
self.dataChanged.emit(index, index, roles)
return

9
electrum/gui/qml/qetxdetails.py

@ -4,7 +4,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from electrum.i18n import _
from electrum.logging import get_logger
from electrum.util import format_time, AddTransactionException, TxMinedInfo
from electrum.util import format_time, TxMinedInfo
from electrum.transaction import tx_from_any, Transaction
from electrum.network import Network
@ -12,6 +12,7 @@ from .qewallet import QEWallet
from .qetypes import QEAmount
from .util import QtEventListener, event_listener
class QETxDetails(QObject, QtEventListener):
_logger = get_logger(__name__)
@ -68,13 +69,13 @@ class QETxDetails(QObject, QtEventListener):
@event_listener
def on_event_verified(self, wallet, txid, info):
if wallet == self._wallet.wallet and txid == self._txid:
self._logger.debug('verified event for our txid %s' % txid)
self._logger.debug(f'verified event for our txid {txid}')
self.update()
@event_listener
def on_event_new_transaction(self, wallet, tx):
if wallet == self._wallet.wallet and tx.txid() == self._txid:
self._logger.debug('new_transaction event for our txid %s' % self._txid)
self._logger.debug(f'new_transaction event for our txid {txid}')
self.update()
walletChanged = pyqtSignal()
@ -292,7 +293,7 @@ class QETxDetails(QObject, QtEventListener):
group_id = item.get('group_id')
if group_id:
full_history = self._wallet.wallet.get_full_history()
group_item = full_history['group:'+ group_id]
group_item = full_history['group:' + group_id]
self._lnamount.satsInt = int(group_item['ln_value'].value)
else:
self._lnamount.satsInt = int(item['amount_msat'] / 1000)

32
electrum/gui/qml/qetxfinalizer.py

@ -287,11 +287,11 @@ class QETxFinalizer(TxFeeSlider):
if self._canRbf != canRbf:
self._canRbf = canRbf
self.canRbfChanged.emit()
self.rbf = self._canRbf # if we can RbF, we do RbF
self.rbf = self._canRbf # if we can RbF, we do RbF
@profiler
def make_tx(self, amount):
self._logger.debug('make_tx amount = %s' % str(amount))
self._logger.debug(f'make_tx amount={amount}')
if self.f_make_tx:
tx = self.f_make_tx(amount)
@ -299,7 +299,7 @@ class QETxFinalizer(TxFeeSlider):
# default impl
coins = self._wallet.wallet.get_spendable_coins(None)
outputs = [PartialTxOutput.from_address_and_value(self.address, amount)]
tx = self._wallet.wallet.make_unsigned_transaction(coins=coins,outputs=outputs, fee=None,rbf=self._rbf)
tx = self._wallet.wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee=None, rbf=self._rbf)
self._logger.debug('fee: %d, inputs: %d, outputs: %d' % (tx.get_fee(), len(tx.inputs()), len(tx.outputs())))
@ -312,7 +312,7 @@ class QETxFinalizer(TxFeeSlider):
try:
# make unsigned transaction
tx = self.make_tx(amount = '!' if self._amount.isMax else self._amount.satsInt)
tx = self.make_tx(amount='!' if self._amount.isMax else self._amount.satsInt)
except NotEnoughFunds:
self.warning = _("Not enough funds")
self._valid = False
@ -373,10 +373,7 @@ class QETxFinalizer(TxFeeSlider):
self.f_accept(self._tx)
return
self._wallet.sign(self._tx,
broadcast=True,
on_success=partial(self.on_signed_tx, False)
)
self._wallet.sign(self._tx, broadcast=True, on_success=partial(self.on_signed_tx, False))
@pyqtSlot()
def sign(self):
@ -384,10 +381,7 @@ class QETxFinalizer(TxFeeSlider):
self._logger.error('no valid tx')
return
self._wallet.sign(self._tx,
broadcast=False,
on_success=partial(self.on_signed_tx, True)
)
self._wallet.sign(self._tx, broadcast=False, on_success=partial(self.on_signed_tx, True))
def on_signed_tx(self, save: bool, tx: Transaction):
self._logger.debug('on_signed_tx')
@ -405,12 +399,13 @@ class QETxFinalizer(TxFeeSlider):
return [str(self._tx), txqr[0], txqr[1]]
# mixin for watching an existing TX based on its txid for verified event
# requires self._wallet to contain a QEWallet instance
# exposes txid qt property
# calls get_tx() once txid is set
# calls tx_verified and emits txMined signal once tx is verified
class TxMonMixin(QtEventListener):
""" mixin for watching an existing TX based on its txid for verified event.
requires self._wallet to contain a QEWallet instance.
exposes txid qt property.
calls get_tx() once txid is set.
calls tx_verified and emits txMined signal once tx is verified.
"""
txMined = pyqtSignal()
def __init__(self, parent=None):
@ -505,7 +500,6 @@ class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin):
self.bumpMethodChanged.emit()
self.update()
def get_tx(self):
assert self._txid
self._orig_tx = self._wallet.wallet.db.get_transaction(self._txid)
@ -783,7 +777,7 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin):
return fee
def update(self):
if not self._txid: # not initialized yet
if not self._txid: # not initialized yet
return
assert self._parent_tx

9
electrum/gui/qml/qetypes.py

@ -11,10 +11,11 @@ from electrum.i18n import _
# should also capture millisats amounts and MAX/'!' indicators
# and (unformatted) string representations
class QEAmount(QObject):
_logger = get_logger(__name__)
def __init__(self, *, amount_sat: int = 0, amount_msat: int = 0, is_max: bool = False, from_invoice = None, parent=None):
def __init__(self, *, amount_sat: int = 0, amount_msat: int = 0, is_max: bool = False, from_invoice=None, parent=None):
super().__init__(parent)
self._amount_sat = int(amount_sat) if amount_sat is not None else None
self._amount_msat = int(amount_msat) if amount_msat is not None else None
@ -31,7 +32,7 @@ class QEAmount(QObject):
@pyqtProperty('qint64', notify=valueChanged)
def satsInt(self):
if self._amount_sat is None: # should normally be defined when accessing this property
if self._amount_sat is None: # should normally be defined when accessing this property
self._logger.warning('amount_sat is undefined, returning 0')
return 0
return self._amount_sat
@ -44,7 +45,7 @@ class QEAmount(QObject):
@pyqtProperty('qint64', notify=valueChanged)
def msatsInt(self):
if self._amount_msat is None: # should normally be defined when accessing this property
if self._amount_msat is None: # should normally be defined when accessing this property
self._logger.warning('amount_msat is undefined, returning 0')
return 0
return self._amount_msat
@ -86,7 +87,7 @@ class QEAmount(QObject):
def copyFrom(self, amount):
if not amount:
self._logger.warning('copyFrom with None argument. assuming 0') # TODO
self._logger.warning('copyFrom with None argument. assuming 0') # TODO
amount = QEAmount()
self.satsInt = amount.satsInt
self.msatsInt = amount.msatsInt

43
electrum/gui/qml/qewallet.py

@ -2,18 +2,17 @@ import asyncio
import queue
import threading
import time
from typing import TYPE_CHECKING, Optional, Tuple, Callable
from typing import TYPE_CHECKING, Callable
from functools import partial
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, QMetaObject, Qt
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer
from electrum import bitcoin
from electrum.i18n import _
from electrum.invoices import InvoiceError, PR_DEFAULT_EXPIRATION_WHEN_CREATING, PR_PAID, PR_BROADCASTING, PR_BROADCAST
from electrum.invoices import InvoiceError, PR_PAID, PR_BROADCASTING, PR_BROADCAST
from electrum.logging import get_logger
from electrum.network import TxBroadcastError, BestEffortRequestFailed
from electrum.transaction import PartialTxOutput, PartialTransaction, Transaction
from electrum.util import parse_max_spend, InvalidPassword, event_listener, AddTransactionException, get_asyncio_loop
from electrum.transaction import PartialTransaction, Transaction
from electrum.util import InvalidPassword, event_listener, AddTransactionException, get_asyncio_loop
from electrum.plugin import run_hook
from electrum.wallet import Multisig_Wallet
from electrum.crypto import pw_decode_with_version_and_mac
@ -30,6 +29,7 @@ if TYPE_CHECKING:
from electrum.wallet import Abstract_Wallet
from .qeinvoice import QEInvoice
class QEWallet(AuthMixin, QObject, QtEventListener):
__instances = []
@ -53,12 +53,14 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
# shared signal for many static wallet properties
dataChanged = pyqtSignal()
balanceChanged = pyqtSignal()
requestStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
requestCreateSuccess = pyqtSignal([str], arguments=['key'])
requestCreateError = pyqtSignal([str], arguments=['error'])
invoiceStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
invoiceCreateSuccess = pyqtSignal()
invoiceCreateError = pyqtSignal([str,str], arguments=['code','error'])
paymentAuthRejected = pyqtSignal()
paymentSucceeded = pyqtSignal([str], arguments=['key'])
paymentFailed = pyqtSignal([str,str], arguments=['key','reason'])
requestNewPassword = pyqtSignal()
@ -196,7 +198,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
if wallet == self.wallet:
self._logger.info(f'removed transaction {tx.txid()}')
self.addressModel.setDirty()
self.historyModel.initModel(True) #setDirty()
self.historyModel.initModel(True) # setDirty()?
self.balanceChanged.emit()
@qt_event_listener
@ -206,7 +208,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
self.balanceChanged.emit()
self.synchronizing = not wallet.is_up_to_date()
if not self.synchronizing:
self.historyModel.initModel() # refresh if dirty
self.historyModel.initModel() # refresh if dirty
@event_listener
def on_event_channel(self, wallet, channel):
@ -224,7 +226,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
def on_event_payment_succeeded(self, wallet, key):
if wallet == self.wallet:
self.paymentSucceeded.emit(key)
self.historyModel.initModel(True) # TODO: be less dramatic
self.historyModel.initModel(True) # TODO: be less dramatic
@event_listener
def on_event_payment_failed(self, wallet, key, reason):
@ -426,13 +428,11 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
@pyqtProperty(bool, notify=dataChanged)
def canSignWithoutCosigner(self):
if isinstance(self.wallet, Multisig_Wallet):
if self.wallet.wallet_type == '2fa': # 2fa is multisig, but it handles cosigning itself
if self.wallet.wallet_type == '2fa': # 2fa is multisig, but it handles cosigning itself
return True
return self.wallet.m == 1
return True
balanceChanged = pyqtSignal()
@pyqtProperty(QEAmount, notify=balanceChanged)
def frozenBalance(self):
c, u, x = self.wallet.get_frozen_balance()
@ -499,9 +499,11 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
success = self.do_sign(tx, broadcast)
if success:
if on_success: on_success(tx)
if on_success:
on_success(tx)
else:
if on_failure: on_failure()
if on_failure:
on_failure()
def do_sign(self, tx, broadcast):
try:
@ -534,14 +536,16 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
# this assumes a 2fa wallet, but there are no other tc_sign_wrapper hooks, so that's ok
def on_sign_complete(self, broadcast, cb: Callable[[Transaction], None] = None, tx: Transaction = None):
self.otpSuccess.emit()
if cb: cb(tx)
if cb:
cb(tx)
if broadcast:
self.broadcast(tx)
# this assumes a 2fa wallet, but there are no other tc_sign_wrapper hooks, so that's ok
def on_sign_failed(self, cb: Callable[[], None] = None, error: str = None):
self.otpFailed.emit('error', error)
if cb: cb()
if cb:
cb()
def request_otp(self, on_submit):
self._otp_on_submit = on_submit
@ -577,7 +581,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
threading.Thread(target=broadcast_thread, daemon=True).start()
#TODO: properly catch server side errors, e.g. bad-txns-inputs-missingorspent
# TODO: properly catch server side errors, e.g. bad-txns-inputs-missingorspent
def save_tx(self, tx: 'PartialTransaction'):
assert tx
@ -585,7 +589,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
try:
if not self.wallet.adb.add_transaction(tx):
self.saveTxError.emit(tx.txid(), 'conflict',
_("Transaction could not be saved.") + "\n" + _("It conflicts with current history."))
_("Transaction could not be saved.") + "\n" + _("It conflicts with current history."))
return
self.wallet.save_db()
self.saveTxSuccess.emit(tx.txid())
@ -595,7 +599,6 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
self.saveTxError.emit(tx.txid(), 'error', str(e))
return False
paymentAuthRejected = pyqtSignal()
def ln_auth_rejected(self):
self.paymentAuthRejected.emit()
@ -724,7 +727,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
xpub = self.wallet.get_fingerprint()
decrypted = pw_decode_with_version_and_mac(encrypted, xpub)
return True
except Exception as e:
except Exception:
return False
@pyqtSlot()

3
electrum/gui/qml/qewalletdb.py

@ -85,7 +85,7 @@ class QEWalletDB(QObject):
@pyqtProperty('QString', notify=passwordChanged)
def password(self):
return '' # no read access
return '' # no read access
@password.setter
def password(self, wallet_password):
@ -142,7 +142,6 @@ class QEWalletDB(QObject):
self.invalidPassword.emit()
else: # storage not encrypted; but it might still have a keystore pw
# FIXME hack... load both db and full wallet, just to tell if it has keystore pw.
# this also completely ignores db.requires_split(), db.get_action(), etc
try:
db = WalletDB(self._storage.read(), storage=self._storage, upgrade=True)
except WalletRequiresSplit as e:

50
electrum/gui/qml/qewizard.py

@ -13,9 +13,13 @@ if TYPE_CHECKING:
class QEAbstractWizard(QObject):
""" Concrete subclasses of QEAbstractWizard must also inherit from a concrete AbstractWizard subclass.
QEAbstractWizard forms the base for all QML GUI based wizards, while AbstractWizard defines
the base for non-gui wizard flow navigation functionality.
"""
_logger = get_logger(__name__)
def __init__(self, parent = None):
def __init__(self, parent=None):
QObject.__init__(self, parent)
@pyqtSlot(result=str)
@ -45,32 +49,33 @@ class QEAbstractWizard(QObject):
class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
createError = pyqtSignal([str], arguments=["error"])
createSuccess = pyqtSignal()
def __init__(self, daemon: 'QEDaemon', plugins: 'Plugins', parent = None):
def __init__(self, daemon: 'QEDaemon', plugins: 'Plugins', parent=None):
NewWalletWizard.__init__(self, daemon.daemon, plugins)
QEAbstractWizard.__init__(self, parent)
self._qedaemon = daemon
self._path = None
self._password = None
# attach view names and accept handlers
self.navmap_merge({
'wallet_name': { 'gui': 'WCWalletName' },
'wallet_type': { 'gui': 'WCWalletType' },
'keystore_type': { 'gui': 'WCKeystoreType' },
'create_seed': { 'gui': 'WCCreateSeed' },
'confirm_seed': { 'gui': 'WCConfirmSeed' },
'have_seed': { 'gui': 'WCHaveSeed' },
'script_and_derivation': { 'gui': 'WCScriptAndDerivation' },
'have_master_key': { 'gui': 'WCHaveMasterKey' },
'multisig': { 'gui': 'WCMultisig' },
'multisig_cosigner_keystore': { 'gui': 'WCCosignerKeystore' },
'multisig_cosigner_key': { 'gui': 'WCHaveMasterKey' },
'multisig_cosigner_seed': { 'gui': 'WCHaveSeed' },
'multisig_cosigner_script_and_derivation': { 'gui': 'WCScriptAndDerivation' },
'imported': { 'gui': 'WCImport' },
'wallet_password': { 'gui': 'WCWalletPassword' }
'wallet_name': {'gui': 'WCWalletName'},
'wallet_type': {'gui': 'WCWalletType'},
'keystore_type': {'gui': 'WCKeystoreType'},
'create_seed': {'gui': 'WCCreateSeed'},
'confirm_seed': {'gui': 'WCConfirmSeed'},
'have_seed': {'gui': 'WCHaveSeed'},
'script_and_derivation': {'gui': 'WCScriptAndDerivation'},
'have_master_key': {'gui': 'WCHaveMasterKey'},
'multisig': {'gui': 'WCMultisig'},
'multisig_cosigner_keystore': {'gui': 'WCCosignerKeystore'},
'multisig_cosigner_key': {'gui': 'WCHaveMasterKey'},
'multisig_cosigner_seed': {'gui': 'WCHaveSeed'},
'multisig_cosigner_script_and_derivation': {'gui': 'WCScriptAndDerivation'},
'imported': {'gui': 'WCImport'},
'wallet_password': {'gui': 'WCWalletPassword'}
})
pathChanged = pyqtSignal()
@ -127,15 +132,14 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
class QEServerConnectWizard(ServerConnectWizard, QEAbstractWizard):
def __init__(self, daemon: 'QEDaemon', parent=None):
ServerConnectWizard.__init__(self, daemon.daemon)
QEAbstractWizard.__init__(self, parent)
# attach view names
self.navmap_merge({
'autoconnect': { 'gui': 'WCAutoConnect' },
'proxy_ask': { 'gui': 'WCProxyAsk' },
'proxy_config': { 'gui': 'WCProxyConfig' },
'server_config': { 'gui': 'WCServerConfig' },
'autoconnect': {'gui': 'WCAutoConnect'},
'proxy_ask': {'gui': 'WCProxyAsk'},
'proxy_config': {'gui': 'WCProxyConfig'},
'server_config': {'gui': 'WCServerConfig'},
})

6
electrum/gui/qml/util.py

@ -31,6 +31,7 @@ class QtEventListener(EventListener):
# decorator for members of the QtEventListener class
def qt_event_listener(func):
func = event_listener(func)
@wraps(func)
def decorator(self, *args):
self.qt_callback_signal.emit( (func,) + args)
@ -56,10 +57,11 @@ def status_update_timer_interval(exp):
return interval
# TODO: copied from desktop client, this could be moved to a set of common code.
class TaskThread(QThread, Logger):
'''Thread that runs background tasks. Callbacks are guaranteed
to happen in the context of its parent.'''
"""Thread that runs background tasks. Callbacks are guaranteed
to happen in the context of its parent."""
class Task(NamedTuple):
task: Callable

4
electrum/gui/qt/wizard/wizard.py

@ -19,6 +19,10 @@ if TYPE_CHECKING:
class QEAbstractWizard(QDialog, MessageBoxMixin):
""" Concrete subclasses of QEAbstractWizard must also inherit from a concrete AbstractWizard subclass.
QEAbstractWizard forms the base for all QtWidgets GUI based wizards, while AbstractWizard defines
the base for non-gui wizard flow navigation functionality.
"""
_logger = get_logger(__name__)
requestNext = pyqtSignal()

Loading…
Cancel
Save