electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

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)