Files
pallectrum/electrum/gui/qt/locktimeedit.py

193 lines
6.4 KiB
Python
Raw Normal View History

# Copyright (C) 2020 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
import time
from datetime import datetime
from typing import Optional, Any
2023-02-09 10:52:09 +01:00
from PyQt5.QtCore import Qt, QDateTime, pyqtSignal
from PyQt5.QtGui import QPalette, QPainter
from PyQt5.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox,
QHBoxLayout, QDateTimeEdit)
from electrum.i18n import _
from electrum.bitcoin import NLOCKTIME_MIN, NLOCKTIME_MAX, NLOCKTIME_BLOCKHEIGHT_MAX
from .util import char_width_in_lineedit, ColorScheme
class LockTimeEdit(QWidget):
2023-02-09 10:52:09 +01:00
valueEdited = pyqtSignal()
def __init__(self, parent=None):
QWidget.__init__(self, parent)
hbox = QHBoxLayout()
self.setLayout(hbox)
hbox.setContentsMargins(0, 0, 0, 0)
hbox.setSpacing(0)
self.locktime_raw_e = LockTimeRawEdit(self)
self.locktime_height_e = LockTimeHeightEdit(self)
self.locktime_date_e = LockTimeDateEdit(self)
self.editors = [self.locktime_raw_e, self.locktime_height_e, self.locktime_date_e]
self.combo = QComboBox()
options = [_("Raw"), _("Block height"), _("Date")]
option_index_to_editor_map = {
0: self.locktime_raw_e,
1: self.locktime_height_e,
2: self.locktime_date_e,
}
default_index = 1
self.combo.addItems(options)
def on_current_index_changed(i):
for w in self.editors:
w.setVisible(False)
w.setEnabled(False)
prev_locktime = self.editor.get_locktime()
self.editor = option_index_to_editor_map[i]
if self.editor.is_acceptable_locktime(prev_locktime):
self.editor.set_locktime(prev_locktime)
self.editor.setVisible(True)
self.editor.setEnabled(True)
self.editor = option_index_to_editor_map[default_index]
self.combo.currentIndexChanged.connect(on_current_index_changed)
self.combo.setCurrentIndex(default_index)
on_current_index_changed(default_index)
hbox.addWidget(self.combo)
for w in self.editors:
hbox.addWidget(w)
hbox.addStretch(1)
2023-02-09 10:52:09 +01:00
self.locktime_height_e.textEdited.connect(self.valueEdited.emit)
self.locktime_raw_e.textEdited.connect(self.valueEdited.emit)
self.locktime_date_e.dateTimeChanged.connect(self.valueEdited.emit)
self.combo.currentIndexChanged.connect(self.valueEdited.emit)
def get_locktime(self) -> Optional[int]:
return self.editor.get_locktime()
def set_locktime(self, x: Any) -> None:
self.editor.set_locktime(x)
class _LockTimeEditor:
min_allowed_value = NLOCKTIME_MIN
max_allowed_value = NLOCKTIME_MAX
def get_locktime(self) -> Optional[int]:
raise NotImplementedError()
def set_locktime(self, x: Any) -> None:
raise NotImplementedError()
@classmethod
def is_acceptable_locktime(cls, x: Any) -> bool:
if not x: # e.g. empty string
return True
try:
x = int(x)
except Exception:
return False
return cls.min_allowed_value <= x <= cls.max_allowed_value
class LockTimeRawEdit(QLineEdit, _LockTimeEditor):
def __init__(self, parent=None):
QLineEdit.__init__(self, parent)
self.setFixedWidth(14 * char_width_in_lineedit())
self.textChanged.connect(self.numbify)
def numbify(self):
text = self.text().strip()
chars = '0123456789'
pos = self.cursorPosition()
pos = len(''.join([i for i in text[:pos] if i in chars]))
s = ''.join([i for i in text if i in chars])
self.set_locktime(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 get_locktime(self) -> Optional[int]:
try:
return int(str(self.text()))
except Exception:
return None
def set_locktime(self, x: Any) -> None:
try:
x = int(x)
except Exception:
self.setText('')
return
x = max(x, self.min_allowed_value)
x = min(x, self.max_allowed_value)
self.setText(str(x))
class LockTimeHeightEdit(LockTimeRawEdit):
max_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX
def __init__(self, parent=None):
LockTimeRawEdit.__init__(self, parent)
self.setFixedWidth(20 * char_width_in_lineedit())
def paintEvent(self, event):
super().paintEvent(event)
panel = QStyleOptionFrame()
self.initStyleOption(panel)
textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self)
textRect.adjust(2, 0, -10, 0)
painter = QPainter(self)
painter.setPen(ColorScheme.GRAY.as_color())
painter.drawText(textRect, int(Qt.AlignRight | Qt.AlignVCenter), "height")
def get_max_allowed_timestamp() -> int:
ts = NLOCKTIME_MAX
# Test if this value is within the valid timestamp limits (which is platform-dependent).
# see #6170
try:
datetime.fromtimestamp(ts)
except (OSError, OverflowError):
ts = 2 ** 31 - 1 # INT32_MAX
datetime.fromtimestamp(ts) # test if raises
return ts
class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor):
min_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX + 1
max_allowed_value = get_max_allowed_timestamp()
def __init__(self, parent=None):
QDateTimeEdit.__init__(self, parent)
self.setMinimumDateTime(datetime.fromtimestamp(self.min_allowed_value))
self.setMaximumDateTime(datetime.fromtimestamp(self.max_allowed_value))
self.setDateTime(QDateTime.currentDateTime())
def get_locktime(self) -> Optional[int]:
dt = self.dateTime().toPyDateTime()
locktime = int(time.mktime(dt.timetuple()))
return locktime
def set_locktime(self, x: Any) -> None:
if not self.is_acceptable_locktime(x):
self.setDateTime(QDateTime.currentDateTime())
return
try:
x = int(x)
except Exception:
self.setDateTime(QDateTime.currentDateTime())
return
dt = datetime.fromtimestamp(x)
self.setDateTime(dt)