qt tx dialog: make scid and addr texts clickable in IO fields
based on:7eea0b6dae52d845017c
This commit is contained in:
@@ -242,7 +242,7 @@ class AddressSynchronizer(Logger, EventListener):
|
||||
conflicting_txns -= {tx_hash}
|
||||
return conflicting_txns
|
||||
|
||||
def get_transaction(self, txid: str) -> Transaction:
|
||||
def get_transaction(self, txid: str) -> Optional[Transaction]:
|
||||
tx = self.db.get_transaction(txid)
|
||||
if tx:
|
||||
# add verified tx info
|
||||
|
||||
@@ -2190,30 +2190,44 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
if tx:
|
||||
self.show_transaction(tx)
|
||||
|
||||
def do_process_from_txid(self):
|
||||
def do_process_from_txid(self, *, parent: QWidget = None, txid: str = None):
|
||||
if parent is None:
|
||||
parent = self
|
||||
from electrum import transaction
|
||||
txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
|
||||
if ok and txid:
|
||||
txid = str(txid).strip()
|
||||
raw_tx = self._fetch_tx_from_network(txid)
|
||||
if txid is None:
|
||||
txid, ok = QInputDialog.getText(parent, _('Lookup transaction'), _('Transaction ID') + ':')
|
||||
if not ok:
|
||||
txid = None
|
||||
if not txid:
|
||||
return
|
||||
txid = str(txid).strip()
|
||||
tx = self.wallet.adb.get_transaction(txid)
|
||||
if tx is None:
|
||||
raw_tx = self._fetch_tx_from_network(txid, parent=parent)
|
||||
if not raw_tx:
|
||||
return
|
||||
tx = transaction.Transaction(raw_tx)
|
||||
self.show_transaction(tx)
|
||||
self.show_transaction(tx)
|
||||
|
||||
def _fetch_tx_from_network(self, txid: str) -> Optional[str]:
|
||||
def _fetch_tx_from_network(self, txid: str, *, parent: QWidget = None) -> Optional[str]:
|
||||
if not self.network:
|
||||
self.show_message(_("You are offline."))
|
||||
self.show_message(_("You are offline."), parent=parent)
|
||||
return
|
||||
try:
|
||||
raw_tx = self.network.run_from_another_thread(
|
||||
self.network.get_transaction(txid, timeout=10))
|
||||
except UntrustedServerReturnedError as e:
|
||||
self.logger.info(f"Error getting transaction from network: {repr(e)}")
|
||||
self.show_message(_("Error getting transaction from network") + ":\n" + e.get_message_for_gui())
|
||||
self.show_message(
|
||||
_("Error getting transaction from network") + ":\n" + e.get_message_for_gui(),
|
||||
parent=parent,
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
self.show_message(_("Error getting transaction from network") + ":\n" + repr(e))
|
||||
self.show_message(
|
||||
_("Error getting transaction from network") + ":\n" + repr(e),
|
||||
parent=parent,
|
||||
)
|
||||
return
|
||||
return raw_tx
|
||||
|
||||
|
||||
@@ -28,19 +28,20 @@ import copy
|
||||
import datetime
|
||||
import traceback
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Callable, Optional, List, Union
|
||||
from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple
|
||||
from functools import partial
|
||||
from decimal import Decimal
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt
|
||||
from PyQt5.QtCore import QSize, Qt, QUrl
|
||||
from PyQt5.QtGui import QTextCharFormat, QBrush, QFont, QPixmap
|
||||
from PyQt5.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGridLayout,
|
||||
QTextEdit, QFrame, QAction, QToolButton, QMenu, QCheckBox)
|
||||
QTextEdit, QFrame, QAction, QToolButton, QMenu, QCheckBox, QTextBrowser)
|
||||
import qrcode
|
||||
from qrcode import exceptions
|
||||
|
||||
from electrum.simple_config import SimpleConfig
|
||||
from electrum.util import quantize_feerate
|
||||
from electrum import bitcoin
|
||||
from electrum.bitcoin import base_encode, NLOCKTIME_BLOCKHEIGHT_MAX
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import run_hook
|
||||
@@ -74,7 +75,7 @@ class TxFiatLabel(QLabel):
|
||||
def setAmount(self, fiat_fee):
|
||||
self.setText(('≈ %s' % fiat_fee) if fiat_fee else '')
|
||||
|
||||
class QTextEditWithDefaultSize(QTextEdit):
|
||||
class QTextBrowserWithDefaultSize(QTextBrowser):
|
||||
def sizeHint(self):
|
||||
return QSize(0, 100)
|
||||
|
||||
@@ -86,7 +87,11 @@ class TxInOutWidget(QWidget):
|
||||
self.wallet = wallet
|
||||
self.main_window = main_window
|
||||
self.inputs_header = QLabel()
|
||||
self.inputs_textedit = QTextEditWithDefaultSize()
|
||||
self.inputs_textedit = QTextBrowserWithDefaultSize()
|
||||
self.inputs_textedit.setOpenLinks(False) # disable automatic link opening
|
||||
self.inputs_textedit.anchorClicked.connect(self._open_internal_link) # send links to our handler
|
||||
self.inputs_textedit.setTextInteractionFlags(
|
||||
self.inputs_textedit.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
|
||||
self.txo_color_recv = TxOutputColoring(
|
||||
legend=_("Receiving Address"), color=ColorScheme.GREEN, tooltip=_("Wallet receive address"))
|
||||
self.txo_color_change = TxOutputColoring(
|
||||
@@ -94,7 +99,11 @@ class TxInOutWidget(QWidget):
|
||||
self.txo_color_2fa = TxOutputColoring(
|
||||
legend=_("TrustedCoin (2FA) batch fee"), color=ColorScheme.BLUE, tooltip=_("TrustedCoin (2FA) fee for the next batch of transactions"))
|
||||
self.outputs_header = QLabel()
|
||||
self.outputs_textedit = QTextEditWithDefaultSize()
|
||||
self.outputs_textedit = QTextBrowserWithDefaultSize()
|
||||
self.outputs_textedit.setOpenLinks(False) # disable automatic link opening
|
||||
self.outputs_textedit.anchorClicked.connect(self._open_internal_link) # send links to our handler
|
||||
self.outputs_textedit.setTextInteractionFlags(
|
||||
self.outputs_textedit.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
|
||||
|
||||
self.inputs_textedit.setMinimumWidth(950)
|
||||
self.outputs_textedit.setMinimumWidth(950)
|
||||
@@ -125,43 +134,59 @@ class TxInOutWidget(QWidget):
|
||||
|
||||
self.inputs_header.setText(inputs_header_text)
|
||||
|
||||
ext = QTextCharFormat()
|
||||
ext = QTextCharFormat() # "external"
|
||||
lnk = QTextCharFormat()
|
||||
lnk.setToolTip(_('Click to open'))
|
||||
lnk.setAnchor(True)
|
||||
lnk.setUnderlineStyle(QTextCharFormat.SingleUnderline)
|
||||
tf_used_recv, tf_used_change, tf_used_2fa = False, False, False
|
||||
def text_format(addr):
|
||||
def addr_text_format(addr):
|
||||
nonlocal tf_used_recv, tf_used_change, tf_used_2fa
|
||||
if self.wallet.is_mine(addr):
|
||||
if self.wallet.is_change(addr):
|
||||
tf_used_change = True
|
||||
return self.txo_color_change.text_char_format
|
||||
fmt = QTextCharFormat(self.txo_color_change.text_char_format)
|
||||
else:
|
||||
tf_used_recv = True
|
||||
return self.txo_color_recv.text_char_format
|
||||
fmt = QTextCharFormat(self.txo_color_recv.text_char_format)
|
||||
fmt.setAnchorHref(addr)
|
||||
fmt.setToolTip(_('Click to open'))
|
||||
fmt.setAnchor(True)
|
||||
fmt.setUnderlineStyle(QTextCharFormat.SingleUnderline)
|
||||
return fmt
|
||||
elif self.wallet.is_billing_address(addr):
|
||||
tf_used_2fa = True
|
||||
return self.txo_color_2fa.text_char_format
|
||||
return ext
|
||||
|
||||
def insert_tx_io(cursor, is_coinbase, short_id, address, value):
|
||||
if is_coinbase:
|
||||
cursor.insertText('coinbase')
|
||||
else:
|
||||
address_str = address or '<address unknown>'
|
||||
value_str = self.main_window.format_amount(value, whitespaces=True)
|
||||
cursor.insertText("%-15s\t"%str(short_id), ext)
|
||||
cursor.insertText("%-62s"%address_str, text_format(address))
|
||||
cursor.insertText('\t', ext)
|
||||
cursor.insertText(value_str, ext)
|
||||
cursor.insertBlock()
|
||||
|
||||
i_text = self.inputs_textedit
|
||||
i_text.clear()
|
||||
i_text.setFont(QFont(MONOSPACE_FONT))
|
||||
i_text.setReadOnly(True)
|
||||
cursor = i_text.textCursor()
|
||||
for txin in self.tx.inputs():
|
||||
for txin_idx, txin in enumerate(self.tx.inputs()):
|
||||
addr = self.wallet.adb.get_txin_address(txin)
|
||||
txin_value = self.wallet.adb.get_txin_value(txin)
|
||||
insert_tx_io(cursor, txin.is_coinbase_input(), txin.short_id, addr, txin_value)
|
||||
if txin.is_coinbase_input():
|
||||
cursor.insertText('coinbase')
|
||||
else:
|
||||
# short_id
|
||||
a_name = f"tx input {txin_idx}"
|
||||
lnk2 = QTextCharFormat(lnk)
|
||||
lnk2.setAnchorHref(txin.prevout.txid.hex())
|
||||
lnk2.setAnchorNames([a_name])
|
||||
cursor.insertText(str(txin.short_id), lnk2)
|
||||
cursor.insertText(" " * max(0, 15 - len(str(txin.short_id))), ext) # padding
|
||||
cursor.insertText('\t', ext)
|
||||
# addr
|
||||
address_str = addr or '<address unknown>'
|
||||
cursor.insertText(address_str, addr_text_format(addr))
|
||||
cursor.insertText(" " * max(0, 62 - len(address_str)), ext) # padding
|
||||
cursor.insertText('\t', ext)
|
||||
# value
|
||||
value_str = self.main_window.format_amount(txin_value, whitespaces=True)
|
||||
cursor.insertText(value_str, ext)
|
||||
cursor.insertBlock()
|
||||
|
||||
self.outputs_header.setText(_("Outputs") + ' (%d)'%len(self.tx.outputs()))
|
||||
o_text = self.outputs_textedit
|
||||
@@ -176,14 +201,39 @@ class TxInOutWidget(QWidget):
|
||||
short_id = ShortID.from_components(tx_height, tx_pos, index)
|
||||
else:
|
||||
short_id = TxOutpoint(tx_hash, index).short_name()
|
||||
|
||||
addr, value = o.get_ui_address_str(), o.value
|
||||
insert_tx_io(cursor, False, short_id, addr, value)
|
||||
# short id
|
||||
cursor.insertText(str(short_id), ext)
|
||||
cursor.insertText(" " * max(0, 15 - len(str(short_id))), ext) # padding
|
||||
cursor.insertText('\t', ext)
|
||||
# addr
|
||||
address_str = addr or '<address unknown>'
|
||||
cursor.insertText(address_str, addr_text_format(addr))
|
||||
cursor.insertText(" " * max(0, 62 - len(address_str)), ext) # padding
|
||||
cursor.insertText('\t', ext)
|
||||
# value
|
||||
value_str = self.main_window.format_amount(value, whitespaces=True)
|
||||
cursor.insertText(value_str, ext)
|
||||
cursor.insertBlock()
|
||||
|
||||
self.txo_color_recv.legend_label.setVisible(tf_used_recv)
|
||||
self.txo_color_change.legend_label.setVisible(tf_used_change)
|
||||
self.txo_color_2fa.legend_label.setVisible(tf_used_2fa)
|
||||
|
||||
def _open_internal_link(self, target):
|
||||
"""Accepts either a str txid, str address, or a QUrl which should be
|
||||
of the bare form "txid" and/or "address" -- used by the clickable
|
||||
links in the inputs/outputs QTextBrowsers"""
|
||||
if isinstance(target, QUrl):
|
||||
target = target.toString(QUrl.None_)
|
||||
assert target
|
||||
if bitcoin.is_address(target):
|
||||
# target was an address, open address dialog
|
||||
self.main_window.show_address(target, parent=self)
|
||||
else:
|
||||
# target was a txid, open new tx dialog
|
||||
self.main_window.do_process_from_txid(txid=target, parent=self)
|
||||
|
||||
|
||||
_logger = get_logger(__name__)
|
||||
dialogs = [] # Otherwise python randomly garbage collects the dialogs...
|
||||
@@ -652,7 +702,6 @@ class BaseTxDialog(QDialog, MessageBoxMixin):
|
||||
|
||||
run_hook('transaction_dialog_update', self)
|
||||
|
||||
|
||||
def add_tx_stats(self, vbox):
|
||||
hbox_stats = QHBoxLayout()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user