'''
Bal
Bitcoin after life
'''
import copy
from datetime import datetime
from decimal import Decimal
import enum
from functools import partial
import json
import os
import random
import sys
import traceback
import time
from typing import (
        TYPE_CHECKING, 
        Callable, 
        Optional, 
        List, 
        Union, 
        Tuple, 
        Mapping,Any)
import urllib.parse
import urllib.request
try:
    QT_VERSION=sys._GUI_QT_VERSION
except:
    QT_VERSION=6
if QT_VERSION == 5:
    from PyQt5.QtGui import QStandardItemModel, QStandardItem
    from PyQt5.QtCore import Qt,QPersistentModelIndex, QModelIndex
    from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QMenu)
    from PyQt5.QtCore import (
            QPersistentModelIndex, 
            QModelIndex,
            Qt, 
            QRectF, 
            QRect, 
            QSizeF, 
            QUrl, 
            QPoint, 
            QSize,
            QDateTime,
            pyqtProperty, 
            pyqtSignal, 
            pyqtSlot, 
            QObject,
            QEventLoop,
            pyqtSignal)
    from PyQt5.QtGui import (
            QStandardItemModel, 
            QStandardItem, 
            QPalette, 
            QColor,
            QPixmap, 
            QImage, 
            QBitmap, 
            QPainter, 
            QFontDatabase, 
            QPen, 
            QFont,
            QColor, 
            QDesktopServices, 
            qRgba, 
            QPainterPath,
            QPalette,
            QPixmap, 
            QImage, 
            QBitmap, 
            QPainter, 
            QFontDatabase, 
            QPen, 
            QFont,
            QIcon,
            QColor, 
            QDesktopServices, 
            qRgba, 
            QPainterPath,
            QPalette)
    from PyQt5.QtWidgets import (
            QDialog, 
            QVBoxLayout, 
            QHBoxLayout, 
            QPushButton, 
            QLabel,
            QMenu,
            QDialog, 
            QVBoxLayout, 
            QHBoxLayout, 
            QPushButton, 
            QLabel,
            QWidget,
            QScrollArea,
            QAbstractItemView,
            QWidget,
            QDateTimeEdit,
            QLineEdit, 
            QStyle, 
            QStyleOptionFrame, 
            QSizePolicy,
            QCheckBox,
            QGridLayout, 
            QHBoxLayout, 
            QLabel,
            QLineEdit,
            QMenu,
            QMenuBar,
            QPushButton, 
            QScrollArea,
            QSpacerItem,
            QSizePolicy,
            QSpinBox,
            QVBoxLayout,
            QWidget,
            QStyle,
            QStyleOptionFrame,
            QComboBox,
            QHBoxLayout,
            ) 
else: #QT6
    from PyQt6.QtCore import (
            Qt,
            QDateTime,
            QPersistentModelIndex, 
            QModelIndex,
            pyqtProperty, 
            pyqtSignal, 
            pyqtSlot, 
            QObject,
            pyqtSignal, 
            QSize,
            Qt, 
            QRectF, 
            QRect, 
            QSizeF, 
            QUrl, 
            QPoint, 
            QSize)
    from PyQt6.QtGui import (
            QStandardItemModel, 
            QStandardItem, 
            QPalette, 
            QColor,
            QPixmap, 
            QImage, 
            QBitmap, 
            QPainter, 
            QFontDatabase, 
            QPen, 
            QFont,
            QColor, 
            QDesktopServices, 
            qRgba, 
            QPainterPath,
            QPalette, 
            QPainter,
            QPixmap, 
            QImage, 
            QBitmap, 
            QPainter, 
            QFontDatabase, 
            QPen, 
            QFont,
            QIcon,
            QColor, 
            QDesktopServices, 
            qRgba, 
            QPainterPath,
            QPalette)
    from PyQt6.QtWidgets import (
            QDialog, 
            QVBoxLayout, 
            QHBoxLayout, 
            QPushButton, 
            QLabel,
            QMenu,
            QAbstractItemView,
            QWidget,
            QDialog, 
            QVBoxLayout, 
            QHBoxLayout, 
            QPushButton, 
            QLabel,
            QWidget,
            QScrollArea,
            QDateTimeEdit,
            QLabel, 
            QVBoxLayout, 
            QCheckBox, 
            QWidget,
            QLabel, 
            QVBoxLayout, 
            QCheckBox,
            QLineEdit, 
            QStyle, 
            QStyleOptionFrame, 
            QSizePolicy,
            QGridLayout, 
            QVBoxLayout, 
            QHBoxLayout, 
            QLabel,
            QPushButton, 
            QLineEdit,
            QCheckBox,
            QSpinBox,
            QMenuBar,
            QMenu,
            QLineEdit,
            QScrollArea,
            QWidget,
            QSpacerItem,
            QComboBox,
            QSizePolicy)
from electrum import json_db
from electrum import constants
from electrum.bitcoin import (
        is_address, 
        NLOCKTIME_MIN, 
        NLOCKTIME_MAX, 
        NLOCKTIME_BLOCKHEIGHT_MAX)
from electrum.gui.qt.amountedit import (
        BTCAmountEdit, 
        char_width_in_lineedit, 
        ColorScheme)
if TYPE_CHECKING:
    from electrum.gui.qt.main_window import ElectrumWindow
from electrum.gui.qt.util import (
        Buttons,
        CancelButton,
        char_width_in_lineedit,
        CloseButton,
        ColorScheme,
        EnterButton, 
        export_meta_gui,
        HelpButton,
        import_meta_gui, 
        MessageBoxMixin,
        OkButton,
        read_QIcon, 
        TaskThread,
        WindowModalDialog,
        WWLabel, 
        read_QIcon_from_bytes,
        read_QPixmap_from_bytes,
        )
from electrum.gui.qt.main_window import StatusBarButton
from electrum.gui.qt.my_treeview import (
        MyTreeView, 
        MySortModel)
from electrum.gui.qt.transaction_dialog import TxDialog
from electrum.gui.qt.password_dialog import PasswordDialog
from electrum.gui.qt.qrtextedit import ScanQRTextEdit
from electrum.i18n import _
from electrum.logging import get_logger,Logger
from electrum.json_db import StoredDict
from electrum.network import (
        Network,
        TxBroadcastError, 
        BestEffortRequestFailed
        )
from electrum.plugin import (
        hook,
        run_hook)
from electrum.transaction import (
        SerializationError,
        Transaction,
        tx_from_any)
from electrum.util import (
        write_json_file,
        read_json_file,
        make_dir, 
        InvalidPassword,
        UserCancelled,
        resource_path,
        write_json_file,
        read_json_file,
        FileImportFailed,
        bfh, 
        read_json_file,
        write_json_file,
        decimal_point_to_base_unit_name,
        FileImportFailed,
        DECIMAL_POINT, 
        FEERATE_PRECISION, 
        quantize_feerate, 
        UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE,
        FileExportFailed)
from .bal import BalPlugin
from .bal_resources import (
        DEFAULT_ICON,
        icon_path)
from .heirs import Heirs
from .util import Util
from .will import (
        Will,
        WillItem,
        NoHeirsException,
        NoWillExecutorNotPresent,
        NotCompleteWillException,
        AmountException,
        HeirNotFoundException,
        HeirChangeException,
        WillexecutorChangeException,
        WillExecutorNotPresent,
        TxFeesChangedException,
        WillExpiredException)
from .willexecutors import Willexecutors
_logger = get_logger(__name__)
class Plugin(BalPlugin,Logger):
    def __init__(self, parent, config, name):
        Logger.__init__(self)
        self.logger.info("INIT BALPLUGIN")
        BalPlugin.__init__(self, parent, config, name)
        self.bal_windows={}
    @hook
    def init_qt(self,gui_object):
        self.logger.info("HOOK init qt")   
        try:
            self.gui_object=gui_object
            for window in gui_object.windows:
                wallet = window.wallet
                if wallet:
                    window.show_warning(_('Please restart Electrum to activate the BAL plugin'), title=_('Success'))
                    return
                w = BalWindow(self,window)
                self.bal_windows[window.winId]= w
                for child in window.children():
                    if isinstance(child,QMenuBar):
                        for menu_child in child.children():
                            if isinstance(menu_child,QMenu):
                                try:
                                    if menu_child.title()==_("&Tools"):
                                        w.init_menubar_tools(menu_child)
                                            
                                except Exception as e:
                                    raise e
                                    self.logger.error(("except:",menu_child.text()))
                                    
        except Exception as e:
            self.logger.error("Error loading plugini {}".format(e))
            raise e
    @hook
    def create_status_bar(self, sb):
        self.logger.info("HOOK create status bar")
        return
        b = StatusBarButton(read_QIcon_from_bytes(self.bal_plugin.read_file('bal32x32.png')), "Bal "+_("Bitcoin After Life"),
                            partial(self.setup_dialog, sb), sb.height())
        sb.addPermanentWidget(b)
    @hook
    def init_menubar(self,window):
        self.logger.info("HOOK init_menubar")
        w = self.get_window(window)
        w.init_menubar_tools(window.tools_menu)
    @hook
    def load_wallet(self,wallet, main_window):
        self.logger.info("HOOK load wallet")
        w = self.get_window(main_window)
        w.wallet = wallet
        w.init_will()
        w.willexecutors = Willexecutors.get_willexecutors(self, update=False, bal_window=w)
        w.disable_plugin = False
        w.ok=True
    @hook
    def close_wallet(self,wallet):
        for winid,win in self.bal_windows.items():
            if win.wallet == wallet:
                win.on_close()
    def get_window(self,window):
        w = self.bal_windows.get(window.winId,None)
        if w is None:
            w=BalWindow(self,window)
            self.bal_windows[window.winId]=w
        return w
      
    def requires_settings(self):
        return True
    
    def settings_widget(self, window):
        w=self.get_window(window.window)
        widget=QWidget()
        enterbutton=EnterButton(_('Settings'), partial(w.settings_dialog,window))
        widget.setLayout(Buttons(enterbutton,widget))
        return widget
    def password_dialog(self, msg=None, parent=None):
        parent = parent or self
        d = PasswordDialog(parent, msg)
        return d.run()
    def get_seed(self):
        password = None
        if self.wallet.has_keystore_encryption():
            password = self.password_dialog(parent=self.d.parent())
            if not password:
                raise UserCancelled()
        keystore = self.wallet.get_keystore()
        if not keystore or not keystore.has_seed():
            return
        self.extension = bool(keystore.get_passphrase(password))
        return keystore.get_seed(password)
    def settings_dialog(self,window,wallet):
        d = BalDialog(window,self,self.get_window_title("Settings"))
        d.setMinimumSize(100, 200)
        qicon=read_QPixmap_from_bytes(self.read_file("bal32x32.png"))
        lbl_logo = QLabel()
        lbl_logo.setPixmap(qicon)
        heir_ping_willexecutors = bal_checkbox(self.PING_WILLEXECUTORS)
        heir_ask_ping_willexecutors = bal_checkbox(self.ASK_PING_WILLEXECUTORS)
        heir_no_willexecutor = bal_checkbox(self.NO_WILLEXECUTOR)
        def on_multiverse_change():
            self.update_all()
        #heir_enable_multiverse = bal_checkbox(self.ENABLE_MULTIVERSE,on_multiverse_change)
        heir_hide_replaced = bal_checkbox(self.HIDE_REPLACED,on_multiverse_change)
        heir_hide_invalidated = bal_checkbox(self.HIDE_INVALIDATED,on_multiverse_change)
        heir_repush = QPushButton("Rebroadcast transactions")
        heir_repush.clicked.connect(partial(self.broadcast_transactions,True))
        grid=QGridLayout(d)
        add_widget(grid,"Hide Replaced",heir_hide_replaced, 1, "Hide replaced transactions from will detail and list")
        add_widget(grid,"Hide Invalidated",heir_hide_invalidated ,2,"Hide invalidated transactions from will detail and list")
        add_widget(grid,"Ping Willexecutors",heir_ping_willexecutors,3,"Ping willexecutors to get payment info before compiling will")
        add_widget(grid," - Ask before",heir_ask_ping_willexecutors,4,"Ask before to ping willexecutor")
        add_widget(grid,"Backup Transaction",heir_no_willexecutor,5,"Add transactions without willexecutor")
        #add_widget(grid,"Enable Multiverse(EXPERIMENTAL/BROKEN)",heir_enable_multiverse,6,"enable multiple locktimes, will import.... ")
        grid.addWidget(heir_repush,7,0)
        grid.addWidget(HelpButton("Broadcast all transactions to willexecutors including those already pushed"),7,2)
        if ret := bool(d.exec()):
            try:
                self.update_all()
                return ret
            except:
                pass
        return False
    def broadcast_transactions(self,force):
        for k,w in self.bal_windows.items():
            w.broadcast_transactions(force)
    def update_all(self):
        for k,w in self.bal_windows.items():
            w.update_all()
    def get_window_title(self,title):
        return _('BAL - ') + _(title) 
