3176 lines
		
	
	
		
			116 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			3176 lines
		
	
	
		
			116 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| '''
 | |
| 
 | |
| 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('<font color="#ff0000">'+_("In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts"+"</font>"))
 | |
| 
 | |
|         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 = "<b>"+_(str(title)) + f":</b>\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("<b>Heirs:</b>"))
 | |
|                 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(_("<b>Willexecutor:</b:")))
 | |
|                     decoded_amount = Util.decode_amount(self.will[w].we['base_fee'],self.parent.decimal_point)
 | |
| 
 | |
|                     detaillayout.addWidget(qlabel(self.will[w].we['url'],f"{decoded_amount} {self.parent.base_unit_name}"))
 | |
|                 detaillayout.addStretch()
 | |
|                 pal = QPalette()
 | |
|                 pal.setColor(QPalette.ColorRole.Window, QColor(self.will[w].get_color()))
 | |
|                 detailw.setAutoFillBackground(True)
 | |
|                 detailw.setPalette(pal)
 | |
| 
 | |
|                 hlayout.addWidget(detailw)
 | |
|                 hlayout.addWidget(WillWidget(w,parent = parent))
 | |
| 
 | |
| class WillExecutorList(MyTreeView):
 | |
|     class Columns(MyTreeView.BaseColumnsEnum):
 | |
|         SELECTED = enum.auto()
 | |
|         URL = enum.auto()
 | |
|         BASE_FEE = enum.auto()
 | |
|         INFO = enum.auto()
 | |
|         ADDRESS = enum.auto()
 | |
|         STATUS = enum.auto()
 | |
| 
 | |
|     headers = {
 | |
|         Columns.SELECTED:_(''),
 | |
|         Columns.URL: _('Url'),
 | |
|         Columns.BASE_FEE: _('Base fee'),
 | |
|         Columns.INFO:_('Info'),
 | |
|         Columns.ADDRESS:_('Default Address'),
 | |
|         Columns.STATUS: _('S'),
 | |
|     }
 | |
| 
 | |
|     ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 2000
 | |
|     key_role = ROLE_HEIR_KEY
 | |
| 
 | |
|     def __init__(self, parent: 'WillExecutorDialog'):
 | |
|         super().__init__(
 | |
|             parent=parent,
 | |
|             stretch_column=self.Columns.ADDRESS,
 | |
|             editable_columns=[self.Columns.URL,self.Columns.BASE_FEE,self.Columns.ADDRESS,self.Columns.INFO],
 | |
| 
 | |
|         )
 | |
|         self.parent = parent
 | |
|         self.setModel(QStandardItemModel(self))
 | |
|         self.setSortingEnabled(True)
 | |
|         self.std_model = self.model()
 | |
|         self.config =parent.bal_plugin.config
 | |
|            
 | |
| 
 | |
|         self.update()
 | |
| 
 | |
| 
 | |
| 
 | |
|     def create_menu(self, position):
 | |
|         menu = QMenu()
 | |
|         idx = self.indexAt(position)
 | |
|         column = idx.column() or self.Columns.URL
 | |
|         selected_keys = []
 | |
|         for s_idx in self.selected_in_column(self.Columns.URL):
 | |
|             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))
 | |
|             if Willexecutors.is_selected(self.parent.willexecutors_list[sel_key]):
 | |
|                 menu.addAction(_("deselect").format(column_title), lambda: self.deselect(selected_keys))
 | |
|             else:
 | |
|                 menu.addAction(_("select").format(column_title), lambda: self.select(selected_keys))
 | |
|             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(_("Ping").format(column_title), lambda: self.ping_willexecutors(selected_keys))
 | |
|             menu.addSeparator()
 | |
|             menu.addAction(_("delete").format(column_title), lambda: self.delete(selected_keys))
 | |
| 
 | |
|         menu.exec(self.viewport().mapToGlobal(position))
 | |
| 
 | |
|     def ping_willexecutors(self,selected_keys):
 | |
|         wout={}
 | |
|         for k in selected_keys:
 | |
|             wout[k]=self.parent.willexecutors_list[k]
 | |
|         self.parent.update_willexecutors(wout)
 | |
| 
 | |
|         self.update()
 | |
| 
 | |
| 
 | |
|     def get_edit_key_from_coordinate(self, row, col):
 | |
|         a= self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY+col)
 | |
|         return a
 | |
| 
 | |
|     def delete(self,selected_keys):
 | |
|         for key in selected_keys:
 | |
|             del self.parent.willexecutors_list[key]
 | |
|         self.update()
 | |
| 
 | |
|     def select(self,selected_keys):
 | |
|         for wid,w in self.parent.willexecutors_list.items():
 | |
|             if wid in selected_keys:
 | |
|                 w['selected']=True
 | |
|         self.update()
 | |
| 
 | |
|     def deselect(self,selected_keys):
 | |
|         for wid,w in self.parent.willexecutors_list.items():
 | |
|             if wid in selected_keys:
 | |
|                 w['selected']=False
 | |
|         self.update()
 | |
| 
 | |
|     def on_edited(self, idx, edit_key, *, text):
 | |
|         prior_name = self.parent.willexecutors_list[edit_key]
 | |
|         col = idx.column()
 | |
|         try:
 | |
|             if col == self.Columns.URL:
 | |
|                 self.parent.willexecutors_list[text]=self.parent.willexecutors_list[edit_key]
 | |
|                 del self.parent.willexecutors_list[edit_key]
 | |
|             if col == self.Columns.BASE_FEE:
 | |
|                 self.parent.willexecutors_list[edit_key]["base_fee"] = Util.encode_amount(text,self.config.get_decimal_point())
 | |
|             if col == self.Columns.ADDRESS:
 | |
|                 self.parent.willexecutors_list[edit_key]["address"] = text
 | |
|             if col == self.Columns.INFO:
 | |
|                 self.parent.willexecutors_list[edit_key]["info"] = text
 | |
|             self.update()
 | |
|         except Exception as e:
 | |
|             pass
 | |
| 
 | |
|     def update(self):
 | |
| 
 | |
|         if self.parent.willexecutors_list is None:
 | |
|             return
 | |
|         try: 
 | |
|             current_key = self.get_role_data_for_current_item(col=self.Columns.URL, role=self.ROLE_HEIR_KEY)
 | |
|             self.model().clear()
 | |
|             self.update_headers(self.__class__.headers)
 | |
| 
 | |
|             set_current = None
 | |
| 
 | |
|             for url, value in self.parent.willexecutors_list.items():
 | |
|                 labels = [""] * len(self.Columns)
 | |
|                 labels[self.Columns.URL] = url 
 | |
|                 if Willexecutors.is_selected(value):
 | |
|                     labels[self.Columns.SELECTED] = [read_QIcon('confirmed.png'),'']
 | |
|                 else:
 | |
|                     labels[self.Columns.SELECTED] = ''
 | |
|                 labels[self.Columns.BASE_FEE] = Util.decode_amount(value.get('base_fee',0),self.config.get_decimal_point())
 | |
|                 if str(value.get('status',0)) == "200":
 | |
|                     labels[self.Columns.STATUS] = [read_QIcon('status_connected.png'),'']
 | |
|                 else:
 | |
|                     labels[self.Columns.STATUS] = [read_QIcon('unconfirmed.png'),'']
 | |
|                 labels[self.Columns.ADDRESS] = str(value.get('address',''))
 | |
|                 labels[self.Columns.INFO] = str(value.get('info',''))
 | |
|                 
 | |
|                 items=[]
 | |
|                 for e in labels:
 | |
|                     if type(e)== list:
 | |
|                         try:
 | |
|                             items.append(QStandardItem(*e))
 | |
|                         except Exception as e:
 | |
|                             pass
 | |
|                     else:
 | |
|                         items.append(QStandardItem(e))
 | |
|                 items[self.Columns.SELECTED].setEditable(False)
 | |
|                 items[self.Columns.URL].setEditable(True)
 | |
|                 items[self.Columns.ADDRESS].setEditable(True)
 | |
|                 items[self.Columns.INFO].setEditable(True)
 | |
|                 items[self.Columns.BASE_FEE].setEditable(True)
 | |
|                 items[self.Columns.STATUS].setEditable(False)
 | |
| 
 | |
|                 items[self.Columns.URL].setData(url, self.ROLE_HEIR_KEY+1)
 | |
|                 items[self.Columns.BASE_FEE].setData(url, self.ROLE_HEIR_KEY+2)
 | |
|                 items[self.Columns.INFO].setData(url, self.ROLE_HEIR_KEY+3)
 | |
|                 items[self.Columns.ADDRESS].setData(url, self.ROLE_HEIR_KEY+4)
 | |
| 
 | |
| 
 | |
|                 self.model().insertRow(self.model().rowCount(), items)
 | |
|                 if url == current_key:
 | |
|                     idx = self.model().index(row_count, self.Columns.NAME)
 | |
|                     set_current = QPersistentModelIndex(idx)
 | |
|                 self.set_current_idx(set_current)
 | |
|             
 | |
|             self.parent.save_willexecutors()
 | |
| 
 | |
|         except Exception as e:
 | |
|             _logger.error(e)
 | |
| 
 | |
| class WillExecutorWidget(QWidget,MessageBoxMixin):
 | |
|     def __init__(self,parent,bal_window,willexecutors=None):
 | |
|         self.bal_window=bal_window
 | |
|         self.bal_plugin=bal_window.bal_plugin
 | |
|         self.parent=parent
 | |
|         MessageBoxMixin.__init__(self)
 | |
|         QWidget.__init__(self,parent)
 | |
|         if willexecutors:
 | |
|             self.willexecutors_list=willexecutors
 | |
|         else:
 | |
|             self.willexecutors_list = Willexecutors.get_willexecutors(self.bal_plugin)
 | |
| 
 | |
|         self.size_label = QLabel()
 | |
|         self.willexecutor_list = WillExecutorList(self)
 | |
| 
 | |
|         vbox = QVBoxLayout(self)
 | |
|         vbox.addWidget(self.size_label)
 | |
| 
 | |
|         widget = QWidget()
 | |
|         hbox=QHBoxLayout(widget)
 | |
|         hbox.addWidget(QLabel(_("Add transactions without willexecutor")))
 | |
|         heir_no_willexecutor = bal_checkbox(self.bal_plugin.NO_WILLEXECUTOR)
 | |
|         hbox.addWidget(heir_no_willexecutor)
 | |
|         spacer_widget = QWidget()
 | |
|         spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
 | |
|         hbox.addWidget(spacer_widget)
 | |
|         vbox.addWidget(widget)
 | |
|         
 | |
|         vbox.addWidget(self.willexecutor_list)
 | |
|         buttonbox = QHBoxLayout()
 | |
| 
 | |
|         b = QPushButton(_('Add'))
 | |
|         b.clicked.connect(self.add)
 | |
|         buttonbox.addWidget(b)
 | |
| 
 | |
| 
 | |
|         b = QPushButton(_('Download List'))
 | |
|         b.clicked.connect(self.download_list)
 | |
|         buttonbox.addWidget(b)
 | |
| 
 | |
|         b = QPushButton(_('Import'))
 | |
|         b.clicked.connect(self.import_file)
 | |
|         buttonbox.addWidget(b)
 | |
| 
 | |
|         b = QPushButton(_('Export'))
 | |
|         b.clicked.connect(self.export_file)
 | |
|         buttonbox.addWidget(b)
 | |
| 
 | |
|         b = QPushButton(_('Ping All'))
 | |
|         b.clicked.connect(self.update_willexecutors)
 | |
|         buttonbox.addWidget(b)
 | |
| 
 | |
|         
 | |
|         vbox.addLayout(buttonbox)
 | |
|         self.willexecutor_list.update()
 | |
|     def add(self):
 | |
|         self.willexecutors_list["http://localhost:8080"]={"info":"New Will Executor","base_fee":0,"status":"-1"}
 | |
|         self.willexecutor_list.update()     
 | |
|     def download_list(self):
 | |
|         self.willexecutors_list.update(Willexecutors.download_list(self.bal_plugin) )
 | |
|         self.willexecutor_list.update()
 | |
|     def export_file(self, path):
 | |
|         Util.export_meta_gui(self.bal_window.window, _('willexecutors.json'), self.export_json_file)
 | |
| 
 | |
|     def export_json_file(self,path):
 | |
|         write_json_file(path, self.willexecutors_list)
 | |
| 
 | |
|     def import_file(self):
 | |
|         import_meta_gui(self.bal_window.window, _('willexecutors.json'), self.import_json_file, self.willexecutors_list.update)
 | |
| 
 | |
|     def update_willexecutors(self,wes=None):
 | |
|         if not wes:
 | |
|             self.willexecutors_list = Willexecutors.get_willexecutors(self.bal_plugin, update = True, bal_window = self.bal_window,force=True,task=self)
 | |
|         else:
 | |
|             self.bal_window.ping_willexecutors(wes,self.parent)
 | |
|             self.willexecutors_list.update(wes)
 | |
|             self.willexecutor_list.update()
 | |
| 
 | |
|         
 | |
|     def import_json_file(self, path):
 | |
|         data = read_json_file(path)
 | |
|         data = self._validate(data)
 | |
|         self.willexecutors_list.update(data)
 | |
|         self.willexecutor_list.update()
 | |
| 
 | |
|     #TODO validate willexecutor json import file
 | |
|     def _validate(self,data):
 | |
|         return data
 | |
| 
 | |
|     def save_willexecutors(self):
 | |
|         Willexecutors.save(self.bal_window.bal_plugin, self.willexecutors_list)
 | |
| 
 | |
| class WillExecutorDialog(BalDialog,MessageBoxMixin):
 | |
|     def __init__(self, bal_window,parent=None):
 | |
|         if not parent:
 | |
|             parent=bal_window.window
 | |
|         BalDialog.__init__(self,parent,bal_window.bal_plugin)
 | |
|         self.bal_plugin = bal_window.bal_plugin
 | |
|         self.config = self.bal_plugin.config
 | |
|         self.bal_window = bal_window
 | |
|         self.willexecutors_list = Willexecutors.get_willexecutors(self.bal_plugin)
 | |
| 
 | |
|         self.setWindowTitle(_('Will-Executor Service List'))
 | |
|         self.setMinimumSize(1000, 200)
 | |
| 
 | |
|         vbox = QVBoxLayout(self)
 | |
|         self.willexecutor_list= WillExecutorWidget(self,self.bal_window,self.willexecutors_list)
 | |
|         vbox.addWidget(self.willexecutor_list)
 | |
| 
 | |
|     
 | |
| 
 | |
|     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()
 | |
| 
 | |
|     
 |