2013-04-06 23:34:12 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
2018-05-09 19:30:18 +02:00
|
|
|
from decimal import Decimal
|
2019-11-19 19:29:10 +01:00
|
|
|
from typing import Union
|
2018-05-09 19:30:18 +02:00
|
|
|
|
2024-09-05 16:20:01 +00:00
|
|
|
from PyQt6.QtCore import pyqtSignal, Qt, QSize
|
2025-01-23 12:58:28 +01:00
|
|
|
from PyQt6.QtGui import QPainter
|
2024-09-05 16:20:01 +00:00
|
|
|
from PyQt6.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy)
|
2013-04-06 23:34:12 +02:00
|
|
|
|
2020-06-28 03:50:34 +02:00
|
|
|
from .util import char_width_in_lineedit, ColorScheme
|
2019-06-29 05:27:28 +02:00
|
|
|
|
2018-06-04 21:01:47 +02:00
|
|
|
from electrum.util import (format_satoshis_plain, decimal_point_to_base_unit_name,
|
2024-02-03 05:13:09 +00:00
|
|
|
FEERATE_PRECISION, quantize_feerate, DECIMAL_POINT, UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE)
|
2022-10-19 17:58:05 +00:00
|
|
|
from electrum.bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC
|
|
|
|
|
|
|
|
|
|
_NOT_GIVEN = object() # sentinel value
|
2013-04-06 23:34:12 +02:00
|
|
|
|
2017-01-22 21:25:24 +03:00
|
|
|
|
2019-11-20 18:00:45 +01:00
|
|
|
class FreezableLineEdit(QLineEdit):
|
2014-06-24 17:44:31 +02:00
|
|
|
frozen = pyqtSignal()
|
2014-06-05 14:49:32 +02:00
|
|
|
|
|
|
|
|
def setFrozen(self, b):
|
|
|
|
|
self.setReadOnly(b)
|
2023-07-07 17:05:08 +02:00
|
|
|
self.setStyleSheet(ColorScheme.LIGHTBLUE.as_stylesheet(True) if b else '')
|
2014-06-24 17:44:31 +02:00
|
|
|
self.frozen.emit()
|
2014-06-05 14:49:32 +02:00
|
|
|
|
2023-06-26 12:58:10 +02:00
|
|
|
def isFrozen(self):
|
|
|
|
|
return self.isReadOnly()
|
2021-07-05 17:26:29 +02:00
|
|
|
|
2025-01-23 12:58:28 +01:00
|
|
|
|
2021-07-05 17:26:29 +02:00
|
|
|
class SizedFreezableLineEdit(FreezableLineEdit):
|
|
|
|
|
|
|
|
|
|
def __init__(self, *, width: int, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self._width = width
|
2024-09-05 16:20:01 +00:00
|
|
|
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
|
2021-09-08 16:36:07 +02:00
|
|
|
self.setMaximumWidth(width)
|
2021-07-05 17:26:29 +02:00
|
|
|
|
|
|
|
|
def sizeHint(self) -> QSize:
|
|
|
|
|
sh = super().sizeHint()
|
|
|
|
|
return QSize(self._width, sh.height())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AmountEdit(SizedFreezableLineEdit):
|
2014-09-07 18:45:06 +02:00
|
|
|
shortcut = pyqtSignal()
|
2013-04-06 23:34:12 +02:00
|
|
|
|
2022-10-19 17:58:05 +00:00
|
|
|
def __init__(self, base_unit, is_int=False, parent=None, *, max_amount=None):
|
2015-04-29 21:41:27 +09:00
|
|
|
# This seems sufficient for hundred-BTC amounts with 8 decimals
|
2021-07-05 17:26:29 +02:00
|
|
|
width = 16 * char_width_in_lineedit()
|
|
|
|
|
super().__init__(width=width, parent=parent)
|
2014-06-11 14:44:26 +02:00
|
|
|
self.base_unit = base_unit
|
|
|
|
|
self.textChanged.connect(self.numbify)
|
|
|
|
|
self.is_int = is_int
|
|
|
|
|
self.is_shortcut = False
|
2018-05-09 19:30:18 +02:00
|
|
|
self.extra_precision = 0
|
2022-10-19 17:58:05 +00:00
|
|
|
self.max_amount = max_amount
|
2014-06-11 14:44:26 +02:00
|
|
|
|
2014-06-16 18:38:28 +02:00
|
|
|
def decimal_point(self):
|
|
|
|
|
return 8
|
|
|
|
|
|
2018-05-09 19:30:18 +02:00
|
|
|
def max_precision(self):
|
|
|
|
|
return self.decimal_point() + self.extra_precision
|
|
|
|
|
|
2014-06-11 14:44:26 +02:00
|
|
|
def numbify(self):
|
2017-01-30 12:36:56 +03:00
|
|
|
text = self.text().strip()
|
2014-06-11 14:44:26 +02:00
|
|
|
if text == '!':
|
2014-09-07 18:45:06 +02:00
|
|
|
self.shortcut.emit()
|
|
|
|
|
return
|
2014-06-11 14:44:26 +02:00
|
|
|
pos = self.cursorPosition()
|
|
|
|
|
chars = '0123456789'
|
locale amounts: consistently use "." as dec point, and " " as thou sep
Always use "." as decimal point, and " " as thousands separator.
Previously,
- for decimal point, we were using
- "." in some places (e.g. AmountEdit, most fiat amounts), and
- `locale.localeconv()['decimal_point']` in others.
- for thousands separator, we were using
- "," in some places (most fiat amounts), and
- " " in others (format_satoshis)
I think it is better to be consistent even if whatever we pick differs from the locale.
Using whitespace for thousands separator (vs comma) is probably less confusing for people
whose locale would user "." for ts and "," for dp (as in e.g. German).
The alternative option would be to always use the locale. Even if we decide to do that later,
this refactoring should be useful.
closes https://github.com/spesmilo/electrum/issues/2629
2023-01-10 14:45:35 +00:00
|
|
|
if not self.is_int: chars += DECIMAL_POINT
|
2014-06-11 14:44:26 +02:00
|
|
|
s = ''.join([i for i in text if i in chars])
|
|
|
|
|
if not self.is_int:
|
locale amounts: consistently use "." as dec point, and " " as thou sep
Always use "." as decimal point, and " " as thousands separator.
Previously,
- for decimal point, we were using
- "." in some places (e.g. AmountEdit, most fiat amounts), and
- `locale.localeconv()['decimal_point']` in others.
- for thousands separator, we were using
- "," in some places (most fiat amounts), and
- " " in others (format_satoshis)
I think it is better to be consistent even if whatever we pick differs from the locale.
Using whitespace for thousands separator (vs comma) is probably less confusing for people
whose locale would user "." for ts and "," for dp (as in e.g. German).
The alternative option would be to always use the locale. Even if we decide to do that later,
this refactoring should be useful.
closes https://github.com/spesmilo/electrum/issues/2629
2023-01-10 14:45:35 +00:00
|
|
|
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()]
|
2022-10-19 17:58:05 +00:00
|
|
|
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)
|
2014-06-11 14:44:26 +02:00
|
|
|
self.setText(s)
|
2015-07-03 20:14:12 +09:00
|
|
|
# setText sets Modified to False. Instead we want to remember
|
|
|
|
|
# if updates were because of user modification.
|
|
|
|
|
self.setModified(self.hasFocus())
|
2014-06-11 14:44:26 +02:00
|
|
|
self.setCursorPosition(pos)
|
|
|
|
|
|
|
|
|
|
def paintEvent(self, event):
|
|
|
|
|
QLineEdit.paintEvent(self, event)
|
|
|
|
|
if self.base_unit:
|
2017-09-23 05:54:38 +02:00
|
|
|
panel = QStyleOptionFrame()
|
2014-06-11 18:52:55 +02:00
|
|
|
self.initStyleOption(panel)
|
2024-09-05 16:20:01 +00:00
|
|
|
textRect = self.style().subElementRect(QStyle.SubElement.SE_LineEditContents, panel, self)
|
2014-06-11 18:52:55 +02:00
|
|
|
textRect.adjust(2, 0, -10, 0)
|
|
|
|
|
painter = QPainter(self)
|
2020-06-28 03:50:34 +02:00
|
|
|
painter.setPen(ColorScheme.GRAY.as_color())
|
2024-09-05 16:20:01 +00:00
|
|
|
painter.drawText(textRect, int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter), self.base_unit())
|
2014-06-11 14:44:26 +02:00
|
|
|
|
2022-10-19 17:22:23 +00:00
|
|
|
def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]:
|
2014-07-16 15:33:59 +02:00
|
|
|
try:
|
locale amounts: consistently use "." as dec point, and " " as thou sep
Always use "." as decimal point, and " " as thousands separator.
Previously,
- for decimal point, we were using
- "." in some places (e.g. AmountEdit, most fiat amounts), and
- `locale.localeconv()['decimal_point']` in others.
- for thousands separator, we were using
- "," in some places (most fiat amounts), and
- " " in others (format_satoshis)
I think it is better to be consistent even if whatever we pick differs from the locale.
Using whitespace for thousands separator (vs comma) is probably less confusing for people
whose locale would user "." for ts and "," for dp (as in e.g. German).
The alternative option would be to always use the locale. Even if we decide to do that later,
this refactoring should be useful.
closes https://github.com/spesmilo/electrum/issues/2629
2023-01-10 14:45:35 +00:00
|
|
|
text = text.replace(DECIMAL_POINT, '.')
|
2022-10-19 17:22:23 +00:00
|
|
|
return (int if self.is_int else Decimal)(text)
|
2023-04-23 01:33:12 +00:00
|
|
|
except Exception:
|
2014-07-16 15:33:59 +02:00
|
|
|
return None
|
2014-06-11 14:44:26 +02:00
|
|
|
|
2022-10-19 17:22:23 +00:00
|
|
|
def get_amount(self) -> Union[None, Decimal, int]:
|
2022-10-19 17:58:05 +00:00
|
|
|
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
|
2022-10-19 17:22:23 +00:00
|
|
|
|
|
|
|
|
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)
|
2017-12-01 09:58:24 +01:00
|
|
|
|
2014-06-11 14:44:26 +02:00
|
|
|
|
|
|
|
|
class BTCAmountEdit(AmountEdit):
|
|
|
|
|
|
2022-10-19 17:58:05 +00:00
|
|
|
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)
|
2014-06-05 12:40:07 +02:00
|
|
|
self.decimal_point = decimal_point
|
2013-04-06 23:34:12 +02:00
|
|
|
|
2014-06-11 18:52:55 +02:00
|
|
|
def _base_unit(self):
|
2018-05-05 12:42:17 +02:00
|
|
|
return decimal_point_to_base_unit_name(self.decimal_point())
|
2014-06-05 12:40:07 +02:00
|
|
|
|
2022-10-19 17:22:23 +00:00
|
|
|
def _get_amount_from_text(self, text):
|
2020-06-22 22:37:58 +02:00
|
|
|
# returns amt in satoshis
|
2014-06-11 18:17:27 +02:00
|
|
|
try:
|
locale amounts: consistently use "." as dec point, and " " as thou sep
Always use "." as decimal point, and " " as thousands separator.
Previously,
- for decimal point, we were using
- "." in some places (e.g. AmountEdit, most fiat amounts), and
- `locale.localeconv()['decimal_point']` in others.
- for thousands separator, we were using
- "," in some places (most fiat amounts), and
- " " in others (format_satoshis)
I think it is better to be consistent even if whatever we pick differs from the locale.
Using whitespace for thousands separator (vs comma) is probably less confusing for people
whose locale would user "." for ts and "," for dp (as in e.g. German).
The alternative option would be to always use the locale. Even if we decide to do that later,
this refactoring should be useful.
closes https://github.com/spesmilo/electrum/issues/2629
2023-01-10 14:45:35 +00:00
|
|
|
text = text.replace(DECIMAL_POINT, '.')
|
2022-10-19 17:22:23 +00:00
|
|
|
x = Decimal(text)
|
2023-04-23 01:33:12 +00:00
|
|
|
except Exception:
|
2014-06-05 12:40:07 +02:00
|
|
|
return None
|
2018-05-09 19:30:18 +02:00
|
|
|
# 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)
|
2014-06-05 12:40:07 +02:00
|
|
|
|
2022-10-19 17:22:23 +00:00
|
|
|
def _get_text_from_amount(self, amount_sat):
|
locale amounts: consistently use "." as dec point, and " " as thou sep
Always use "." as decimal point, and " " as thousands separator.
Previously,
- for decimal point, we were using
- "." in some places (e.g. AmountEdit, most fiat amounts), and
- `locale.localeconv()['decimal_point']` in others.
- for thousands separator, we were using
- "," in some places (most fiat amounts), and
- " " in others (format_satoshis)
I think it is better to be consistent even if whatever we pick differs from the locale.
Using whitespace for thousands separator (vs comma) is probably less confusing for people
whose locale would user "." for ts and "," for dp (as in e.g. German).
The alternative option would be to always use the locale. Even if we decide to do that later,
this refactoring should be useful.
closes https://github.com/spesmilo/electrum/issues/2629
2023-01-10 14:45:35 +00:00
|
|
|
text = format_satoshis_plain(amount_sat, decimal_point=self.decimal_point())
|
|
|
|
|
text = text.replace('.', DECIMAL_POINT)
|
|
|
|
|
return text
|
2022-10-19 17:22:23 +00:00
|
|
|
|
2020-06-22 22:37:58 +02:00
|
|
|
def setAmount(self, amount_sat):
|
|
|
|
|
if amount_sat is None:
|
|
|
|
|
self.setText(" ") # Space forces repaint in case units changed
|
2015-05-27 16:34:42 +09:00
|
|
|
else:
|
2022-10-19 17:22:23 +00:00
|
|
|
text = self._get_text_from_amount(amount_sat)
|
|
|
|
|
self.setText(text)
|
2023-06-26 12:58:10 +02:00
|
|
|
self.setFrozen(self.isFrozen()) # re-apply styling, as it is nuked by setText (?)
|
2020-06-25 22:03:40 +02:00
|
|
|
self.repaint() # macOS hack for #6269
|
2015-08-04 07:15:54 +02:00
|
|
|
|
2017-12-18 22:26:29 +01:00
|
|
|
|
|
|
|
|
class FeerateEdit(BTCAmountEdit):
|
2018-05-09 19:30:18 +02:00
|
|
|
|
2022-10-19 17:58:05 +00:00
|
|
|
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)
|
2018-05-09 19:30:18 +02:00
|
|
|
self.extra_precision = FEERATE_PRECISION
|
|
|
|
|
|
2015-08-04 07:15:54 +02:00
|
|
|
def _base_unit(self):
|
2024-02-03 05:13:09 +00:00
|
|
|
return UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE
|
2017-12-18 22:26:29 +01:00
|
|
|
|
2022-10-19 17:22:23 +00:00
|
|
|
def _get_amount_from_text(self, text):
|
|
|
|
|
sat_per_byte_amount = super()._get_amount_from_text(text)
|
2018-06-04 21:17:25 +02:00
|
|
|
return quantize_feerate(sat_per_byte_amount)
|
2018-06-04 21:01:47 +02:00
|
|
|
|
2022-10-19 17:22:23 +00:00
|
|
|
def _get_text_from_amount(self, amount):
|
2018-06-04 21:01:47 +02:00
|
|
|
amount = quantize_feerate(amount)
|
2022-10-19 17:22:23 +00:00
|
|
|
return super()._get_text_from_amount(amount)
|