class shown_cv():
    _type= bool
    def __init__(self,value):
        self.value=value
    def get(self):
        return self.value
    def set(self,value):
        self.value=value
class BalWindow(Logger):
    def __init__(self,bal_plugin: 'BalPlugin',window: 'ElectrumWindow'):
        Logger.__init__(self)
        self.bal_plugin = bal_plugin
        self.window = window
        self.heirs = {}
        self.will = {}
        self.willitems = {}
        self.willexecutors = {}
        self.will_settings = None
        self.heirs_tab = self.create_heirs_tab()
        self.will_tab = self.create_will_tab()
        self.ok= False
        self.disable_plugin = True
        
        if self.window.wallet:
            self.wallet = self.window.wallet
            self.heirs_tab.wallet = self.wallet
            self.will_tab.wallet = self.wallet
    def init_menubar_tools(self,tools_menu):
        self.tools_menu=tools_menu
        def add_optional_tab(tabs, tab, icon, description):
            tab.tab_icon = icon
            tab.tab_description = description
            tab.tab_pos = len(tabs)
            if tab.is_shown_cv:
                tabs.addTab(tab, icon, description.replace("&", ""))
        def add_toggle_action(tab):
            is_shown = tab.is_shown_cv.get()
            tab.menu_action = self.window.view_menu.addAction(tab.tab_description, lambda: self.window.toggle_tab(tab))
            tab.menu_action.setCheckable(True)
            tab.menu_action.setChecked(is_shown)
        add_optional_tab(self.window.tabs, self.heirs_tab, read_QIcon_from_bytes(self.bal_plugin.read_file("heir.png")), _("&Heirs"))
        add_optional_tab(self.window.tabs, self.will_tab, read_QIcon_from_bytes(self.bal_plugin.read_file("will.png")), _("&Will"))
        tools_menu.addSeparator()
        self.tools_menu.willexecutors_action = tools_menu.addAction(_("&Will-Executors"), self.show_willexecutor_dialog)
        self.window.view_menu.addSeparator()
        add_toggle_action(self.heirs_tab)
        add_toggle_action(self.will_tab)
    def load_willitems(self):
        self.willitems={}
        for wid,w in self.will.items():
            self.willitems[wid]=WillItem(w,wallet=self.wallet)
        if self.willitems:
            self.will_list.will=self.willitems
            self.will_list.update_will(self.willitems)
            self.will_tab.update()
    def save_willitems(self):
        keys = list(self.will.keys())
        for k in keys:
            del self.will[k]
        for wid,w in self.willitems.items():
            self.will[wid]=w.to_dict()
    def init_will(self):
        self.logger.info("********************init_____will____________**********")
        if not self.willexecutors:
            self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin, update=False, bal_window=self) 
        if not self.heirs:
            self.heirs = Heirs._validate(Heirs(self.wallet.db))
        if not self.will:
            self.will=self.wallet.db.get_dict("will")
            if self.will:
                self.willitems = {}
                try:
                    self.load_willitems()
                except:
                    self.disable_plugin=True
                    self.show_warning(_('Please restart Electrum to activate the BAL plugin'), title=_('Success'))
                    self.close_wallet()
                    return
        if not self.will_settings:
            self.will_settings=self.wallet.db.get_dict("will_settings")
            self.logger.info("will_settings: {}".format(self.will_settings))
            if not self.will_settings:
                Util.copy(self.will_settings,self.bal_plugin.default_will_settings())
                self.logger.debug("not_will_settings {}".format(self.will_settings))
            self.bal_plugin.validate_will_settings(self.will_settings)
            self.heir_list.update_will_settings()
    
    def init_wizard(self):
        wizard_dialog = BalWizardDialog(self)
        wizard_dialog.exec()
    def show_willexecutor_dialog(self):
        self.willexecutor_dialog = WillExecutorDialog(self)
        self.willexecutor_dialog.show()
    def create_heirs_tab(self):
        self.heir_list = l = HeirList(self,self.window)
        tab = self.window.create_list_tab(l)
        tab.is_shown_cv = shown_cv(True)
        return tab
    def create_will_tab(self):
        self.will_list = l = PreviewList(self,self.window,None)
        tab = self.window.create_list_tab(l)
        tab.is_shown_cv = shown_cv(True)
        return tab
    def new_heir_dialog(self,heir_key=None):
        heir = self.heirs.get(heir_key)
        title = "New heir"
        if heir:
            title = f"Edit: {heir_key}"
        d = BalDialog(self.window, self.bal_plugin,self.bal_plugin.get_window_title(_(title)))
        vbox = QVBoxLayout(d)
        grid = QGridLayout()
        heir_name = QLineEdit()
        heir_name.setFixedWidth(32 * char_width_in_lineedit())
        if heir:
            heir_name.setText(str(heir_key))
        heir_address = QLineEdit()
        heir_address.setFixedWidth(32 * char_width_in_lineedit())
        if heir:
            heir_address.setText(str(heir[0]))
        heir_amount = PercAmountEdit(self.window.get_decimal_point)
        if heir:
            heir_amount.setText(str(Util.decode_amount(heir[1],self.bal_plugin.config.get_decimal_point())))
        heir_locktime = HeirsLockTimeEdit(self.window,0)
        if heir:
            heir_locktime.set_locktime(heir[2])
        heir_is_xpub = QCheckBox()
        new_heir_button=QPushButton(_("Add another heir"))
        self.add_another_heir=False
        def new_heir():
            self.add_another_heir=True
            d.accept()
        new_heir_button.clicked.connect(new_heir)
        new_heir_button.setDefault(True)
        grid.addWidget(QLabel(_("Name")), 1, 0)
        grid.addWidget(heir_name, 1, 1)
        grid.addWidget(HelpButton(_("Unique name or description about heir")),1,2)
        grid.addWidget(QLabel(_("Address")), 2, 0)
        grid.addWidget(heir_address, 2, 1)
        grid.addWidget(HelpButton(_("heir bitcoin address")),2,2)
        grid.addWidget(QLabel(_("Amount")),3,0)
        grid.addWidget(heir_amount,3,1)
        grid.addWidget(HelpButton(_("Fixed or Percentage amount if end with %")),3,2)
        locktime_label=QLabel(_("Locktime"))
        enable_multiverse=self.bal_plugin.ENABLE_MULTIVERSE.get()
        if enable_multiverse:
            grid.addWidget(locktime_label,4,0)
            grid.addWidget(heir_locktime,4,1)
            grid.addWidget(HelpButton(_("locktime")),4,2)
        vbox.addLayout(grid)
        buttons=[CancelButton(d), OkButton(d)]
        if not heir:
            buttons.append(new_heir_button)
        vbox.addLayout(Buttons(*buttons))
        while d.exec():
            #TODO SAVE HEIR
            heir = [
                    heir_name.text(),
                    heir_address.text(),
                    Util.encode_amount(heir_amount.text(),self.bal_plugin.config.get_decimal_point()),
                    str(heir_locktime.get_locktime()), 
                    ]
            try:
                self.set_heir(heir)
                if self.add_another_heir:
                    self.new_heir_dialog()
                break
            except Exception as e:
                self.show_error(str(e))
    #def export_inheritance_handler(self,path):
    #    txs = self.build_inheritance_transaction(ignore_duplicate=True, keep_original=False)
    #    with open(path,"w") as f:
    #        for tx in txs:
    #            tx['status']+="."+BalPlugin.STATUS_EXPORTED
    #            f.write(str(tx['tx']))
    #            f.write('\n')
 
    def set_heir(self,heir):
        heir=list(heir)
        if not self.bal_plugin.ENABLE_MULTIVERSE.get():
            heir[3]=self.will_settings['locktime']
        h=Heirs.validate_heir(heir[0],heir[1:])
        self.heirs[heir[0]]=h
        self.heir_list.update()
        return True
    def delete_heirs(self,heirs):
        for heir in heirs:
            del self.heirs[heir]
        self.heirs.save()
        self.heir_list.update()
        return True
    
    def import_heirs(self,):
        import_meta_gui(self.window, _('heirs'), self.heirs.import_file, self.heir_list.update)
    def export_heirs(self):
        Util.export_meta_gui(self.window, _('heirs'), self.heirs.export_file)
    def prepare_will(self, ignore_duplicate = False, keep_original = False):
        will = self.build_inheritance_transaction(ignore_duplicate = ignore_duplicate, keep_original=keep_original)
        return will
    
    def delete_not_valid(self,txid,s_utxo):
        raise NotImplementedError()
    def update_will(self,will):
        Will.update_will(self.willitems,will)
        self.willitems.update(will)
        Will.normalize_will(self.willitems,self.wallet)
    def build_will(self, ignore_duplicate = True, keep_original = True ):
        will = {}
        willtodelete=[]
        willtoappend={}
        try:
            self.init_class_variables()
            self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin, update=False, bal_window=self) 
            
            if not self.no_willexecutor:
                f=False
                for u,w in self.willexecutors.items():
                    if Willexecutors.is_selected(w):
                        f=True
                if not f:
                    raise NoWillExecutorNotPresent("No Will-Executor or backup transaction selected")
            txs = self.heirs.get_transactions(self.bal_plugin,self.window.wallet,self.will_settings['tx_fees'],None,self.date_to_check)
            self.logger.info(txs)
            creation_time = time.time()
            if txs:
                for txid in txs:
                    txtodelete=[]
                    _break = False
                    tx = {}
                    tx['tx'] = txs[txid]
                    tx['my_locktime'] = txs[txid].my_locktime
                    tx['heirsvalue'] = txs[txid].heirsvalue
                    tx['description'] = txs[txid].description
                    tx['willexecutor'] = copy.deepcopy(txs[txid].willexecutor)
                    tx['status'] = _("New")
                    tx['tx_fees'] = txs[txid].tx_fees
                    tx['time'] = creation_time
                    tx['heirs'] = copy.deepcopy(txs[txid].heirs)
                    tx['txchildren'] = []
                    will[txid]=WillItem(tx,_id=txid,wallet=self.wallet)
                self.update_will(will)
        except Exception as e:
            raise e
            pass
        return self.willitems 
    def check_will(self):
        return Will.is_will_valid(self.willitems, self.block_to_check, self.date_to_check, self.will_settings['tx_fees'],self.window.wallet.get_utxos(),heirs=self.heirs,willexecutors=self.willexecutors ,self_willexecutor=self.no_willexecutor, wallet = self.wallet, callback_not_valid_tx=self.delete_not_valid)
    def show_message(self,text):
        self.window.show_message(text)
    def show_warning(self,text,parent =None):
        self.window.show_warning(text, parent= None)
    def show_error(self,text):
        self.window.show_error(text)
    def show_critical(self,text):
        self.window.show_critical(text)
    def init_heirs_to_locktime(self,multiverse=False):
        for heir in self.heirs:
            h=self.heirs[heir]
            if not multiverse:
                self.heirs[heir]=[h[0],h[1],self.will_settings['locktime']]
    def init_class_variables(self):
            if not self.heirs:
                raise NoHeirsException()
                return
            try: 
                self.date_to_check = Util.parse_locktime_string(self.will_settings['threshold'])
                found = False
                self.locktime_blocks=self.bal_plugin.LOCKTIME_BLOCKS.get()
                self.current_block = Util.get_current_height(self.wallet.network)
                self.block_to_check = self.current_block + self.locktime_blocks
                self.no_willexecutor = self.bal_plugin.NO_WILLEXECUTOR.get()
                self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin,update=True,bal_window=self,task=False) 
                self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get())
            except Exception as e:
                self.logger.error(e)
                raise e
    def build_inheritance_transaction(self,ignore_duplicate = True, keep_original = True):
        try:
            if self.disable_plugin:
                self.logger.info("plugin is disabled")
                return
            if not self.heirs:
                self.logger.warning("not heirs {}".format(self.heirs))
                return
            self.init_class_variables()
            try:
                Will.check_amounts(self.heirs,self.willexecutors,self.window.wallet.get_utxos(),self.date_to_check,self.window.wallet.dust_threshold())
            except AmountException as e:
                self.show_warning(_(f"In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.\n{e}"))
            locktime = Util.parse_locktime_string(self.will_settings['locktime'])
            if locktime < self.date_to_check:
                self.show_error(_("locktime is lower than threshold"))
                return
            if not self.no_willexecutor:
                f=False
                for k,we in self.willexecutors.items():
                    if Willexecutors.is_selected(we):
                        f=True
                if not f:
                    self.show_error(_(" no backup transaction or willexecutor selected"))
                    return
            try:
                self.check_will()
            except WillExpiredException as e:
                self.invalidate_will()
                return
            except NoHeirsException:
                return
            except NotCompleteWillException as e:
                self.logger.info("{}:{}".format(type(e),e))
                message = False 
                if isinstance(e,HeirChangeException):
                    message ="Heirs changed:"
                elif isinstance(e,WillExecutorNotPresent):
                    message = "Will-Executor not present:"
                elif isinstance(e,WillexecutorChangeException):
                    message = "Will-Executor changed"
                elif isinstance(e,TxFeesChangedException):
                    message = "Txfees are changed"
                elif isinstance(e,HeirNotFoundException):
                    message = "Heir not found"
                if message:
                    self.show_message(f"{_(message)}:\n {e}\n{_('will have to be built')}")
                
                self.logger.info("build will")
                self.build_will(ignore_duplicate,keep_original)
                try:
                    self.check_will()
                    for wid,w in self.willitems.items():
                        self.wallet.set_label(wid,"BAL Transaction")
                except WillExpiredException as e:
                    self.invalidate_will()
                except NotCompleteWillException as e:
                    self.show_error("Error:{}\n {}".format(str(e),_("Please, check your heirs, locktime and threshold!")))
                self.window.history_list.update()
                self.window.utxo_list.update()
            self.update_all()
            return self.willitems
        except Exception as e:
            raise e
    def show_transaction_real(
        self,
        tx: Transaction,
        *,
        parent: 'ElectrumWindow',
        prompt_if_unsaved: bool = False,
        external_keypairs: Mapping[bytes, bytes] = None,
        payment_identifier: 'PaymentIdentifier' = None,
    ):
        try:
            d = TxDialog(
                tx,
                parent=parent,
                prompt_if_unsaved=prompt_if_unsaved,
                external_keypairs=external_keypairs,
                #payment_identifier=payment_identifier,
            )
            d.setWindowIcon(read_QIcon_from_bytes(self.bal_plugin.read_file("bal32x32.png")))
        except SerializationError as e:
            self.logger.error('unable to deserialize the transaction')
            parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
        else:
            d.show()
            return d
    def show_transaction(self,tx=None,txid=None,parent = None):
        if not parent:
            parent = self.window
        if txid !=None and txid in self.willitems:
            tx=self.willitems[txid].tx
        if not tx:
            raise Exception(_("no tx"))
        return self.show_transaction_real(tx,parent=parent)
    def invalidate_will(self):
        def on_success(result):
            if result:
                self.show_message(_("Please sign and broadcast this transaction to invalidate current will"))
                self.wallet.set_label(result.txid(),"BAL Invalidate")
                a=self.show_transaction(result)
            else:
                self.show_message(_("No transactions to invalidate"))
        def on_failure(exec_info):
            self.show_error(f"ERROR:{exec_info}")
        fee_per_byte=self.will_settings.get('tx_fees',1)
        task = partial(Will.invalidate_will,self.willitems,self.wallet,fee_per_byte)
        msg = _("Calculating Transactions")
        self.waiting_dialog = BalWaitingDialog(self, msg, task, on_success, on_failure,exe=False)
        self.waiting_dialog.exe()
    def sign_transactions(self,password):
        try:
            txs={}
            signed = None
            tosign = None
            def get_message():
                msg = ""
                if signed:
                    msg=_(f"signed: {signed}\n")
                return msg + _(f"signing: {tosign}")
            for txid in Will.only_valid(self.willitems):
                wi = self.willitems[txid]
                tx = copy.deepcopy(wi.tx)
                if wi.get_status('COMPLETE'):
                    txs[txid]=tx
                    continue
                tosign=txid
                try:
                    self.waiting_dialog.update(get_message())
                except:pass
                for txin in tx.inputs():
                    prevout = txin.prevout.to_json()
                    if prevout[0] in self.willitems:
                        change = self.willitems[prevout[0]].tx.outputs()[prevout[1]]
                        txin._trusted_value_sats = change.value
                        try:
                            txin.script_descriptor = change.script_descriptor
                        except:
                            pass
                        txin.is_mine=True
                        txin._TxInput__address=change.address
                        txin._TxInput__scriptpubkey = change.scriptpubkey
                        txin._TxInput__value_sats = change.value
                
                self.wallet.sign_transaction(tx, password,ignore_warnings=True)
                signed=tosign
                is_complete=False
                if tx.is_complete():
                    is_complete=True
                    wi.set_status('COMPLETE',True)
                txs[txid]=tx
        except Exception as e:
            return None
        return txs
    def get_wallet_password(self,message=None,parent=None):
        parent =self.window if not parent else parent
        password = None
        if self.wallet.has_keystore_encryption():
            password = self.bal_plugin.password_dialog(parent=parent,msg=message)
            if password is None:
                return False
            try:
                self.wallet.check_password(password)
            except Exception as e:
                self.show_error(str(e))
                password = self.get_wallet_password(message)
        return password
    def on_close(self):
        try:
            if not self.disable_plugin:
                close_window=BalBuildWillDialog(self)
                close_window.build_will_task()
                self.save_willitems()
                self.heirs_tab.close()
                self.will_tab.close()
                self.tools_menu.removeAction(self.tools_menu.willexecutors_action)
                self.window.toggle_tab(self.heirs_tab)
                self.window.toggle_tab(self.will_tab)
                self.window.tabs.update()
        except Exception as e: 
            pass
    def ask_password_and_sign_transactions(self,callback=None):
        def on_success(txs):
            if txs:
                for txid,tx in txs.items():
                    self.willitems[txid].tx=copy.deepcopy(tx)
                    self.will[txid]=self.willitems[txid].to_dict()
                try:
                    self.will_list.update()
                except:
                    pass
                if callback:
                    try:
                        callback()
                    except Exception as e:
                        raise e
        def on_failure(exc_info):
            self.logger.info("sign fail: {}".format(exc_info))
            self.show_error(exc_info)
        password= self.get_wallet_password() 
        task = partial(self.sign_transactions,password)
        msg = _('Signing transactions...')
        self.waiting_dialog = BalWaitingDialog(self, msg, task, on_success, on_failure,exe=False)
        self.waiting_dialog.exe()
    def broadcast_transactions(self,force=False):
        def on_success(sulcess):
            self.will_list.update()
            if sulcess:
                self.logger.info("error, some transaction was not sent");
                self.show_warning(_("Some transaction was not broadcasted"))
                return
            self.logger.debug("OK, sulcess transaction was sent")
            self.show_message(_("All transactions are broadcasted to respective Will-Executors"))
        def on_failure(err):
            self.logger.error(err)
        task = partial(self.push_transactions_to_willexecutors,force)
        msg = _('Selecting Will-Executors')
        self.waiting_dialog = BalWaitingDialog(self,msg,task,on_success,on_failure,exe=False)
        self.waiting_dialog.exe()
    def push_transactions_to_willexecutors(self,force=False):
        willexecutors = Willexecutors.get_willexecutor_transactions(self.willitems)
        def getMsg(willexecutors):
            msg = "Broadcasting Transactions to Will-Executors:\n"
            for url in willexecutors:
                msg += f"{url}:\t{willexecutors[url]['broadcast_status']}\n"
            return msg
        error=False
        for url in willexecutors:
            willexecutor = willexecutors[url]
            self.waiting_dialog.update(getMsg(willexecutors))
            if 'txs' in willexecutor:
                try:
                    if Willexecutors.push_transactions_to_willexecutor(willexecutors[url]):
                        for wid in willexecutors[url]['txsids']:
                            self.willitems[wid].set_status('PUSHED', True)
                        willexecutors[url]['broadcast_status'] = _("Success")
                    else:
                        for wid in willexecutors[url]['txsids']:
                            self.willitems[wid].set_status('PUSH_FAIL', True)
                            error=True
                        willexecutors[url]['broadcast_status'] = _("Failed")
                    del willexecutor['txs']
                except Willexecutors.AlreadyPresentException:
                    for wid in willexecutor['txsids']:
                        row = self.waiting_dialog.update("checking {} - {} : {}".format(self.willitems[wid].we['url'],wid, "Waiting"))
                        self.willitems[wid].check_willexecutor()
                        row = self.waiting_dialog.update("checked {} - {} : {}".format(self.willitems[wid].we['url'],wid,self.willitems[wid].get_status("CHECKED" )))
        if error:
            return True
    def export_json_file(self,path):
        for wid in self.willitems:
            self.willitems[wid].set_status('EXPORTED', True)
            self.will[wid]=self.willitems[wid].to_dict()
        write_json_file(path, self.will)
    def export_will(self):
        try:
            Util.export_meta_gui(self.window, _('will.json'), self.export_json_file)
        except Exception as e:
            self.show_error(str(e))
            raise e
        
    def import_will(self):
        def sulcess():
            self.will_list.update_will(self.willitems)
        import_meta_gui(self.window, _('will'), self.import_json_file,sulcess)
    def import_json_file(self,path):
        try:
            data = read_json_file(path)
            willitems = {}
            for k,v in data.items():
                data[k]['tx']=tx_from_any(v['tx'])
                willitems[k]=WillItem(data[k],_id=k)
            self.update_will(willitems)
        except Exception as e:
            raise e
            raise FileImportFailed(_("Invalid will file"))
    def check_transactions_task(self,will):
        start = time.time()
        for wid,w in will.items():
            if w.we:
                self.waiting_dialog.update("checking transaction: {}\n willexecutor: {}".format(wid,w.we['url']))
                w.check_willexecutor()
        if time.time()-start < 3:
            time.sleep(3-(time.time()-start))
    def check_transactions(self,will):
        def on_success(result):
            del self.waiting_dialog
            self.update_all()
            pass
        def on_failure(e):
            self.logger.error(f"error checking transactions {e}")
            pass
        task = partial(self.check_transactions_task,will)
        msg = _('Check Transaction')
        self.waiting_dialog = BalWaitingDialog(self,msg,task,on_success,on_failure,exe=False)
        self.waiting_dialog.exe()
    def ping_willexecutors_task(self,wes):
        self.logger.info("ping willexecutots task")
        pinged = []
        failed = []
        def get_title():
            msg = _('Ping Will-Executors:')
            msg += "\n\n"
            for url in wes:
                urlstr = "{:<50}: ".format(url[:50])
                if url in pinged:
                    urlstr += "Ok"
                elif url in failed:
                    urlstr +="Ko"
                else:
                    urlstr += "--"
                urlstr+="\n"
                msg+=urlstr
            return msg 
        for url,we in wes.items():
            try:
                self.waiting_dialog.update(get_title())
            except:
                pass
            wes[url]=Willexecutors.get_info_task(url,we)
            if wes[url]['status']=='KO':
                failed.append(url)
            else:
                pinged.append(url)
            
    def ping_willexecutors(self,wes,parent=None):
        if not parent:
            parent=self
        def on_success(result):
            del self.waiting_dialog
            try:
                parent.willexecutor_list.update()
            except Exception as e:
                _logger.error(f"error updating willexecutors {e}")
                pass
        def on_failure(e):
            self.logger.error(e)
            pass
        self.logger.info("ping willexecutors")
        task = partial(self.ping_willexecutors_task,wes)
        msg = _('Ping Will-Executors')
        self.waiting_dialog = BalWaitingDialog(self,msg,task,on_success,on_failure,exe=False)
        self.waiting_dialog.exe()
    def preview_modal_dialog(self):
        self.dw=WillDetailDialog(self)
        self.dw.show()
            
    def update_all(self):
        self.will_list.update_will(self.willitems)
        self.heirs_tab.update()
        self.will_tab.update()
        self.will_list.update()
