Browse Source

qt tx dialog: make scid and addr texts clickable in IO fields

based on:
7eea0b6dae
52d845017c
master
SomberNight 3 years ago
parent
commit
7d42676785
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 2
      electrum/address_synchronizer.py
  2. 34
      electrum/gui/qt/main_window.py
  3. 103
      electrum/gui/qt/transaction_dialog.py

2
electrum/address_synchronizer.py

@ -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

34
electrum/gui/qt/main_window.py

@ -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

103
electrum/gui/qt/transaction_dialog.py

@ -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()

Loading…
Cancel
Save