locktimeedit.py (6133B)
1 # Copyright (C) 2020 The Electrum developers 2 # Distributed under the MIT software license, see the accompanying 3 # file LICENCE or http://www.opensource.org/licenses/mit-license.php 4 5 import time 6 from datetime import datetime 7 from typing import Optional, Any 8 9 from PyQt5.QtCore import Qt, QDateTime 10 from PyQt5.QtGui import QPalette, QPainter 11 from PyQt5.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox, 12 QHBoxLayout, QDateTimeEdit) 13 14 from electrum.i18n import _ 15 from electrum.bitcoin import NLOCKTIME_MIN, NLOCKTIME_MAX, NLOCKTIME_BLOCKHEIGHT_MAX 16 17 from .util import char_width_in_lineedit, ColorScheme 18 19 20 class LockTimeEdit(QWidget): 21 22 def __init__(self, parent=None): 23 QWidget.__init__(self, parent) 24 25 hbox = QHBoxLayout() 26 self.setLayout(hbox) 27 hbox.setContentsMargins(0, 0, 0, 0) 28 hbox.setSpacing(0) 29 30 self.locktime_raw_e = LockTimeRawEdit(self) 31 self.locktime_height_e = LockTimeHeightEdit(self) 32 self.locktime_date_e = LockTimeDateEdit(self) 33 self.editors = [self.locktime_raw_e, self.locktime_height_e, self.locktime_date_e] 34 35 self.combo = QComboBox() 36 options = [_("Raw"), _("Block height"), _("Date")] 37 option_index_to_editor_map = { 38 0: self.locktime_raw_e, 39 1: self.locktime_height_e, 40 2: self.locktime_date_e, 41 } 42 default_index = 1 43 self.combo.addItems(options) 44 45 def on_current_index_changed(i): 46 for w in self.editors: 47 w.setVisible(False) 48 w.setEnabled(False) 49 prev_locktime = self.editor.get_locktime() 50 self.editor = option_index_to_editor_map[i] 51 if self.editor.is_acceptable_locktime(prev_locktime): 52 self.editor.set_locktime(prev_locktime) 53 self.editor.setVisible(True) 54 self.editor.setEnabled(True) 55 56 self.editor = option_index_to_editor_map[default_index] 57 self.combo.currentIndexChanged.connect(on_current_index_changed) 58 self.combo.setCurrentIndex(default_index) 59 on_current_index_changed(default_index) 60 61 hbox.addWidget(self.combo) 62 for w in self.editors: 63 hbox.addWidget(w) 64 hbox.addStretch(1) 65 66 def get_locktime(self) -> Optional[int]: 67 return self.editor.get_locktime() 68 69 def set_locktime(self, x: Any) -> None: 70 self.editor.set_locktime(x) 71 72 73 class _LockTimeEditor: 74 min_allowed_value = NLOCKTIME_MIN 75 max_allowed_value = NLOCKTIME_MAX 76 77 def get_locktime(self) -> Optional[int]: 78 raise NotImplementedError() 79 80 def set_locktime(self, x: Any) -> None: 81 raise NotImplementedError() 82 83 @classmethod 84 def is_acceptable_locktime(cls, x: Any) -> bool: 85 if not x: # e.g. empty string 86 return True 87 try: 88 x = int(x) 89 except: 90 return False 91 return cls.min_allowed_value <= x <= cls.max_allowed_value 92 93 94 class LockTimeRawEdit(QLineEdit, _LockTimeEditor): 95 96 def __init__(self, parent=None): 97 QLineEdit.__init__(self, parent) 98 self.setFixedWidth(14 * char_width_in_lineedit()) 99 self.textChanged.connect(self.numbify) 100 101 def numbify(self): 102 text = self.text().strip() 103 chars = '0123456789' 104 pos = self.cursorPosition() 105 pos = len(''.join([i for i in text[:pos] if i in chars])) 106 s = ''.join([i for i in text if i in chars]) 107 self.set_locktime(s) 108 # setText sets Modified to False. Instead we want to remember 109 # if updates were because of user modification. 110 self.setModified(self.hasFocus()) 111 self.setCursorPosition(pos) 112 113 def get_locktime(self) -> Optional[int]: 114 try: 115 return int(str(self.text())) 116 except: 117 return None 118 119 def set_locktime(self, x: Any) -> None: 120 try: 121 x = int(x) 122 except: 123 self.setText('') 124 return 125 x = max(x, self.min_allowed_value) 126 x = min(x, self.max_allowed_value) 127 self.setText(str(x)) 128 129 130 class LockTimeHeightEdit(LockTimeRawEdit): 131 max_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX 132 133 def __init__(self, parent=None): 134 LockTimeRawEdit.__init__(self, parent) 135 self.setFixedWidth(20 * char_width_in_lineedit()) 136 137 def paintEvent(self, event): 138 super().paintEvent(event) 139 panel = QStyleOptionFrame() 140 self.initStyleOption(panel) 141 textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self) 142 textRect.adjust(2, 0, -10, 0) 143 painter = QPainter(self) 144 painter.setPen(ColorScheme.GRAY.as_color()) 145 painter.drawText(textRect, Qt.AlignRight | Qt.AlignVCenter, "height") 146 147 148 def get_max_allowed_timestamp() -> int: 149 ts = NLOCKTIME_MAX 150 # Test if this value is within the valid timestamp limits (which is platform-dependent). 151 # see #6170 152 try: 153 datetime.fromtimestamp(ts) 154 except (OSError, OverflowError): 155 ts = 2 ** 31 - 1 # INT32_MAX 156 datetime.fromtimestamp(ts) # test if raises 157 return ts 158 159 160 class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): 161 min_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX + 1 162 max_allowed_value = get_max_allowed_timestamp() 163 164 def __init__(self, parent=None): 165 QDateTimeEdit.__init__(self, parent) 166 self.setMinimumDateTime(datetime.fromtimestamp(self.min_allowed_value)) 167 self.setMaximumDateTime(datetime.fromtimestamp(self.max_allowed_value)) 168 self.setDateTime(QDateTime.currentDateTime()) 169 170 def get_locktime(self) -> Optional[int]: 171 dt = self.dateTime().toPyDateTime() 172 locktime = int(time.mktime(dt.timetuple())) 173 return locktime 174 175 def set_locktime(self, x: Any) -> None: 176 if not self.is_acceptable_locktime(x): 177 self.setDateTime(QDateTime.currentDateTime()) 178 return 179 try: 180 x = int(x) 181 except: 182 self.setDateTime(QDateTime.currentDateTime()) 183 return 184 dt = datetime.fromtimestamp(x) 185 self.setDateTime(dt)