def add_widget(grid,label,widget,row,help_):
    grid.addWidget(QLabel(_(label)),row,0)
    grid.addWidget(widget,row,1)
    grid.addWidget(HelpButton(help_),row,2)
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_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)
        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)
        #spacer_widget = QWidget()
        #spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        #hbox.addWidget(spacer_widget)
        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()
    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))
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)
_NOT_GIVEN = object()  # sentinel value
class PercAmountEdit(BTCAmountEdit):
    def __init__(self, decimal_point, is_int=False, parent=None, *, max_amount=_NOT_GIVEN):
        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 BalDialog(WindowModalDialog):
    def __init__(self,parent,bal_plugin,title=None, icon = 'bal32x32.png'):
        self.parent=parent
        WindowModalDialog.__init__(self,parent,title)
        #WindowModalDialog.__init__(self,parent)
        self.setWindowIcon(read_QIcon_from_bytes(bal_plugin.read_file(icon)))
class BalWizardDialog(BalDialog):
    def __init__(self,bal_window: 'BalWindow' ):
        assert bal_window
        BalDialog.__init__(self, bal_window.window, bal_window.bal_plugin, _("Bal Wizard Setup"))
        self.setMinimumSize(800, 400)
        self.bal_window = bal_window
        self.parent = bal_window.window
        self.layout=QVBoxLayout(self)
        self.widget = BalWizardHeirsWidget(bal_window,self,self.on_next_heir,None,self.on_cancel_heir)
        self.layout.addWidget(self.widget)
    def next_widget(self,widget):
        self.layout.removeWidget(self.widget)
        self.widget.close()
        self.widget=widget
        self.layout.addWidget(self.widget)
        #self.update()
        #self.repaint()
    def on_next_heir(self):
        self.next_widget(BalWizardLocktimeAndFeeWidget(self.bal_window,self,self.on_next_locktimeandfee,self.on_previous_heir,self.on_cancel_heir))
    def on_previous_heir(self):
        self.next_widget(BalWizardHeirsWidget(self.bal_window,self,self.on_next_heir,None,self.on_cancel_heir))
    def on_cancel_heir(self):
        pass
    def on_next_wedonwload(self):
        self.next_widget(BalWizardWEWidget(self.bal_window,self,self.on_next_we,self.on_next_locktimeandfee,self.on_cancel_heir))
    def on_next_we(self):
        close_window=BalBuildWillDialog(self.bal_window,self)
        close_window.build_will_task()
        self.close()
        #self.next_widget(BalWizardLocktimeAndFeeWidget(self.bal_window,self,self.on_next_locktimeandfee,self.on_next_wedonwload,self.on_next_wedonwload.on_cancel_heir))
    def on_next_locktimeandfee(self):
        self.next_widget(BalWizardWEDownloadWidget(self.bal_window,self,self.on_next_wedonwload,self.on_next_heir,self.on_cancel_heir))
    def on_accept(self):
        pass
    def on_reject(self):
        pass
    def on_close(self):
        pass
    def closeEvent(self,event):
        self.bal_window.update_all()
        self.bal_window.heir_list.update_will_settings()
        pass
class BalWizardWidget(QWidget):
    title = None
    message = None
    def __init__(self,bal_window: 'BalWindow',parent,on_next,on_previous,on_cancel):
        QWidget.__init__(self,parent)
        self.vbox=QVBoxLayout(self)
        self.bal_window=bal_window
        self.parent=parent
        self.on_next=on_next
        self.on_cancel=on_cancel
        self.titleLabel = QLabel(self.title)
        self.vbox.addWidget(self.titleLabel)
        self.messageLabel = QLabel(_(self.message))
        self.vbox.addWidget(self.messageLabel)
        self.content = self.get_content()
        self.content_container= QWidget()
        self.containrelayout=QVBoxLayout(self.content_container)
        self.containrelayout.addWidget(self.content)
        self.vbox.addWidget(self.content_container)
        spacer_widget = QWidget()
        spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        self.vbox.addWidget(spacer_widget)
        self.buttons=[]
        if on_previous:
            self.on_previous=on_previous
            self.previous_button = QPushButton(_("Previous"))
            self.previous_button.clicked.connect(self._on_previous)
            self.buttons.append(self.previous_button)
        self.next_button = QPushButton(_("Next"))
        self.next_button.clicked.connect(self._on_next)
        self.buttons.append(self.next_button)
            
        self.abort_button = QPushButton(_("Cancel"))
        self.abort_button.clicked.connect(self._on_cancel)
        self.buttons.append(self.abort_button)
        self.vbox.addLayout(Buttons(*self.buttons))
    def _on_cancel(self):
        self.on_cancel()
        self.parent.close()
    def _on_next(self):
        if self.validate():
            self.on_next()
            
    def _on_previous(self):
        self.on_previous()
 
    def get_content(self):
        pass
    
    def validate(self):
        return True
class BalWizardHeirsWidget(BalWizardWidget):
    title="Bitcoin After Life Heirs"
    message="Please add your heirs\n remember that 100% of wallet balance will be spent"
    def get_content(self):
        self.heirs_list=HeirList(self.bal_window,self.parent)
        button_add=QPushButton(_("Add"))
        button_add.clicked.connect(self.add_heir)
        button_import=QPushButton(_("Import"))
        button_import.clicked.connect(self.import_from_file)
        button_export=QPushButton(_("Export"))
        button_import.clicked.connect(self.export_to_file)
        widget=QWidget()
        vbox=QVBoxLayout(widget)
        vbox.addWidget(self.heirs_list)
        vbox.addLayout(Buttons(button_add,button_import,button_export))
        return widget
    
    def import_from_file(self):
        self.bal_window.import_heirs()
        self.heirs_list.update()
    def export_to_file(self):
        self.bal_window.export_heirs()
    def add_heir(self):
        self.bal_window.new_heir_dialog()
        self.heirs_list.update()
    def validate(self):
        return True
