# 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 from PyQt5.QtCore import Qt, QDateTime 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 class LockTimeEdit(QWidget): 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.locktime_height_e = LockTimeHeightEdit() self.locktime_date_e = LockTimeDateEdit() 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) 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: 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: return None def set_locktime(self, x: Any) -> None: try: x = int(x) except: 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()) self.help_palette = QPalette() 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(self.help_palette.brush(QPalette.Disabled, QPalette.Text).color()) painter.drawText(textRect, Qt.AlignRight | Qt.AlignVCenter, "height") class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): min_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX + 1 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: self.setDateTime(QDateTime.currentDateTime()) return dt = datetime.fromtimestamp(x) self.setDateTime(dt)