178 lines
6.4 KiB
Python
178 lines
6.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from decimal import Decimal
|
|
from typing import Union
|
|
|
|
from PyQt6.QtCore import pyqtSignal, Qt, QSize
|
|
from PyQt6.QtGui import QPainter
|
|
from PyQt6.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy)
|
|
|
|
from .util import char_width_in_lineedit, ColorScheme
|
|
|
|
from electrum.util import (format_satoshis_plain, decimal_point_to_base_unit_name,
|
|
FEERATE_PRECISION, quantize_feerate, DECIMAL_POINT, UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE)
|
|
from electrum.bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC
|
|
|
|
_NOT_GIVEN = object() # sentinel value
|
|
|
|
|
|
class FreezableLineEdit(QLineEdit):
|
|
frozen = pyqtSignal()
|
|
|
|
def setFrozen(self, b):
|
|
self.setReadOnly(b)
|
|
self.setStyleSheet(ColorScheme.LIGHTBLUE.as_stylesheet(True) if b else '')
|
|
self.frozen.emit()
|
|
|
|
def isFrozen(self):
|
|
return self.isReadOnly()
|
|
|
|
|
|
class SizedFreezableLineEdit(FreezableLineEdit):
|
|
|
|
def __init__(self, *, width: int, parent=None):
|
|
super().__init__(parent)
|
|
self._width = width
|
|
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
|
|
self.setMaximumWidth(width)
|
|
|
|
def sizeHint(self) -> QSize:
|
|
sh = super().sizeHint()
|
|
return QSize(self._width, sh.height())
|
|
|
|
|
|
class AmountEdit(SizedFreezableLineEdit):
|
|
shortcut = pyqtSignal()
|
|
|
|
def __init__(self, base_unit, is_int=False, parent=None, *, max_amount=None):
|
|
# This seems sufficient for hundred-BTC amounts with 8 decimals
|
|
width = 16 * char_width_in_lineedit()
|
|
super().__init__(width=width, parent=parent)
|
|
self.base_unit = base_unit
|
|
self.textChanged.connect(self.numbify)
|
|
self.is_int = is_int
|
|
self.is_shortcut = False
|
|
self.extra_precision = 0
|
|
self.max_amount = max_amount
|
|
|
|
def decimal_point(self):
|
|
return 8
|
|
|
|
def max_precision(self):
|
|
return self.decimal_point() + self.extra_precision
|
|
|
|
def numbify(self):
|
|
text = self.text().strip()
|
|
if text == '!':
|
|
self.shortcut.emit()
|
|
return
|
|
pos = self.cursorPosition()
|
|
chars = '0123456789'
|
|
if not self.is_int: chars += DECIMAL_POINT
|
|
s = ''.join([i for i in text if i in chars])
|
|
if not self.is_int:
|
|
if DECIMAL_POINT in s:
|
|
p = s.find(DECIMAL_POINT)
|
|
s = s.replace(DECIMAL_POINT, '')
|
|
s = s[:p] + DECIMAL_POINT + s[p:p+self.max_precision()]
|
|
if self.max_amount:
|
|
if (amt := self._get_amount_from_text(s)) and amt >= self.max_amount:
|
|
s = self._get_text_from_amount(self.max_amount)
|
|
self.setText(s)
|
|
# setText sets Modified to False. Instead we want to remember
|
|
# if updates were because of user modification.
|
|
self.setModified(self.hasFocus())
|
|
self.setCursorPosition(pos)
|
|
|
|
def paintEvent(self, event):
|
|
QLineEdit.paintEvent(self, event)
|
|
if self.base_unit:
|
|
panel = QStyleOptionFrame()
|
|
self.initStyleOption(panel)
|
|
textRect = self.style().subElementRect(QStyle.SubElement.SE_LineEditContents, panel, self)
|
|
textRect.adjust(2, 0, -10, 0)
|
|
painter = QPainter(self)
|
|
painter.setPen(ColorScheme.GRAY.as_color())
|
|
painter.drawText(textRect, int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter), self.base_unit())
|
|
|
|
def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]:
|
|
try:
|
|
text = text.replace(DECIMAL_POINT, '.')
|
|
return (int if self.is_int else Decimal)(text)
|
|
except Exception:
|
|
return None
|
|
|
|
def get_amount(self) -> Union[None, Decimal, int]:
|
|
amt = self._get_amount_from_text(str(self.text()))
|
|
if self.max_amount and amt and amt >= self.max_amount:
|
|
return self.max_amount
|
|
return amt
|
|
|
|
def _get_text_from_amount(self, amount) -> str:
|
|
return "%d" % amount
|
|
|
|
def setAmount(self, amount):
|
|
text = self._get_text_from_amount(amount)
|
|
self.setText(text)
|
|
|
|
|
|
class BTCAmountEdit(AmountEdit):
|
|
|
|
def __init__(self, decimal_point, is_int=False, parent=None, *, max_amount=_NOT_GIVEN):
|
|
if max_amount is _NOT_GIVEN:
|
|
max_amount = TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN
|
|
AmountEdit.__init__(self, self._base_unit, is_int, parent, max_amount=max_amount)
|
|
self.decimal_point = decimal_point
|
|
|
|
def _base_unit(self):
|
|
return decimal_point_to_base_unit_name(self.decimal_point())
|
|
|
|
def _get_amount_from_text(self, text):
|
|
# returns amt in satoshis
|
|
try:
|
|
text = text.replace(DECIMAL_POINT, '.')
|
|
x = Decimal(text)
|
|
except Exception:
|
|
return None
|
|
# scale it to max allowed precision, make it an int
|
|
power = pow(10, self.max_precision())
|
|
max_prec_amount = int(power * x)
|
|
# if the max precision is simply what unit conversion allows, just return
|
|
if self.max_precision() == self.decimal_point():
|
|
return max_prec_amount
|
|
# otherwise, scale it back to the expected unit
|
|
amount = Decimal(max_prec_amount) / pow(10, self.max_precision()-self.decimal_point())
|
|
return Decimal(amount) if not self.is_int else int(amount)
|
|
|
|
def _get_text_from_amount(self, amount_sat):
|
|
text = format_satoshis_plain(amount_sat, decimal_point=self.decimal_point())
|
|
text = text.replace('.', DECIMAL_POINT)
|
|
return text
|
|
|
|
def setAmount(self, amount_sat):
|
|
if amount_sat is None:
|
|
self.setText(" ") # Space forces repaint in case units changed
|
|
else:
|
|
text = self._get_text_from_amount(amount_sat)
|
|
self.setText(text)
|
|
self.setFrozen(self.isFrozen()) # re-apply styling, as it is nuked by setText (?)
|
|
self.repaint() # macOS hack for #6269
|
|
|
|
|
|
class FeerateEdit(BTCAmountEdit):
|
|
|
|
def __init__(self, decimal_point, is_int=False, parent=None, *, max_amount=_NOT_GIVEN):
|
|
super().__init__(decimal_point, is_int, parent, max_amount=max_amount)
|
|
self.extra_precision = FEERATE_PRECISION
|
|
|
|
def _base_unit(self):
|
|
return UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE
|
|
|
|
def _get_amount_from_text(self, text):
|
|
sat_per_byte_amount = super()._get_amount_from_text(text)
|
|
return quantize_feerate(sat_per_byte_amount)
|
|
|
|
def _get_text_from_amount(self, amount):
|
|
amount = quantize_feerate(amount)
|
|
return super()._get_text_from_amount(amount)
|