class BalWizardWEDownloadWidget(BalWizardWidget):
    title=_("Bitcoin After Life Will-Executors")
    message=_("Choose willexecutors download method")
    def get_content(self):
        question=QLabel()
        self.combo=QComboBox()
        self.combo.addItems([
            "Automatically download and select willexecutors",
            "Only download willexecutors list",
            "Import willexecutor list from file",
            "Manual"])
        #heir_name.setFixedWidth(32 * char_width_in_lineedit())
        return self.combo
    def validate(self):
        return True
    def _on_next(self):
        index = self.combo.currentIndex()
        _logger.debug(f"selected index:{index}")
        if index < 3:
            self.bal_window.willexecutors=Willexecutors.get_willexecutors(self.bal_window.bal_plugin)
            
            if index == 2:
                def doNothing():
                    self.bal_window.willexecutors.update(self.willexecutors)
                    Willexecutors.save(self.bal_window.bal_plugin,self.bal_window.willexecutors)
                    pass
                import_meta_gui(self.bal_window.window, _('willexecutors.json'), self.import_json_file, doNothing)
            if index < 2:
                def on_success(willexecutors):
                    self.bal_window.willexecutors.update(willexecutors)
                    self.bal_window.ping_willexecutors(self.bal_window.willexecutors)
                    if index < 1:
                        for we in self.bal_window.willexecutors:
                            if self.bal_window.willexecutors[we]['status']==200:
                                self.bal_window.willexecutors[we]['selected']=True
                    Willexecutors.save(self.bal_window.bal_plugin,self.bal_window.willexecutors)
                def on_failure(fail):
                    _logger.debug(f"Failed to download willexecutors list {fail}")
                    pass
                task = partial(Willexecutors.download_list,self.bal_window.bal_plugin)
                msg = _("Downloading Will-Executors list")
                self.waiting_dialog = BalWaitingDialog(self.bal_window, msg, task, on_success, on_failure,exe=False)
                self.waiting_dialog.exe()
            
        elif index == 3:
            #TODO DO NOTHING
            pass
        if self.validate():
            return self.on_next()
    def import_json_file(self, path):
        data = read_json_file(path)
        data = self._validate(data)
        self.willexecutors=data
    def _validate(self,data):
        return data
class BalWizardWEWidget(BalWizardWidget):
    title=("Bitcoin After Life Will-Executors")
    message=_("Configure and select your willexecutors")
    def get_content(self):
        widget=QWidget()
        vbox=QVBoxLayout(widget)
        vbox.addWidget(WillExecutorWidget(self,self.bal_window,Willexecutors.get_willexecutors(self.bal_window.bal_plugin)))
        return widget
class BalWizardLocktimeAndFeeWidget(BalWizardWidget):
    title=("Bitcoin After Life Will Settings")
    message=_("")
    def get_content(self):
        widget=QWidget()
        self.heir_locktime = HeirsLockTimeEdit(widget,0)
        will_settings=self.bal_window.bal_plugin.WILL_SETTINGS.get()
        self.heir_locktime.set_locktime(will_settings['locktime'])
        def on_heir_locktime():
            if not self.heir_locktime.get_locktime():
                self.heir_locktime.set_locktime('1y')
            self.bal_window.will_settings['locktime'] = self.heir_locktime.get_locktime() if self.heir_locktime.get_locktime() else "1y"
            self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
        self.heir_locktime.valueEdited.connect(on_heir_locktime)
        self.heir_threshold = HeirsLockTimeEdit(widget,0)
        self.heir_threshold.set_locktime(will_settings['threshold'])
        def on_heir_threshold():
            if not self.heir_threshold.get_locktime():
                self.heir_threshold.set_locktime('180d')
            self.bal_window.will_settings['threshold'] = self.heir_threshold.get_locktime() 
            self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
        self.heir_threshold.valueEdited.connect(on_heir_threshold)
        self.heir_tx_fees = QSpinBox(widget)
        self.heir_tx_fees.setMinimum(1)
        self.heir_tx_fees.setMaximum(10000)
        self.heir_tx_fees.setValue(will_settings['tx_fees'])
        def on_heir_tx_fees():
            if not self.heir_tx_fees.value():
                self.heir_tx_fees.set_value(1)
            self.bal_window.will_settings['tx_fees'] = self.heir_tx_fees.value()
            self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
        self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees)
        
        def make_hlayout(label,twidget,help_text):
            tw=QWidget()
            hlayout=QHBoxLayout(tw)
            hlayout.addWidget(QLabel(label))
            hlayout.addWidget(twidget)
            hlayout.addWidget(HelpButton(help_text))
            hlayout.addStretch(1)
            spacer_widget = QWidget()
            spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
            hlayout.addWidget(spacer_widget)
            return tw
        layout = QVBoxLayout(widget)
        
        layout.addWidget(make_hlayout(_("Delivery Time:"),self.heir_locktime,_("Locktime* to be used in the transaction\n"
                                    +"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"
                                    +"* locktime can be anticipated to update will\n")))
        layout.addWidget(make_hlayout(_("Check Alive:"),self.heir_threshold,_("Check  to ask for invalidation.\n"
                                    +"When less then this time is missing, ask to invalidate.\n"
                                    +"If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n"
                                    +"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\n")))
        layout.addWidget(make_hlayout(_("Fees(sats/vbyte):"),self.heir_tx_fees,("Fee to be used in the transaction")))
        spacer_widget = QWidget()
        spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        layout.addWidget(spacer_widget)
        return widget
class BalWaitingDialog(BalDialog):
    updatemessage=pyqtSignal([str], arguments=['message'])
    def __init__(self, bal_window: 'BalWindow', message: str, task, on_success=None, on_error=None, on_cancel=None,exe=True):
        assert bal_window
        BalDialog.__init__(self, bal_window.window,bal_window.bal_plugin, _("Please wait"))
        self.message_label = QLabel(message)
        vbox = QVBoxLayout(self)
        vbox.addWidget(self.message_label)
        self.updatemessage.connect(self.update_message)
        if on_cancel:
            self.cancel_button = CancelButton(self)
            self.cancel_button.clicked.connect(on_cancel)
            vbox.addLayout(Buttons(self.cancel_button))
        self.accepted.connect(self.on_accepted)
        self.task=task
        self.on_success = on_success
        self.on_error = on_error
        self.on_cancel = on_cancel
        if exe:
            self.exe()
    def exe(self):
        self.thread = TaskThread(self)
        self.thread.finished.connect(self.deleteLater)  # see #3956
        self.thread.finished.connect(self.finished)
        self.thread.add(self.task, self.on_success, self.accept, self.on_error)
        self.exec()
    def hello(self):
        pass
    def finished(self):
        _logger.info("finished")
    def wait(self):
        self.thread.wait()
    def on_accepted(self):
        self.thread.stop()
    def update_message(self,msg):
        self.message_label.setText(msg)
    def update(self, msg):
        self.updatemessage.emit(msg)
    def getText(self):
         return self.message_label.text()
    def closeEvent(self,event):
        self.thread.stop()
class BalBlockingWaitingDialog(BalDialog):
    def __init__(self, bal_window: 'BalWindow', message: str, task: Callable[[], Any]):
        BalDialog.__init__(self, bal_window, bal_window.bal_plugin,_("Please wait"))
        self.message_label = QLabel(message)
        vbox = QVBoxLayout(self)
        vbox.addWidget(self.message_label)
        self.finished.connect(self.deleteLater)  # see #3956
        # show popup
        self.show()
        # refresh GUI; needed for popup to appear and for message_label to get drawn
        QCoreApplication.processEvents()
        QCoreApplication.processEvents()
        try:
            # block and run given task
            task()
        finally:
            # close popup
            self.accept()
class bal_checkbox(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 BalBuildWillDialog(BalDialog):
    updatemessage=pyqtSignal()
    def __init__(self,bal_window,parent=None):
        if not parent:
            parent = bal_window.window
        BalDialog.__init__(self,parent,babl_window.bal_plugin, "Building Will")
        self.updatemessage.connect(self.update)
        self.bal_window=bal_window
        self.message_label = QLabel("Building Will:")
        self.vbox = QVBoxLayout(self)
        self.vbox.addWidget(self.message_label)
        self.qwidget=QWidget()
        self.vbox.addWidget(self.qwidget)
        self.labels=[]
        self.check_row = None
        self.inval_row = None
        self.build_row = None
        self.sign_row = None
        self.push_row = None
        self.network = Network.get_instance()
        self._stopping = False
        self.thread = TaskThread(self)
        self.thread.finished.connect(self.task_finished)  # see #3956
    def task_finished(self):
        pass
        
    def build_will_task(self):
        _logger.debug("build will task to be started")
        self.thread.add(self.task_phase1,on_success=self.on_success_phase1,on_done=self.on_accept,on_error=self.on_error_phase1)
        self.show()
        self.exec()
    def task_phase1(self):
        _logger.debug("close plugin phase 1 started")
        try:
            self.bal_window.init_class_variables()
        except NoHeirsException:
            return False, None
        self.msg_set_status("checking variables","Waiting")
        try:
            Will.check_amounts(self.bal_window.heirs,self.bal_window.willexecutors,self.bal_window.window.wallet.get_utxos(),self.bal_window.date_to_check,self.bal_window.window.wallet.dust_threshold())
        except AmountException:
            self.msg_edit_row(''+_("In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts"+""))
        self.msg_set_checking()
        have_to_build=False
        try:
            self.bal_window.check_will()
            self.msg_set_checking('Ok')
        except WillExpiredException as e:
            self.msg_set_checking("Expired")
            fee_per_byte=self.bal_window.will_settings.get('tx_fees',1)
            return None, Will.invalidate_will(self.bal_window.willitems,self.bal_window.wallet,fee_per_byte)
        except NoHeirsException:
            self.msg_set_checking("No Heirs")
        except NotCompleteWillException as e:
            message = False
            have_to_build=True
            if isinstance(e,HeirChangeException):
                message ="Heirs changed:"
            elif isinstance(e,WillExecutorNotPresent):
                message = "Will-Executor not present"
            elif isinstance(e,WillexecutorChangeException):
                message = "Will-Executor changed"
            elif isinstance(e,TxFeesChangedException):
                message = "Txfees are changed"
            elif isinstance(e,HeirNotFoundException):
                message = "Heir not found"
            if message:
                self.msg_set_checking(message)
            else:
                self.msg_set_checking("New")
        if have_to_build:
            self.msg_set_building()
            try:
                self.bal_window.build_will()
                self.bal_window.check_will()
                for wid in Will.only_valid(self.bal_window.willitems):
                    self.bal_window.wallet.set_label(wid,"BAL Transaction")
                self.msg_set_building("Ok")
            except Exception as e:
                self.msg_set_building(self.msg_error(e))
                return False,None
        have_to_sign = False
        for wid in Will.only_valid(self.bal_window.willitems):
            if not self.bal_window.willitems[wid].get_status("COMPLETE"):
                have_to_sign = True
                break
        return have_to_sign, None
        
    def on_accept(self):
        pass
    def on_accept_phase2(self):
        pass
    def on_error_push(self):
        pass
    def wait(self,secs):
        wait_row=None
        for i in range(secs,0,-1):
            if self._stopping:
                return
            wait_row = self.msg_edit_row(f"Please wait {i}secs",      wait_row)
            time.sleep(1)
        self.msg_del_row(wait_row)
    def loop_broadcast_invalidating(self,tx):
        self.msg_set_invalidating("Broadcasting")
        try:
            tx.add_info_from_wallet(self.bal_window.wallet)
            self.network.run_from_another_thread(tx.add_info_from_network(self.network))
            txid = self.network.run_from_another_thread(self.network.broadcast_transaction(tx,timeout=120),timeout=120)
            self.msg_set_invalidating("Ok")
            if not txid:
                _logger.debug(f"should not be none txid: {txid}")
            
        except TxBroadcastError as e:
            _logger.error(e)
            msg = e.get_message_for_gui()
            self.msg_set_invalidating(self.msg_error(msg))
        except BestEffortRequestFailed as e:
            self.msg_set_invalidating(self.msg_error(e))
    def loop_push(self):
        self.msg_set_pushing("Broadcasting")
        retry = False
        try:
            willexecutors=Willexecutors.get_willexecutor_transactions(self.bal_window.willitems)
            for url,willexecutor in willexecutors.items():
                try:
                    if Willexecutors.is_selected(self.bal_window.willexecutors.get(url)):
                        _logger.debug(f"{url}: {willexecutor}")
                        if not Willexecutors.push_transactions_to_willexecutor(willexecutor):
                            for wid in willexecutor['txsids']:
                                self.bal_window.willitems[wid].set_status('PUSH_FAIL',True)
                            retry=True
                        else:
                            for wid in willexecutor['txsids']:
                                self.bal_window.willitems[wid].set_status('PUSHED',True)
                except Willexecutors.AlreadyPresentException:
                    for wid in willexecutor['txsids']:
                        row = self.msg_edit_row("checking {} - {} : {}".format(self.bal_window.willitems[wid].we['url'],wid, "Waiting"))
                        self.bal_window.willitems[wid].check_willexecutor()
                        row = self.msg_edit_row("checked {} - {} : {}".format(self.bal_window.willitems[wid].we['url'],wid,self.bal_window.willitems[wid].get_status("CHECKED" )),row)
                        
                            
                except Exception as e:
                    _logger.error(e)
                    raise e
            if retry:
                raise Exception("retry")
        except Exception as e:
            self.msg_set_pushing(self.msg_error(e))
            self.wait(10)
            if not self._stopping:
                self.loop_push()
    def invalidate_task(self,tx,password):
        _logger.debug(f"invalidate tx: {tx}")
        tx = self.bal_window.wallet.sign_transaction(tx,password)
        try:
            if tx:
                if tx.is_complete():
                    self.loop_broadcast_invalidating(tx)
                    self.wait(5)
                else:
                    raise
            else:
                raise
        except Exception as e:
            self.msg_set_invalidating("Error")
            raise Exception("Impossible to sign")
    def on_success_invalidate(self,success):
        self.thread.add(self.task_phase1,on_success=self.on_success_phase1,on_done=self.on_accept,on_error=self.on_error_phase1)
    def on_error(self,error):
        _logger.error(error)
        pass
    def on_success_phase1(self,result):
        self.have_to_sign,tx = list(result)
        _logger.debug("have to sign {}".format(self.have_to_sign))
        password=None
        if self.have_to_sign is None:
            self.msg_set_invalidating()
           #need to sign invalidate and restart phase 1
            password = self.bal_window.get_wallet_password("Invalidate your old will",parent=self)
            if password is False:
                self.msg_set_invalidating("Aborted")
                self.wait(3)
                self.close()
                return
            self.thread.add(partial(self.invalidate_task,tx,password),on_success=self.on_success_invalidate, on_done=self.on_accept, on_error=self.on_error)
            
            return
        
        elif self.have_to_sign:
            password = self.bal_window.get_wallet_password("Sign your will",parent=self)
            if password is False:
                self.msg_set_signing('Aborted')
        else:
            self.msg_set_signing('Nothing to do')
        self.thread.add(partial(self.task_phase2,password),on_success=self.on_success_phase2,on_done=self.on_accept_phase2,on_error=self.on_error_phase2)
        return
    
    def on_success_phase2(self,arg=False):
        self.thread.stop()
        self.bal_window.save_willitems()
        self.msg_edit_row("Finished")
        self.close()
    def closeEvent(self,event):
        self.bal_window.update_all()
        self._stopping=True
        self.thread.stop()
    def task_phase2(self,password):
        if self.have_to_sign:
            try:
                if txs:=self.bal_window.sign_transactions(password):
                    for txid,tx in txs.items():
                        self.bal_window.willitems[txid].tx = copy.deepcopy(tx)
                    self.bal_window.save_willitems()
                    self.msg_set_signing("Ok")
            except Exception as e:
                self.msg_set_signing(self.msg_error(e))
        self.msg_set_pushing()
        have_to_push = False
        for wid in Will.only_valid(self.bal_window.willitems):
            w=self.bal_window.willitems[wid]
            if w.we and w.get_status("COMPLETE") and not w.get_status("PUSHED"):
                have_to_push = True
        if not have_to_push:
            self.msg_set_pushing("Nothing to do")
        else:
            try:
                self.loop_push()
                self.msg_set_pushing("Ok")
            except Exception as e:
                self.msg_set_pushing(self.msg_error(e))
        self.msg_edit_row("Ok")
        self.wait(5)
    def on_error_phase1(self,error):
        _logger.error(f"error phase1: {error}")
    def on_error_phase2(self,error):
        _logger.error("error phase2: { error}")
    def msg_set_checking(self, status = None, row = None):
        row = self.check_row if row is None else row
        self.check_row = self.msg_set_status("Checking your will",    row,  status)
    def msg_set_invalidating(self, status = None, row = None):
        row = self.inval_row if row is None else row
        self.inval_row = self.msg_set_status("Invalidating old will",    self.inval_row,  status)
    def msg_set_building(self, status = None, row = None):
        row = self.build_row if row is None else row
        self.build_row = self.msg_set_status("Building your will",    self.build_row,  status)
    def msg_set_signing(self, status = None, row = None):
        row = self.sign_row if row is None else row
        self.sign_row = self.msg_set_status("Signing your will",      self.sign_row,   status)
    def msg_set_pushing(self, status = None, row = None):
        row = self.push_row if row is None else row
        self.push_row = self.msg_set_status("Broadcasting your will to executors",      self.push_row,   status)
    def msg_set_waiting(self, status = None, row = None):
        row = self.wait_row if row is None else row
        self.wait_row = self.msg_edit_row(f"Please wait {status}secs",      self.wait_row)
    def msg_error(self,e):
        return "Error: {}".format(e)
    def msg_set_status(self,msg,row,status=None):
        status= "Wait" if status is None else status
        line="{}:\t{}".format(_(msg), status)
        return self.msg_edit_row(line,row)
        
    
    def ask_password(self,msg=None):
        self.password=self.bal_window.get_wallet_password(msg,parent=self)
    def msg_edit_row(self,line,row=None):
        _logger.debug(f"{row},{line}")
        
        try:
            self.labels[row]=line
        except Exception as e:
            self.labels.append(line)
            row=len(self.labels)-1
        
        self.updatemessage.emit()
    
        return row
    def msg_del_row(self,row):
        try:
            del self.labels[row]
        except Exception as e:
            pass
        self.updatemessage.emit()
    def update(self):
        self.vbox.removeWidget(self.qwidget)
        self.qwidget=QWidget(self)
        labelsbox = QVBoxLayout(self.qwidget)
        for label in self.labels:
            labelsbox.addWidget(QLabel(label))
        self.vbox.addWidget(self.qwidget)
    def get_text(self):
        return self.message_label.text()
    pass
class HeirList(MyTreeView,MessageBoxMixin):
    class Columns(MyTreeView.BaseColumnsEnum):
        NAME = enum.auto()
        ADDRESS = enum.auto()
        AMOUNT = enum.auto()
    headers = {
        Columns.NAME: _('Name'),
        Columns.ADDRESS: _('Address'),
        Columns.AMOUNT: _('Amount'),
    }
    filter_columns = [Columns.NAME, Columns.ADDRESS]
    ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1000
    ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 1001
    key_role = ROLE_HEIR_KEY
    def __init__(self, bal_window: 'BalWindow',parent):
        super().__init__(
            parent=parent,
            main_window=bal_window.window,
            stretch_column=self.Columns.NAME,
            editable_columns=[self.Columns.NAME,self.Columns.ADDRESS,self.Columns.AMOUNT],
        )
        self.decimal_point = bal_window.bal_plugin.config.get_decimal_point()
        self.bal_window = bal_window
    
        try:
            self.setModel(QStandardItemModel(self))
            self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder)
            self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
        except:
            pass
        self.setSortingEnabled(True)
        self.std_model = self.model()
        self.update()
    
    def on_activated(self,idx):
        self.on_double_click(idx)
    def on_double_click(self,idx):
        edit_key = self.get_edit_key_from_coordinate(idx.row(),idx.column())
        heir= self.bal_window.heirs.get(edit_key)
        self.bal_window.new_heir_dialog(edit_key)
    def on_edited(self, idx, edit_key, *, text):
        original = prior_name = self.bal_window.heirs.get(edit_key)
        if not prior_name:
            return
        col = idx.column()
        try:
            if col == 2:
                text = Util.encode_amount(text,self.decimal_point)
            elif col == 0:
                self.bal_window.delete_heirs([edit_key])
                edit_key = text
            prior_name[col-1] = text
            prior_name.insert(0,edit_key)
            prior_name = tuple(prior_name)
        except Exception as e:
            prior_name = (edit_key,)+prior_name[:col-1]+(text,)+prior_name[col:]
       
        try:
            self.bal_window.set_heir(prior_name)
        except Exception as e:
            pass
            try:
                self.bal_window.set_heir((edit_key,)+original)
            except Exception as e:
                self.update()
    def create_menu(self, position):
        menu = QMenu()
        idx = self.indexAt(position)
        column = idx.column() or self.Columns.NAME
        selected_keys = []
        for s_idx in self.selected_in_column(self.Columns.NAME):
            sel_key = self.model().itemFromIndex(s_idx).data(0)
            selected_keys.append(sel_key)
        if selected_keys and idx.isValid():
            column_title = self.model().horizontalHeaderItem(column).text()
            column_data = '\n'.join(self.model().itemFromIndex(s_idx).text()
                                    for s_idx in self.selected_in_column(column))
            menu.addAction(_("Copy {}").format(column_title), lambda: self.place_text_on_clipboard(column_data, title=column_title))
            if column in self.editable_columns:
                item = self.model().itemFromIndex(idx)
                if item.isEditable():
                    persistent = QPersistentModelIndex(idx)
                    menu.addAction(_("Edit {}").format(column_title), lambda p=persistent: self.edit(QModelIndex(p)))
            menu.addAction(_("Delete"), lambda: self.bal_window.delete_heirs(selected_keys))
        menu.exec(self.viewport().mapToGlobal(position))
    #def get_selected_keys(self):
    #    selected_keys = []
    #    for s_idx in self.selected_in_column(self.Columns.NAME):
    #        sel_key = self.model().itemFromIndex(s_idx).data(0)
    #        selected_keys.append(sel_key)
    #    return selected_keys
    def update(self):
        if self.maybe_defer_update():
            return
        current_key = self.get_role_data_for_current_item(col=self.Columns.NAME, role=self.ROLE_HEIR_KEY)
        self.model().clear()
        self.update_headers(self.__class__.headers)
        set_current = None
        for key in sorted(self.bal_window.heirs.keys()):
            heir = self.bal_window.heirs[key]
            labels = [""] * len(self.Columns)
            labels[self.Columns.NAME] = key
            labels[self.Columns.ADDRESS] = heir[0]
            labels[self.Columns.AMOUNT] = Util.decode_amount(heir[1],self.decimal_point)
            items = [QStandardItem(x) for x in labels]
            items[self.Columns.NAME].setEditable(True)
            items[self.Columns.ADDRESS].setEditable(True)
            items[self.Columns.AMOUNT].setEditable(True)
            items[self.Columns.NAME].setData(key, self.ROLE_HEIR_KEY+1)
            items[self.Columns.ADDRESS].setData(key, self.ROLE_HEIR_KEY+2)
            items[self.Columns.AMOUNT].setData(key, self.ROLE_HEIR_KEY+3)
            self.model().insertRow(self.model().rowCount(), items)
            if key == current_key:
                idx = self.model().index(row_count, self.Columns.NAME)
                set_current = QPersistentModelIndex(idx)
        self.set_current_idx(set_current)
        # FIXME refresh loses sort order; so set "default" here:
        self.filter()
        run_hook('update_heirs_tab', self)
        self.update_will_settings()
    def refresh_row(self, key, row):
        # nothing to update here
        pass
    def get_edit_key_from_coordinate(self, row, col):
        return self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY+col+1) 
    def create_toolbar(self, config):
        toolbar, menu = self.create_toolbar_with_menu('')
        menu.addAction(_("&New Heir"), self.bal_window.new_heir_dialog)
        menu.addAction(_("Import"), self.bal_window.import_heirs)
        menu.addAction(_("Export"), lambda: self.bal_window.export_heirs())
        self.heir_locktime = HeirsLockTimeEdit(self,0)
        def on_heir_locktime():
            if not self.heir_locktime.get_locktime():
                self.heir_locktime.set_locktime('1y')
            self.bal_window.will_settings['locktime'] = self.heir_locktime.get_locktime() if self.heir_locktime.get_locktime() else "1y"
            self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
        self.heir_locktime.valueEdited.connect(on_heir_locktime)
        self.heir_threshold = HeirsLockTimeEdit(self,0)
        def on_heir_threshold():
            if not self.heir_threshold.get_locktime():
                self.heir_threshold.set_locktime('180d')
            self.bal_window.will_settings['threshold'] = self.heir_threshold.get_locktime() 
            self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
        self.heir_threshold.valueEdited.connect(on_heir_threshold)
        self.heir_tx_fees = QSpinBox()
        self.heir_tx_fees.setMinimum(1)
        self.heir_tx_fees.setMaximum(10000)
        def on_heir_tx_fees():
            if not self.heir_tx_fees.value():
                self.heir_tx_fees.set_value(1)
            self.bal_window.will_settings['tx_fees'] = self.heir_tx_fees.value()
            self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
        self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees)
        self.heirs_widget = QWidget()
        layout = QHBoxLayout()
        self.heirs_widget.setLayout(layout)
        
        layout.addWidget(QLabel(_("Delivery Time:")))
        layout.addWidget(self.heir_locktime)
        layout.addWidget(HelpButton(_("Locktime* to be used in the transaction\n"
                                    +"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"
                                    +"* locktime can be anticipated to update will\n")))
        layout.addWidget(QLabel(" "))
        layout.addWidget(QLabel(_("Check Alive:")))
        layout.addWidget(self.heir_threshold)
        layout.addWidget(HelpButton(_("Check  to ask for invalidation.\n"
                                    +"When less then this time is missing, ask to invalidate.\n"
                                    +"If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n"
                                    +"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\n")))
        layout.addWidget(QLabel(" "))
        layout.addWidget(QLabel(_("Fees:")))
        layout.addWidget(self.heir_tx_fees)
        layout.addWidget(HelpButton(_("Fee to be used in the transaction")))
        layout.addWidget(QLabel("sats/vbyte"))
        layout.addWidget(QLabel(" "))
        newHeirButton = QPushButton(_("New Heir"))
        newHeirButton.clicked.connect(self.bal_window.new_heir_dialog)
        layout.addWidget(newHeirButton)
        toolbar.insertWidget(2, self.heirs_widget)
        return toolbar
    def update_will_settings(self):
        try:
            self.heir_locktime.set_locktime(self.bal_window.will_settings['locktime'])
            self.heir_tx_fees.setValue(int(self.bal_window.will_settings['tx_fees']))
            self.heir_threshold.set_locktime(self.bal_window.will_settings['threshold'])
        except Exception as e:
            _logger.error(f"Exception update_will_settings {e}")
    def build_transactions(self):
        will = self.bal_window.prepare_will()
class PreviewList(MyTreeView):
    class Columns(MyTreeView.BaseColumnsEnum):
        LOCKTIME = enum.auto()
        TXID = enum.auto()
        WILLEXECUTOR = enum.auto()
        STATUS = enum.auto()
    headers = {
        Columns.LOCKTIME: _('Locktime'),
        Columns.TXID: _('Txid'),
        Columns.WILLEXECUTOR: _('Will-Executor'),
        Columns.STATUS: _('Status'),
    }
    ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 2000
    key_role = ROLE_HEIR_KEY
    def __init__(self, bal_window: 'BalWindow',parent,will):
        super().__init__(
            parent=parent,
            stretch_column=self.Columns.TXID,
        )
        self.parent=parent
        self.bal_window=bal_window
        self.decimal_point=bal_window.bal_plugin.config.get_decimal_point
        self.setModel(QStandardItemModel(self))
        self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
    
        if not will is None:
            self.will = will
        else:
            self.will = bal_window.willitems
        self.wallet=bal_window.window.wallet
        self.setModel(QStandardItemModel(self))
        self.setSortingEnabled(True)
        self.std_model = self.model()
        self.config = bal_window.bal_plugin.config
        self.bal_plugin=self.bal_window.bal_plugin
        self.update()
    def on_activated(self,idx):
        self.on_double_click(idx)
    def on_double_click(self,idx):
        idx = self.model().index(idx.row(), self.Columns.TXID)
        sel_key = self.model().itemFromIndex(idx).data(0)
        self.show_transaction([sel_key])
    def create_menu(self, position):
        menu = QMenu()
        idx = self.indexAt(position)
        column = idx.column() or self.Columns.TXID
        selected_keys = []
        for s_idx in self.selected_in_column(self.Columns.TXID):
            sel_key = self.model().itemFromIndex(s_idx).data(0)
            selected_keys.append(sel_key)
        if selected_keys and idx.isValid():
            column_title = self.model().horizontalHeaderItem(column).text()
            column_data = '\n'.join(self.model().itemFromIndex(s_idx).text()
                                    for s_idx in self.selected_in_column(column))
            menu.addAction(_("details").format(column_title), lambda: self.show_transaction(selected_keys)).setEnabled(len(selected_keys)<2)
            menu.addAction(_("check ").format(column_title), lambda: self.check_transactions(selected_keys))
            menu.addSeparator()
            menu.addAction(_("delete").format(column_title), lambda: self.delete(selected_keys))
        menu.exec(self.viewport().mapToGlobal(position))
    def delete(self,selected_keys):
        for key in selected_keys:
            del self.will[key]
            try:
                del self.bal_window.willitems[key]
            except:
                pass
            try:
                del self.bal_window.will[key]
            except:
                pass
        self.update()
    def check_transactions(self,selected_keys):
        wout = {}
        for k in selected_keys:
            wout[k] = self.will[k]
        if wout:
            self.bal_window.check_transactions(wout)
        self.update()
    def show_transaction(self,selected_keys):
        for key in selected_keys:
            self.bal_window.show_transaction(self.will[key].tx)
        self.update()
    def select(self,selected_keys):
        self.selected += selected_keys
        self.update()
    def deselect(self,selected_keys):
        for key in selected_keys:
            self.selected.remove(key)
        self.update()
    def update_will(self,will):
        self.will.update(will)
        self.update()
    def update(self):
        try:
            self.menu.removeAction(self.importaction)
        except Exception as e:
            pass
        finally:
            if self.bal_plugin.ENABLE_MULTIVERSE.get():
                try:
                    self.importaction=self.menu.addAction(_("Import"), self.import_will)
                except Exception as ex:
                    pass
        if self.will is None:
            return
        current_key = self.get_role_data_for_current_item(col=self.Columns.TXID, role=self.ROLE_HEIR_KEY)
        self.model().clear()
        self.update_headers(self.__class__.headers)
        set_current = None
        for txid,bal_tx in self.will.items():
            if self.bal_window.bal_plugin._hide_replaced and bal_tx.get_status('REPLACED'):
                continue
            if self.bal_window.bal_plugin._hide_invalidated and bal_tx.get_status('INVALIDATED'):
                continue
            if not isinstance(bal_tx,WillItem):
                bal_tx=WillItem(bal_tx)
            tx=bal_tx.tx
            labels = [""] * len(self.Columns)
            labels[self.Columns.LOCKTIME] = Util.locktime_to_str(tx.locktime)
            labels[self.Columns.TXID] = txid
            we = 'None'
            if bal_tx.we:
                we = bal_tx.we['url']
            labels[self.Columns.WILLEXECUTOR]=we
            status = bal_tx.status
            if len(bal_tx.status) > 53:
                status = "...{}".format(status[-50:])
            labels[self.Columns.STATUS] = status
            
            
            
            items=[]
            for e in labels:
                if type(e)== list:
                    try:
                        items.append(QStandardItem(*e))
                    except Exception as e:
                        pass
                else:
                    items.append(QStandardItem(str(e)))
                items[-1].setBackground(QColor(bal_tx.get_color()))
            self.model().insertRow(self.model().rowCount(), items)
            if txid == current_key:
                idx = self.model().index(row_count, self.Columns.TXID)
                set_current = QPersistentModelIndex(idx)
            self.set_current_idx(set_current)
    def create_toolbar(self, config): 
        toolbar, menu = self.create_toolbar_with_menu('') 
        menu.addAction(_("Prepare"), self.build_transactions) 
        menu.addAction(_("Display"), self.bal_window.preview_modal_dialog) 
        menu.addAction(_("Sign"), self.ask_password_and_sign_transactions)
        menu.addAction(_("Export"), self.export_will)
        if self.bal_plugin.ENABLE_MULTIVERSE.get():
            self.importaction=menu.addAction(_("Import"), self.import_will)
        menu.addAction(_("Broadcast"), self.broadcast)
        menu.addAction(_("Check"), self.check)
        menu.addAction(_("Invalidate"), self.invalidate_will)
        wizard=QPushButton(_("Setup Wizard"))
        wizard.clicked.connect(self.bal_window.init_wizard)
        display = QPushButton(_("Display"))
        display.clicked.connect(self.bal_window.preview_modal_dialog)
        widget = QWidget()
        hlayout = QHBoxLayout(widget)
        hlayout.addWidget(wizard)
        hlayout.addWidget(display)
        toolbar.insertWidget(2,widget)
        self.menu=menu
        self.toolbar=toolbar
        return toolbar
    def hide_replaced(self):
        self.bal_window.bal_plugin.hide_replaced()
        self.update()
    def hide_invalidated(self):
        f.bal_window.bal_plugin.hide_invalidated()
        self.update()
    def build_transactions(self):
        will = self.bal_window.prepare_will()
        if will:
            self.update_will(will)
    def export_json_file(self,path):
        write_json_file(path, self.will)
    def export_will(self):
        self.bal_window.export_will()
        self.update()
    def import_will(self):
        self.bal_window.import_will()
    def ask_password_and_sign_transactions(self):
        self.bal_window.ask_password_and_sign_transactions(callback=self.update) 
                    
    def broadcast(self):
        self.bal_window.broadcast_transactions()
        self.update()
    def check(self):
        self.bal_window.check_transactions(self.bal_window.willitems)
        self.update()
    def invalidate_will(self):
        self.bal_window.invalidate_will()
        self.update()
