""" bal.gui.qt.widgets ================== Reusable, self-contained Qt widgets used to build the BAL tabs and dialogs. These are "leaf" widgets: they receive the :class:`BalWindow` controller (and any data they need) as constructor arguments at runtime, so this module does not import ``window``/``dialogs`` and therefore introduces no import cycles. Contents: * ClickableLabel, BalLineEdit, BalTextEdit, BalCheckBox - thin Qt wrappers * BalTxFeesWidget - fee-rate editor * _LockTimeEditor + BalTimeEditWidget + raw/date editors - locktime editing * ThresholdTimeWidget / LockTimeWidget - threshold & locktime * WillSettingsWidget - the settings panel * PercAmountEdit - amount-or-percentage editor * WillWidget - single will-tx box """ from .common import * from .common import _, _logger, Will from .calendar import BalCalendar class ClickableLabel(QLabel): doubleClicked = pyqtSignal() def mouseDoubleClickEvent(self, event): self.doubleClicked.emit() super().mouseDoubleClickEvent(event) class BalTxFeesWidget(QWidget): valueChanged = pyqtSignal() current_value = None def __init__(self, bal_window, parent, value=None): super().__init__(parent) self.bal_window = bal_window layout = QHBoxLayout(self) self.txfee_widget = QSpinBox(self) self.txfee_widget.setMinimum(1) self.txfee_widget.setMaximum(10000) value = ( value if value else self.bal_window.bal_plugin.WILL_SETTINGS.get()["baltx_fees"] ) self.set_value(value) self.default_value = self.bal_window.bal_plugin.default_will_settings()[ "baltx_fees" ] self.txfee_widget.valueChanged.connect(self.on_heir_tx_fees) #label = ClickableLabel("$") #label.doubleClicked.connect(self.doubleclick) #layout.addWidget(label) button = HelpButton(_("mining fees expressed in sats/vbyte to be used in the Bitcoin transaction.\nHigher value ensure your transaction will be confirmed")) button.setText("丰") button.setStyleSheet("font-size: 16px;") layout.addWidget(button) layout.addWidget(self.txfee_widget) # Expose the leading icon (prefix) and the editable field so the parent # WillSettingsWidget can align them on a grid (see its vertical layout). self.prefix_widget = button self.field_widget = self.txfee_widget def doubleclick(self, event=None): pass def set_read_only(self, read_only=True): # Show the fee but make it non-editable (no spin arrows, no keyboard), # so it can only be changed from the "Build your will" wizard. self.txfee_widget.setReadOnly(read_only) self.txfee_widget.setButtonSymbols( QAbstractSpinBox.ButtonSymbols.NoButtons if read_only else QAbstractSpinBox.ButtonSymbols.UpDownArrows ) # Light-grey background when locked, so the read-only state is visible # (same look as the date fields); empty stylesheet restores the # editable appearance used inside the wizard. self.txfee_widget.setStyleSheet( "QSpinBox{background-color:#f0f0f0;}" if read_only else "" ) def get_value(self): return self.txfee_widget.value() def set_value(self, value, emit=True): value = int(value) if value is not None else 20 if getattr(self, "_updating", False): return self._updating = True try: self.current_value = value spin = self.txfee_widget spin.blockSignals(True) spin.setValue(value) spin.blockSignals(False) finally: self._updating = False if emit: spin.valueChanged.emit(value) def on_heir_tx_fees(self, value=None, update_all=True): if value != self.current_value: try: self.set_value(value) if update_all: self.bal_window.update_setting_widgets( self.get_value(), "baltx_fees", True ) except Exception as e: _logger.error(f"error while trying to update txfees{e}") log_error(e) else: pass class _LockTimeEditor: min_allowed_value = NLOCKTIME_MIN max_allowed_value = NLOCKTIME_MAX alarm = None def get_value(self) -> Optional[int]: raise NotImplementedError() def set_value(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 @staticmethod 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 BalTimeEditWidget(QWidget, _LockTimeEditor): valueEdited = pyqtSignal() _setting_locktime = False current_value = None current_index = None default_value = None help_text = ( "if you choose Raw, you can insert various options based on suffix:\n" + " - d: number of days after current day(ex: 1d means tomorrow)\n" + " - y: number of years after currrent day(ex: 1y means one year from today)\n" ) label_text = None tooltip_text = None base_field = None def __init__(self, bal_window, parent, default_locktime=None): super().__init__(parent) self.bal_window = bal_window hbox = QHBoxLayout() self.setLayout(hbox) hbox.setContentsMargins(0, 0, 0, 0) hbox.setSpacing(0) self.setMinimumWidth(40 * char_width_in_lineedit()) self.locktime_raw_e = TimeRawEditWidget(self, time_edit=self) self.locktime_date_e = LockTimeDateEdit(self, time_edit=self) self.editors = [self.locktime_raw_e, self.locktime_date_e] self.combo = QComboBox() options = [_("Raw"), _("Date")] self.option_index_to_editor_map = { 0: self.locktime_raw_e, 1: self.locktime_date_e, } self.combo.addItems(options) default_index = 0 if not default_locktime: default_locktime = self.bal_window.bal_plugin.WILL_SETTINGS.get()[self.base_field] try: int(default_locktime) default_index = 1 except Exception: default_index = 0 #hbox.addWidget(QLabel(self.label_text)) help_button=HelpButton(self.help_text) help_button.setText(self.label_text) # Show a short label (e.g. "Delivery time" / "Check Alive") when the # user hovers the icon, so the emoji button is self-explanatory. if self.tooltip_text: help_button.setToolTip(_(self.tooltip_text)) #help_button.setStyleSheet("font-size: 155555); hbox.addWidget(help_button) # Expose the leading icon (prefix) so the parent WillSettingsWidget can # align all rows on a common left edge (see its vertical layout). self.prefix_widget = help_button self.combo.currentIndexChanged.connect(self.on_current_index_changed) for w in self.editors: w.setVisible(False) w.setEnabled(False) self.editor = self.option_index_to_editor_map[default_index] self.editor.setVisible(True) self.editor.setEnabled(True) self.set_index(default_index) #self.on_current_index_changed(default_index) self.set_value(default_locktime) self.current_value=default_locktime hbox.addWidget(self.combo) for w in self.editors: hbox.addWidget(w) hbox.addStretch(1) # spssscer_widget = QWidget() # spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) # hbox.addWidget(spacer_widget) self.valueEdited.connect(lambda: self.update_will_settings(True)) 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 update_will_settings( self, update_all=False, update_will_dialog=False, update_heirs_dialog=False, ): self.bal_window.update_setting_widgets( self.get_value(), self.base_field, update_all, update_will_dialog, update_heirs_dialog, ) def on_current_index_changed(self, i): self.current_index = i for w in self.editors: w.setVisible(False) w.setEnabled(False) # prev_locktime = self.editor.get_value() self.editor = self.option_index_to_editor_map[i] if i==0: self.editor.set_value(self.bal_window.bal_plugin.default_will_settings_relative()[self.base_field]) else: self.editor.set_value(self.bal_window.bal_plugin.default_will_settings_absolute()[self.base_field]) self.valueEdited.emit() # if self.editor.is_acceptable_locktime(prev_locktime): # self.editor.set_value(prev_locktime, force=False) self.editor.setVisible(True) self.editor.setEnabled(True) self.bal_window.update_combo_setting_widgets(i, self.base_field,True) def get_value(self) -> Optional[str]: val = self.editor.get_value() #return self.current_value return val def set_index(self, index): if self.current_index != index: self.combo.setCurrentIndex(index) #self.on_current_index_changed(index, force) def set_value( self, x: Any, force=None, update_all=False, update_will_dialog=False, update_heirs_dialog=False, ) -> None: if not x: if self.current_index == 0: x = self.bal_window.bal_plugin.default_will_settings_relative()[self.base_field] elif self.current_index == 1: x = self.bal_window.bal_plugin.default_will_settings_absolute()[self.base_field] if x != self.get_value(): self.editor.set_value(x) self.current_value = x self.bal_window.update_setting_widgets(x, self.base_field) def set_read_only(self, read_only=True): """Show the value but make it non-editable. Used everywhere except the "Build your will" wizard, where the date is the only place the user is allowed to change it. The Raw/Date combo is disabled and both editors become read-only with no spin buttons. """ self.combo.setEnabled(not read_only) for w in self.editors: w.set_read_only(read_only) class TimeRawEditWidget(QWidget): editingFinished = pyqtSignal() def is_acceptable_locktime(self, value): return True def __init__(self, parent, time_edit=None): super().__init__(parent) self.editor = LockTimeRawEdit(parent, time_edit) self.label = QLabel("") self.label.setFixedWidth(10 * char_width_in_lineedit()) self.layout = QHBoxLayout(self) self.layout.addWidget(self.editor) self.layout.addWidget(self.label) self.editor.editingFinished.connect(self.editingFinished.emit) self.get_value = self.editor.get_value self.set_value = self.editor.set_value def set_read_only(self, read_only=True): self.editor.setReadOnly(read_only) # Match the Date editor: grey background when locked so the read-only # state is visible; empty stylesheet restores the editable look. self.editor.setStyleSheet( "QLineEdit{background-color:#f0f0f0;}" if read_only else "" ) class LockTimeRawEdit(QLineEdit, _LockTimeEditor): def __init__(self, parent=None, time_edit=None): QLineEdit.__init__(self, parent) self.setFixedWidth(12 * char_width_in_lineedit()) self.textChanged.connect(self.numbify) self.isdays = False self.isyears = False self.time_edit = time_edit @staticmethod def replace_str(text): return str(text).replace("d", "").replace("y", "") 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: pass return pos, s def numbify(self): text = self.text().strip() chars = "0123456789dy" 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.isdays = False self.isyears = False self.isblocks = False pos, s = self.checkbdy(s, pos, "d") pos, s = self.checkbdy(s, pos, "y") if "d" in s: self.isdays = True if "y" in s: self.isyears = True if self.isdays: s = self.replace_str(s) + "d" if self.isyears: s = self.replace_str(s) + "y" self.blockSignals(True) self.setText(s) self.blockSignals(False) self.current_value = s self.setModified(self.hasFocus()) self.setCursorPosition(pos) def get_value(self) -> Optional[str]: try: return str(self.text()) except Exception: return None def set_value(self, x: Any, force=True) -> None: if x != self.get_value(): self.blockSignals(True) self.setText(str(x)) self.blockSignals(False) self.numbify() class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): min_allowed_value = 0 max_allowed_value = _LockTimeEditor.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 set_read_only(self, read_only=True): # Read-only display: keyboard editing disabled and the up/down spin # arrows removed, so the date can only be changed from the wizard. self.setReadOnly(read_only) self.setButtonSymbols( QAbstractSpinBox.ButtonSymbols.NoButtons if read_only else QAbstractSpinBox.ButtonSymbols.UpDownArrows ) # A read-only QDateTimeEdit keeps a white background by default, which # does not visually signal that it is locked. Paint it light grey (like # the disabled combo/fee fields next to it) so the user sees at a glance # that the date is not editable here; an empty stylesheet restores the # default look when the field is made editable again (in the wizard). self.setStyleSheet( "QDateTimeEdit{background-color:#f0f0f0;}" if read_only else "" ) def get_value(self) -> Optional[int]: #dt = self.dateTime().toPyDateTime() #locktime = int(time.mktime(dt.timetuple())) #p# #dt = dt_edit.dateTime() ## QDateTimets = dt.toSecsSinceEpoch() dt = self.dateTime() _ts = dt.toSecsSinceEpoch() return _ts def set_value(self, x: Any, force=False) -> None: if not self.is_acceptable_locktime(x): self.setDateTime(QDateTime.currentDateTime()) return try: x = int(x) except Exception as e: x = QDateTime.currentDateTime().timestamp() finally: # Use the overflow-safe converter: on Windows datetime.fromtimestamp # raises OverflowError for timestamps past 2038 (e.g. NLOCKTIME_MAX). _dt = BalTimestamp._safe_fromtimestamp(x) #if self.alarm != dt: self.setDateTime(_dt) self.alarm = _dt class ThresholdTimeWidget(BalTimeEditWidget): # rich_text=True is used by the HelpButton, so HTML tags (,
) render. help_text = ( "CHECK ALIVE

" "Check to ask for invalidation.

" "When less then this time is missing, ask to invalidate.
" "If you fail to invalidate during this time, your transactions will be delivered to your heirs.

" "if you choose Raw, you can insert various options based on suffix:
" " - d: number of days after current day(ex: 1d means tomorrow)
" " - y: number of years after currrent day(ex: 1y means one year from today)
" ) label_text = "🚨" #label_text = "Check Alive" tooltip_text = "Check Alive" base_field = "threshold" def __init__(self, bal_window, parent, init_value=None): if init_value is None: init_value = bal_window.bal_plugin.WILL_SETTINGS.get()["threshold"] super().__init__(bal_window, parent, init_value) self.default_value = self.bal_window.bal_plugin.default_will_settings()[ "threshold" ] class LockTimeWidget(BalTimeEditWidget): # rich_text=True is used by the HelpButton, so HTML tags (,
) render. help_text = ( "DELIVERY TIME

" "Set Locktime for transactions.
" "Any time is needed transaction will be anticipated by 1day

" "if you choose Raw, you can insert various options based on suffix:
" " - d: number of days after current day(ex: 1d means tomorrow)
" " - y: number of years after currrent day(ex: 1y means one year from today)
" ) label_text = "🚛" #label_text = "Locktime" tooltip_text = "Delivery time" base_field = "locktime" def __init__(self, bal_window, parent, init_value=None): if init_value is None: init_value = bal_window.bal_plugin.WILL_SETTINGS.get()["locktime"] super().__init__(bal_window, parent, init_value) self.default_value = self.bal_window.bal_plugin.default_will_settings()[ "locktime" ] class WillSettingsWidget(QWidget): def __init__(self, bal_window: "BalWindow", parent, layout_type="h", read_only=True): self.widgets = {} QWidget.__init__(self, parent) self.bal_window = bal_window # When read_only=True (toolbars, Heirs tab) the delivery time, check # alive and fee fields are display-only; they can only be edited from # the "Build your will" wizard, which passes read_only=False. self.read_only = read_only box = QHBoxLayout(self) if layout_type == "h" else QVBoxLayout(self) self.calendar_button = QPushButton() self.calendar_button.setIcon( read_QIcon_from_bytes( self.bal_window.bal_plugin.read_file("icons/calendar.png") ) ) # Tooltip so the icon is self-explanatory when hovered. self.calendar_button.setToolTip(_("Calendar")) self.calendar_button.clicked.connect(self.open_or_save_calendar) self.widgets["locktime"] = LockTimeWidget(bal_window, self) self.widgets["threshold"] = ThresholdTimeWidget(bal_window, self) self.widgets["locktime"].valueEdited.connect(self.on_locktime_change) self.widgets["threshold"].valueEdited.connect(self.on_locktime_change) # self.widgets['baltx_fees'].valueChange.connect(self.bal_window.update_setting_widgets) self.on_locktime_change() self.widgets["baltx_fees"] = BalTxFeesWidget(bal_window, self) if not hasattr(bal_window, "txfee_widgets"): bal_window.txfee_widgets = [] w = self.widgets["baltx_fees"] if w not in bal_window.txfee_widgets: bal_window.txfee_widgets.append(w) if layout_type == "h": box.addWidget(self.widgets["locktime"]) box.addWidget(self.widgets["threshold"]) box.addWidget(self.calendar_button) box.addWidget(self.widgets["baltx_fees"]) else: # Vertical layout (the "Build your will" wizard): make every row the # same width and left aligned so they all fit in one tidy block, # instead of letting the calendar button and the fee field stretch to # the dialog's right edge (which made them far wider than the date # rows above). # # IMPORTANT: the leading icons keep their ORIGINAL size. The icons # are HelpButtons, which already pin themselves to a fixed width # (2.2 * char_width_in_lineedit()); we must NOT widen them, otherwise # they look oversized compared with the original toolbar layout. We # only need to (1) align the calendar row's left edge with the icons' # original width and (2) cap every row to the date-row width. locktime_w = self.widgets["locktime"] threshold_w = self.widgets["threshold"] fees_w = self.widgets["baltx_fees"] # Original icon width (HelpButton's own fixed width); used only to # offset the calendar button so its field starts under the others. icon_w = locktime_w.prefix_widget.sizeHint().width() # Common row width = natural width of the date rows (the reference). row_w = max( locktime_w.sizeHint().width(), threshold_w.sizeHint().width(), ) for w in (locktime_w, threshold_w, fees_w): w.setFixedWidth(row_w) # The calendar row has no prefix icon: wrap it so it starts with an # empty spacer of the icon width (calendar field aligned with the # date/fee fields) and cap it to the same total width as the rows # above, so it no longer stretches to the dialog's right edge. calendar_row = QWidget(self) calendar_box = QHBoxLayout(calendar_row) calendar_box.setContentsMargins(0, 0, 0, 0) calendar_box.setSpacing(0) calendar_spacer = QWidget() calendar_spacer.setFixedWidth(icon_w) calendar_box.addWidget(calendar_spacer) calendar_box.addWidget(self.calendar_button) calendar_row.setFixedWidth(row_w) box.addWidget(locktime_w, alignment=Qt.AlignmentFlag.AlignLeft) box.addWidget(threshold_w, alignment=Qt.AlignmentFlag.AlignLeft) box.addWidget(calendar_row, alignment=Qt.AlignmentFlag.AlignLeft) box.addWidget(fees_w, alignment=Qt.AlignmentFlag.AlignLeft) if self.read_only: self.widgets["locktime"].set_read_only(True) self.widgets["threshold"].set_read_only(True) self.widgets["baltx_fees"].set_read_only(True) def create_alarms(self, alarm_start, alarm_end): """Delegate to the pure :meth:`BalCalendar.build_alarms` helper. The reminder count comes from the plugin's ``ALARM_NUMBER`` setting; the actual VALARM generation lives in ``BalCalendar`` so it can be unit tested without a Qt widget. """ num_alarms = self.bal_window.bal_plugin.ALARM_NUMBER.get() return BalCalendar.build_alarms(num_alarms, alarm_start, alarm_end) def open_or_save_calendar(self): now = BalCalendar.format_time(datetime.now()) locktime = self.widgets["locktime"].alarm threshold = self.widgets["threshold"].alarm # Use the larger of current time and threshold as alarm start alarm_start_dt = max(datetime.now(), threshold) alarm_end_dt = locktime alarm_end = BalCalendar.format_time(alarm_end_dt) heirs_details = "\r\n".join(f" {heir} - {self.bal_window.heirs[heir][0]}, {self.bal_window.heirs[heir][1]}" for heir in self.bal_window.heirs) event_description = BalCalendar.ical_escape( f"{self.bal_window.bal_plugin.EVENT_DESCRIPTION.get()}".replace("$wallet_name",str(self.bal_window.wallet)).replace("$heirs_complete",heirs_details) ) uid = f"bal-{str(self.bal_window.wallet)}" summary = BalCalendar.ical_escape( f"{self.bal_window.bal_plugin.EVENT_SUMMARY.get()}".replace("$wallet_name",str(self.bal_window.wallet)) ) # The event date/time is the lower value between the will-settings # locktime (here represented by ``alarm_end_dt``) and the valid will # transaction with the lowest locktime. min_tx_locktime = Will.get_min_locktime( self.bal_window.willitems, None ) event_ts = BalCalendar.compute_event_timestamp( alarm_end_dt.timestamp(), min_tx_locktime ) event_dt = datetime.fromtimestamp(event_ts) event_time = BalCalendar.format_time(event_dt) lines = [ "BEGIN:VCALENDAR", "VERSION:2.0", f"PRODID:-//Bitcoin After Life//Electrum Plugin/{BalPlugin.__version__}", "BEGIN:VEVENT", f"UID:{uid}", f"DTSTAMP:{now}", f"DTSTART:{event_time}", f"DTEND:{event_time}", f"SUMMARY:{summary}", f"DESCRIPTION:{event_description}", ] lines.extend(self.create_alarms(alarm_start_dt, alarm_end_dt)) lines.extend([ "END:VEVENT", "END:VCALENDAR", ]) lines = [s.rstrip("\r\n") for s in lines] ics_content = "\r\n".join(lines) + "\r\n" self.temp_path = BalCalendar.write_temp_ics(ics_content) opened = BalCalendar.open_with_default_app( self.bal_window.bal_plugin.CALENDAR_APP.get(), self.temp_path ) if opened: _logger.info(f"File opened with default app: {self.temp_path}") else: export_meta_gui( self.bal_window.window, f"will_event.ics",self.save_to_cwd ) def save_to_cwd(self, filename="event.ics"): """Copy the temporary .ics file to ``filename`` in the current dir. Overwrites the destination file if it already exists. Returns the absolute path of the written file. """ target = os.path.abspath(filename) _logger.debug(f"save_to_cwd {self.temp_path},{filename}") with open(self.temp_path, "rb") as src, open(target, "wb") as dst: dst.write(src.read()) return target def on_locktime_change(self): locktime = self.widgets["locktime"].get_value() threshold = self.widgets["threshold"].get_value() locktime = BalTimestamp(locktime) threshold = BalTimestamp(threshold) min_locktime = min( Will.get_min_locktime(self.bal_window.willitems, NLOCKTIME_MAX), locktime.to_timestamp(), ) td = threshold.to_date(min_locktime, True) self.widgets["threshold"].alarm=td self.bal_window.will_settings["real_threshold"]=td.timestamp() try: self.widgets["threshold"].editor.label.setText(td.strftime("%Y-%m-%d")) except Exception as _e: pass td = locktime.to_date() alarm = BalTimestamp(min_locktime).to_date() self.widgets["locktime"].alarm=alarm self.bal_window.will_settings["real_locktime"]=td.timestamp() try: self.widgets["locktime"].editor.label.setText(td.strftime("%Y-%m-%d")) except Exception as _e: pass class PercAmountEdit(BTCAmountEdit): def __init__(self, decimal_point, is_int=False, parent=None, *, max_amount=None): super().__init__(decimal_point, is_int, parent, max_amount=max_amount) def numbify(self): text = self.text().strip() if text == "!": self.shortcut.emit() return pos = self.cursorPosition() chars = "0123456789%" chars += DECIMAL_POINT s = "".join([i for i in text if i in chars]) if "%" in s: self.is_perc = True s = s.replace("%", "") else: self.is_perc = False if DECIMAL_POINT in s: p = s.find(DECIMAL_POINT) s = s.replace(DECIMAL_POINT, "") s = s[:p] + DECIMAL_POINT + s[p : p + 8] if self.is_perc: s += "%" self.setText(s) self.setModified(self.hasFocus()) self.setCursorPosition(pos) def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]: try: text = text.replace(DECIMAL_POINT, ".") text = text.replace("%", "") return (Decimal)(text) except Exception: return None def _get_text_from_amount(self, amount): out = super()._get_text_from_amount(amount) if self.is_perc: out += "%" return out def paintEvent(self, event): QLineEdit.paintEvent(self, event) if self.base_unit: panel = QStyleOptionFrame() self.initStyleOption(panel) textRect = self.style().subElementRect( QStyle.SubElement.SE_LineEditContents, panel, self ) textRect.adjust(2, 0, -10, 0) painter = QPainter(self) painter.setPen(ColorScheme.GRAY.as_color()) if len(self.text()) == 0: painter.drawText( textRect, int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter), self.base_unit() + " or perc value", ) class BalLineEdit(QLineEdit): def __init__(self,variable): QLineEdit.__init__(self) self.setText(variable.get()) def on_edit(): variable.set(self.text()) self.editingFinished.connect(on_edit) class BalTextEdit(QTextEdit): def __init__(self,variable): QTextEdit.__init__(self) self.setPlainText(variable.get()) def on_edit(): variable.set(self.toPlainText()) self.textChanged.connect(on_edit) class BalCheckBox(QCheckBox): def __init__(self, variable, on_click=None): QCheckBox.__init__(self) self.setChecked(variable.get()) self.on_click = on_click def on_check(v): variable.set(v == 2) #variable.get() if self.on_click: self.on_click() self.stateChanged.connect(on_check) class WillWidget(QWidget): def __init__(self, father=None, parent=None): super().__init__() vlayout = QVBoxLayout() self.setLayout(vlayout) self.will = parent.bal_window.willitems self._bal_parent = parent for w in self.will: if ( self.will[w].get_status("REPLACED") and self._bal_parent.bal_window.bal_plugin._hide_replaced ): continue if ( self.will[w].get_status("INVALIDATED") and self._bal_parent.bal_window.bal_plugin._hide_invalidated ): continue f = self.will[w].father if father == f: qwidget = QWidget() # childWidget = QWidget() hlayout = QHBoxLayout(qwidget) qwidget.setLayout(hlayout) vlayout.addWidget(qwidget) detailw = QWidget() detaillayout = QVBoxLayout() detailw.setLayout(detaillayout) willpushbutton = QPushButton(w) willpushbutton.clicked.connect( partial(self._bal_parent.bal_window.show_transaction, txid=w) ) detaillayout.addWidget(willpushbutton) locktime = str(BalTimestamp(self.will[w].tx.locktime)) creation = str(BalTimestamp(self.will[w].time)) def qlabel(title, value): label = "" + _(str(title)) + f":\t{str(value)}" return QLabel(label) detaillayout.addWidget(qlabel("Locktime", locktime)) detaillayout.addWidget(qlabel("Creation Time", creation)) try: total_fees = ( self.will[w].tx.input_value() - self.will[w].tx.output_value() ) except Exception: total_fees = -1 decoded_fees = total_fees fee_per_byte = round(total_fees / self.will[w].tx.estimated_size(), 3) fees_str = str(decoded_fees) + " (" + str(fee_per_byte) + " sats/vbyte)" detaillayout.addWidget(qlabel("Transaction fees:", fees_str)) detaillayout.addWidget(qlabel("Status:", self.will[w].status)) detaillayout.addWidget(QLabel("")) detaillayout.addWidget(QLabel("Heirs:")) for heir in self.will[w].heirs: if 'w!ll3x3c"' not in heir: decoded_amount = Util.decode_amount( self.will[w].heirs[heir][3], self._bal_parent.decimal_point ) detaillayout.addWidget( qlabel( heir, f"{decoded_amount} {self._bal_parent.base_unit_name}" ) ) if self.will[w].we: detaillayout.addWidget(QLabel("")) detaillayout.addWidget(QLabel(_("Willexecutor: