256 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # 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 . import qt_resources
 | |
| if qt_resources.QT_VERSION == 5:
 | |
|     from PyQt5.QtCore import Qt, QDateTime, pyqtSignal
 | |
|     from PyQt5.QtGui import QPalette, QPainter
 | |
|     from PyQt5.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox, QHBoxLayout, QDateTimeEdit)
 | |
| else:
 | |
|     from PyQt6.QtCore import Qt, QDateTime, pyqtSignal
 | |
|     from PyQt6.QtGui import QPalette, QPainter
 | |
|     from PyQt6.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 electrum.gui.qt.util import char_width_in_lineedit, ColorScheme
 | |
| 
 | |
| 
 | |
| class HeirsLockTimeEdit(QWidget):
 | |
| 
 | |
|     valueEdited = pyqtSignal()
 | |
|     locktime_threshold = 50000000
 | |
|     def __init__(self, parent=None,default_index = 1):
 | |
|         QWidget.__init__(self, parent)
 | |
| 
 | |
|         hbox = QHBoxLayout()
 | |
|         self.setLayout(hbox)
 | |
|         hbox.setContentsMargins(0, 0, 0, 0)
 | |
|         hbox.setSpacing(0)
 | |
| 
 | |
|         self.locktime_raw_e = LockTimeRawEdit(self,time_edit = self)
 | |
|         #self.locktime_height_e = LockTimeHeightEdit(self)
 | |
|         self.locktime_date_e = LockTimeDateEdit(self,time_edit = self)
 | |
|         #self.editors = [self.locktime_raw_e, self.locktime_height_e, self.locktime_date_e]
 | |
|         self.editors = [self.locktime_raw_e, self.locktime_date_e]
 | |
| 
 | |
|         self.combo = QComboBox()
 | |
|         #options = [_("Raw"), _("Block height"), _("Date")]
 | |
|         options = [_("Raw"),_("Date")]
 | |
|         self.option_index_to_editor_map = {
 | |
|             0: self.locktime_raw_e,
 | |
|             #1: self.locktime_height_e,
 | |
|             1: self.locktime_date_e,
 | |
|             #2: self.locktime_date_e,
 | |
|         }
 | |
|         self.combo.addItems(options)
 | |
| 
 | |
| 
 | |
|         self.editor = self.option_index_to_editor_map[default_index]
 | |
|         self.combo.currentIndexChanged.connect(self.on_current_index_changed)
 | |
|         self.combo.setCurrentIndex(default_index)
 | |
|         self.on_current_index_changed(default_index)
 | |
| 
 | |
|         hbox.addWidget(self.combo)
 | |
|         for w in self.editors:
 | |
|             hbox.addWidget(w)
 | |
|         hbox.addStretch(1)
 | |
| 
 | |
|         #self.locktime_height_e.textEdited.connect(self.valueEdited.emit)
 | |
|         self.locktime_raw_e.editingFinished.connect(self.valueEdited.emit)
 | |
|         self.locktime_date_e.dateTimeChanged.connect(self.valueEdited.emit)
 | |
|         self.combo.currentIndexChanged.connect(self.valueEdited.emit)
 | |
| 
 | |
|     def on_current_index_changed(self,i):
 | |
|         for w in self.editors:
 | |
|             w.setVisible(False)
 | |
|             w.setEnabled(False)
 | |
|         prev_locktime = self.editor.get_locktime()
 | |
|         self.editor = self.option_index_to_editor_map[i]
 | |
|         if self.editor.is_acceptable_locktime(prev_locktime):
 | |
|             self.editor.set_locktime(prev_locktime,force=True)
 | |
|         self.editor.setVisible(True)
 | |
|         self.editor.setEnabled(True)
 | |
| 
 | |
|     def get_locktime(self) -> Optional[str]:
 | |
|         return self.editor.get_locktime()
 | |
| 
 | |
|     def set_index(self,index):
 | |
|         self.combo.setCurrentIndex(index)
 | |
|         self.on_current_index_changed(index)
 | |
| 
 | |
|     def set_locktime(self, x: Any,force=True) -> None:
 | |
|         self.editor.set_locktime(x,force)
 | |
| 
 | |
| 
 | |
| 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,force=True) -> 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 as e:
 | |
|             return False
 | |