class PreviewDialog(BalDialog,MessageBoxMixin):
    def __init__(self, bal_window, will):
        self.parent = bal_window.window
        BalDialog.__init__(self,bal_window = bal_window,bal_plugin=bal_window.bal_plugin)
        self.bal_plugin = bal_window.bal_plugin
        self.gui_object = self.bal_plugin.gui_object
        self.config = self.bal_plugin.config
        self.bal_window = bal_window
        self.wallet = bal_window.window.wallet
        self.format_amount = bal_window.window.format_amount
        self.base_unit = bal_window.window.base_unit
        self.format_fiat_and_units = bal_window.window.format_fiat_and_units 
        self.fx = bal_window.window.fx 
        self.format_fee_rate = bal_window.window.format_fee_rate
        self.show_address = bal_window.window.show_address
        if not will:
            self.will = bal_window.willitems
        else:
            self.will = will
        self.setWindowTitle(_('Transactions Preview'))
        self.setMinimumSize(1000, 200)
        self.size_label = QLabel()
        self.transactions_list = PreviewList(self.bal_window,self.will)
        vbox = QVBoxLayout(self)
        vbox.addWidget(self.size_label)
        vbox.addWidget(self.transactions_list)
        buttonbox = QHBoxLayout()
        b = QPushButton(_('Sign'))
        b.clicked.connect(self.transactions_list.ask_password_and_sign_transactions)
        buttonbox.addWidget(b)
        b = QPushButton(_('Export Will'))
        b.clicked.connect(self.transactions_list.export_will)
        buttonbox.addWidget(b)
        b = QPushButton(_('Broadcast'))
        b.clicked.connect(self.transactions_list.broadcast)
        buttonbox.addWidget(b)
        b = QPushButton(_('Invalidate will'))
        b.clicked.connect(self.transactions_list.invalidate_will)
        buttonbox.addWidget(b)
        vbox.addLayout(buttonbox)
        self.update()
    
    def update_will(self,will):
        self.will.update(will)
        self.transactions_list.update_will(will)
        self.update()
    
    def update(self):
        self.transactions_list.update()
    def is_hidden(self):
        return self.isMinimized() or self.isHidden()
    def show_or_hide(self):
        if self.is_hidden():
            self.bring_to_top()
        else:
            self.hide()
    def bring_to_top(self):
        self.show()
        self.raise_()
    def closeEvent(self, event):
        event.accept()
    
def read_bal_QIcon(icon_basename: str=DEFAULT_ICON) -> QIcon:
    return QIcon(icon_path(icon_basename))
def read_bal_QPixmap(icon_basename: str=DEFAULT_ICON) -> QPixmap:
    return QPixmap(icon_path(icon_basename))
class WillDetailDialog(BalDialog):
    def __init__(self, bal_window):
        self.will = bal_window.willitems
        self.threshold = Util.parse_locktime_string(bal_window.will_settings['threshold'])
        self.bal_window = bal_window 
        Will.add_willtree(self.will)
        super().__init__(bal_window.window,bal_window.bal_plugin)
        self.config = bal_window.window.config
        self.wallet = bal_window.wallet
        self.format_amount = bal_window.window.format_amount
        self.base_unit = bal_window.window.base_unit
        self.format_fiat_and_units = bal_window.window.format_fiat_and_units
        self.fx = bal_window.window.fx
        self.format_fee_rate = bal_window.window.format_fee_rate
        self.decimal_point = bal_window.bal_plugin.config.get_decimal_point()
        self.base_unit_name = decimal_point_to_base_unit_name(self.decimal_point)
        self.setWindowTitle(_('Will Details'))
        self.setMinimumSize(670,700)
        self.vlayout= QVBoxLayout()
        w=QWidget()
        hlayout = QHBoxLayout(w)
        b = QPushButton(_('Sign'))
        b.clicked.connect(self.ask_password_and_sign_transactions)
        hlayout.addWidget(b)
 
        b = QPushButton(_('Broadcast')) 
        b.clicked.connect(self.broadcast_transactions) 
        hlayout.addWidget(b) 
        b = QPushButton(_('Export'))
        b.clicked.connect(self.export_will)
        hlayout.addWidget(b)
        b = QPushButton(_('Invalidate'))
        b.clicked.connect(bal_window.invalidate_will)
        hlayout.addWidget(b)
        self.vlayout.addWidget(w)
        self.paint_scroll_area()
        self.vlayout.addWidget(QLabel(_("Expiration date: ")+Util.locktime_to_str(self.threshold)))
        self.vlayout.addWidget(self.scrollbox)
        w=QWidget()
        hlayout = QHBoxLayout(w)
        hlayout.addWidget(QLabel(_("Valid Txs:")+ str(len(Will.only_valid_list(self.will)))))
        hlayout.addWidget(QLabel(_("Total Txs:")+ str(len(self.will))))
        self.vlayout.addWidget(w)
        self.setLayout(self.vlayout)
    def paint_scroll_area(self):
        self.scrollbox = QScrollArea()
        viewport = QWidget(self.scrollbox)
        self.willlayout = QVBoxLayout(viewport)
        self.detailsWidget = WillWidget(parent=self)
        self.willlayout.addWidget(self.detailsWidget)
        self.scrollbox.setWidget(viewport)
        viewport.setLayout(self.willlayout)
    def ask_password_and_sign_transactions(self):
        self.bal_window.ask_password_and_sign_transactions(callback=self.update)
        self.update()
    def broadcast_transactions(self):
        self.bal_window.broadcast_transactions()
        self.update()
    def export_will(self):
        self.bal_window.export_will()
    def toggle_replaced(self):
        self.bal_window.bal_plugin.hide_replaced()
        toggle = _("Hide")
        if self.bal_window.bal_plugin._hide_replaced:
            toggle = _("Unhide")
        self.toggle_replace_button.setText(f"{toggle} {_('replaced')}")
        self.update()
    def toggle_invalidated(self):
        self.bal_window.bal_plugin.hide_invalidated()
        toggle = _("Hide")
        if self.bal_window.bal_plugin._hide_invalidated:
            toggle = _("Unhide")
        self.toggle_invalidate_button.setText(_(f"{toggle} {_('invalidated')}"))
        self.update()
    def update(self):
        self.will = self.bal_window.willitems
        pos = self.vlayout.indexOf(self.scrollbox)
        self.vlayout.removeWidget(self.scrollbox)
        self.paint_scroll_area()
        self.vlayout.insertWidget(pos,self.scrollbox)
        super().update()
class WillWidget(QWidget):
    def __init__(self,father=None,parent = None):
        super().__init__()
        vlayout = QVBoxLayout()
        self.setLayout(vlayout)
        self.will = parent.bal_window.willitems
        self.parent = parent
        for w in self.will:
            if self.will[w].get_status('REPLACED') and self.parent.bal_window.bal_plugin._hide_replaced:
                continue
            if self.will[w].get_status('INVALIDATED') and self.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.parent.bal_window.show_transaction,txid=w))
                detaillayout.addWidget(willpushbutton)
                locktime = Util.locktime_to_str(self.will[w].tx.locktime)
                creation = Util.locktime_to_str(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:
                    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.parent.decimal_point)
                        detaillayout.addWidget(qlabel(heir,f"{decoded_amount} {self.parent.base_unit_name}"))
                if self.will[w].we:
                    detaillayout.addWidget(QLabel(""))
                    detaillayout.addWidget(QLabel(_("Willexecutor: