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} conflicting_txns -= {tx_hash}
return conflicting_txns return conflicting_txns
def get_transaction(self, txid: str) -> Transaction: def get_transaction(self, txid: str) -> Optional[Transaction]:
tx = self.db.get_transaction(txid) tx = self.db.get_transaction(txid)
if tx: if tx:
# add verified tx info # add verified tx info

34
electrum/gui/qt/main_window.py

@ -2190,30 +2190,44 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
if tx: if tx:
self.show_transaction(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 from electrum import transaction
txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':') if txid is None:
if ok and txid: txid, ok = QInputDialog.getText(parent, _('Lookup transaction'), _('Transaction ID') + ':')
txid = str(txid).strip() if not ok:
raw_tx = self._fetch_tx_from_network(txid) 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: if not raw_tx:
return return
tx = transaction.Transaction(raw_tx) 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: if not self.network:
self.show_message(_("You are offline.")) self.show_message(_("You are offline."), parent=parent)
return return
try: try:
raw_tx = self.network.run_from_another_thread( raw_tx = self.network.run_from_another_thread(
self.network.get_transaction(txid, timeout=10)) self.network.get_transaction(txid, timeout=10))
except UntrustedServerReturnedError as e: except UntrustedServerReturnedError as e:
self.logger.info(f"Error getting transaction from network: {repr(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 return
except Exception as e: 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
return raw_tx return raw_tx

103
electrum/gui/qt/transaction_dialog.py

@ -28,19 +28,20 @@ import copy
import datetime import datetime
import traceback import traceback
import time 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 functools import partial
from decimal import Decimal 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.QtGui import QTextCharFormat, QBrush, QFont, QPixmap
from PyQt5.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGridLayout, 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 import qrcode
from qrcode import exceptions from qrcode import exceptions
from electrum.simple_config import SimpleConfig from electrum.simple_config import SimpleConfig
from electrum.util import quantize_feerate from electrum.util import quantize_feerate
from electrum import bitcoin
from electrum.bitcoin import base_encode, NLOCKTIME_BLOCKHEIGHT_MAX from electrum.bitcoin import base_encode, NLOCKTIME_BLOCKHEIGHT_MAX
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import run_hook from electrum.plugin import run_hook
@ -74,7 +75,7 @@ class TxFiatLabel(QLabel):
def setAmount(self, fiat_fee): def setAmount(self, fiat_fee):
self.setText(('%s' % fiat_fee) if fiat_fee else '') self.setText(('%s' % fiat_fee) if fiat_fee else '')
class QTextEditWithDefaultSize(QTextEdit): class QTextBrowserWithDefaultSize(QTextBrowser):
def sizeHint(self): def sizeHint(self):
return QSize(0, 100) return QSize(0, 100)
@ -86,7 +87,11 @@ class TxInOutWidget(QWidget):
self.wallet = wallet self.wallet = wallet
self.main_window = main_window self.main_window = main_window
self.inputs_header = QLabel() 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( self.txo_color_recv = TxOutputColoring(
legend=_("Receiving Address"), color=ColorScheme.GREEN, tooltip=_("Wallet receive address")) legend=_("Receiving Address"), color=ColorScheme.GREEN, tooltip=_("Wallet receive address"))
self.txo_color_change = TxOutputColoring( self.txo_color_change = TxOutputColoring(
@ -94,7 +99,11 @@ class TxInOutWidget(QWidget):
self.txo_color_2fa = TxOutputColoring( self.txo_color_2fa = TxOutputColoring(
legend=_("TrustedCoin (2FA) batch fee"), color=ColorScheme.BLUE, tooltip=_("TrustedCoin (2FA) fee for the next batch of transactions")) legend=_("TrustedCoin (2FA) batch fee"), color=ColorScheme.BLUE, tooltip=_("TrustedCoin (2FA) fee for the next batch of transactions"))
self.outputs_header = QLabel() 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.inputs_textedit.setMinimumWidth(950)
self.outputs_textedit.setMinimumWidth(950) self.outputs_textedit.setMinimumWidth(950)
@ -125,43 +134,59 @@ class TxInOutWidget(QWidget):
self.inputs_header.setText(inputs_header_text) 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 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 nonlocal tf_used_recv, tf_used_change, tf_used_2fa
if self.wallet.is_mine(addr): if self.wallet.is_mine(addr):
if self.wallet.is_change(addr): if self.wallet.is_change(addr):
tf_used_change = True tf_used_change = True
return self.txo_color_change.text_char_format fmt = QTextCharFormat(self.txo_color_change.text_char_format)
else: else:
tf_used_recv = True 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): elif self.wallet.is_billing_address(addr):
tf_used_2fa = True tf_used_2fa = True
return self.txo_color_2fa.text_char_format return self.txo_color_2fa.text_char_format
return ext 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 = self.inputs_textedit
i_text.clear() i_text.clear()
i_text.setFont(QFont(MONOSPACE_FONT)) i_text.setFont(QFont(MONOSPACE_FONT))
i_text.setReadOnly(True) i_text.setReadOnly(True)
cursor = i_text.textCursor() 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) addr = self.wallet.adb.get_txin_address(txin)
txin_value = self.wallet.adb.get_txin_value(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())) self.outputs_header.setText(_("Outputs") + ' (%d)'%len(self.tx.outputs()))
o_text = self.outputs_textedit o_text = self.outputs_textedit
@ -176,14 +201,39 @@ class TxInOutWidget(QWidget):
short_id = ShortID.from_components(tx_height, tx_pos, index) short_id = ShortID.from_components(tx_height, tx_pos, index)
else: else:
short_id = TxOutpoint(tx_hash, index).short_name() short_id = TxOutpoint(tx_hash, index).short_name()
addr, value = o.get_ui_address_str(), o.value 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_recv.legend_label.setVisible(tf_used_recv)
self.txo_color_change.legend_label.setVisible(tf_used_change) self.txo_color_change.legend_label.setVisible(tf_used_change)
self.txo_color_2fa.legend_label.setVisible(tf_used_2fa) 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__) _logger = get_logger(__name__)
dialogs = [] # Otherwise python randomly garbage collects the dialogs... dialogs = [] # Otherwise python randomly garbage collects the dialogs...
@ -652,7 +702,6 @@ class BaseTxDialog(QDialog, MessageBoxMixin):
run_hook('transaction_dialog_update', self) run_hook('transaction_dialog_update', self)
def add_tx_stats(self, vbox): def add_tx_stats(self, vbox):
hbox_stats = QHBoxLayout() hbox_stats = QHBoxLayout()

Loading…
Cancel
Save