|         return cls.min_allowed_value <= x <= cls.max_allowed_value
 | |
| 
 | |
| 
 | |
| class LockTimeRawEdit(QLineEdit, _LockTimeEditor):
 | |
| 
 | |
|     def __init__(self, parent=None,time_edit=None):
 | |
|         QLineEdit.__init__(self, parent)
 | |
|         self.setFixedWidth(14 * char_width_in_lineedit())
 | |
|         self.textChanged.connect(self.numbify)
 | |
|         self.isdays = False
 | |
|         self.isyears = False
 | |
|         self.isblocks = False
 | |
|         self.time_edit=time_edit
 | |
| 
 | |
|     def replace_str(self,text):
 | |
|         return str(text).replace('d','').replace('y','').replace('b','')
 | |
| 
 | |
|     def checkbdy(self,s,pos,appendix):
 | |
|         try:
 | |
|             charpos = pos-1
 | |
|             charpos = max(0,charpos)
 | |
|             charpos = min(len(s)-1,charpos)
 | |
|             if appendix ==  s[charpos]:
 | |
|                 s=self.replace_str(s)+appendix
 | |
|                 pos = charpos
 | |
|         except Exception as e:
 | |
|             pass
 | |
|         return pos, s
 | |
| 
 | |
|     def numbify(self):
 | |
|         text = self.text().strip()
 | |
|         #chars = '0123456789bdy' removed the option to choose locktime by block
 | |
|         chars = '0123456789dy'
 | |
|         pos = posx = 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.isdays = False
 | |
|         self.isyears = False
 | |
|         self.isblocks = False
 | |
| 
 | |
|         pos,s = self.checkbdy(s,pos,'d')
 | |
|         pos,s = self.checkbdy(s,pos,'y')
 | |
|         pos,s = self.checkbdy(s,pos,'b')
 | |
| 
 | |
|         if 'd' in s: self.isdays = True 
 | |
|         if 'y' in s: self.isyears = True
 | |
|         if 'b' in s: self.isblocks = True
 | |
| 
 | |
| 
 | |
|         if self.isdays: s= self.replace_str(s) + 'd'
 | |
|         if self.isyears: s = self.replace_str(s) + 'y'
 | |
|         if self.isblocks: s= self.replace_str(s) + 'b'
 | |
| 
 | |
|         self.set_locktime(s,force=False)
 | |
|         # 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[str]:
 | |
|         try:
 | |
|             return str(self.text())
 | |
|         except Exception as e:
 | |
|             return None
 | |
| 
 | |
|     def set_locktime(self, x: Any,force=True) -> None:
 | |
|         out = str(x)
 | |
|         if 'd' in out:
 | |
|             out = self.replace_str(x)+'d'
 | |
|         elif 'y' in out:
 | |
|             out = self.replace_str(x)+'y'
 | |
|         elif 'b' in out:
 | |
|             out = self.replace_str(x)+'b'
 | |
|         else:
 | |
|             try:
 | |
|                 out = int(x)
 | |
|             except Exception as e:
 | |
|                 self.setText('')
 | |
|                 return
 | |
|             out = max(out, self.min_allowed_value)
 | |
|             out = min(out, self.max_allowed_value)
 | |
|         self.setText(str(out))
 | |
|         #try:
 | |
|         #    if self.time_edit and int(out)>self.time_edit.locktime_threshold and not force:
 | |
|         #        self.time_edit.set_index(1)
 | |
|         #except:
 | |
|         #    pass
 | |
| 
 | |
| class LockTimeHeightEdit(LockTimeRawEdit):
 | |
|     max_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX
 | |
| 
 | |
|     def __init__(self, parent=None,time_edit=None):
 | |
|         LockTimeRawEdit.__init__(self, parent)
 | |
|         self.setFixedWidth(20 * char_width_in_lineedit())
 | |
|         self.time_edit = time_edit
 | |
| 
 | |
|     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,time_edit=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())
 | |
|         self.time_edit = time_edit
 | |
| 
 | |
|     def get_locktime(self) -> Optional[int]:
 | |
|         dt = self.dateTime().toPyDateTime()
 | |
|         locktime = int(time.mktime(dt.timetuple()))
 | |
|         return locktime
 | |
| 
 | |
|     def set_locktime(self, x: Any,force = False) -> 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)
 | 
