forked from bitcoinafterlife/bal-electrum-plugin
BAL - Bitcoin After Life Electrum plugin (v0.2.8)
Behavior-preserving refactor of the original BAL plugin with clean separation
of business logic from the PyQt GUI.
Layout:
bal/core/ GUI-free logic (util, plugin_base, heirs, will, willexecutors)
bal/gui/qt/ PyQt6 presentation (theme, common, widgets, calendar, dialogs,
lists, window, plugin)
bal/qt.py Qt entry-point shim (works as internal and external zip plugin)
bal/manifest.json standard-conforming metadata
Tooling:
build_zip.py deterministic, zipimport-friendly archive builder
tests/smoke_test.py imports + behavior regression test
tests/external_zip_test.py reproduces Electrum's external-zip loading
Targets Electrum 4.7.2 + PyQt6. Logic kept byte-identical where possible.
This commit is contained in:
273
bal/gui/qt/plugin.py
Normal file
273
bal/gui/qt/plugin.py
Normal file
@@ -0,0 +1,273 @@
|
||||
"""
|
||||
bal.gui.qt.plugin
|
||||
=================
|
||||
|
||||
The Qt entry point of the plugin.
|
||||
|
||||
:class:`Plugin` subclasses :class:`bal.core.plugin_base.BalPlugin` and adds the
|
||||
Electrum ``@hook`` methods that wire the plugin into the Qt GUI (status-bar
|
||||
button, Tools menu, wallet load/close, settings dialog). Electrum instantiates
|
||||
this class because the package ``manifest.json`` declares ``available_for:
|
||||
["qt"]`` and the loader imports ``qt.py`` (a thin shim re-exporting this class).
|
||||
|
||||
One :class:`bal.gui.qt.window.BalWindow` is created per top-level wallet window
|
||||
and cached in ``self.bal_windows``.
|
||||
"""
|
||||
|
||||
from .common import *
|
||||
from .common import _, _logger # underscore names are not re-exported by "import *"
|
||||
from .widgets import BalCheckBox, BalLineEdit, BalTextEdit
|
||||
from .window import BalWindow
|
||||
from .dialogs import BalDialog
|
||||
|
||||
|
||||
class Plugin(BalPlugin):
|
||||
def __init__(self, parent, config, name):
|
||||
_logger.info("INIT BALPLUGIN")
|
||||
BalPlugin.__init__(self, parent, config, name)
|
||||
self.bal_windows = {}
|
||||
|
||||
@hook
|
||||
def init_qt(self, gui_object):
|
||||
_logger.info("HOOK bal 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
|
||||
top_level_window=window.top_level_window()
|
||||
w = BalWindow(self, top_level_window)
|
||||
self.bal_windows[top_level_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:
|
||||
_logger.error(
|
||||
("init_qt except:", menu_child.text())
|
||||
)
|
||||
raise e
|
||||
|
||||
except Exception as e:
|
||||
_logger.error("Error loading plugini {}".format(e))
|
||||
raise e
|
||||
|
||||
@hook
|
||||
def create_status_bar(self, sb):
|
||||
_logger.info("HOOK create status bar")
|
||||
b = StatusBarButton(
|
||||
read_QIcon_from_bytes(self.read_file("icons/bal32x32.png")),
|
||||
"Bal " + _("Bitcoin After Life"),
|
||||
partial(self.settings_dialog, sb),
|
||||
sb.height(),
|
||||
)
|
||||
sb.addPermanentWidget(b)
|
||||
|
||||
@hook
|
||||
def init_menubar(self, window):
|
||||
_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):
|
||||
_logger.debug("HOOK load wallet")
|
||||
w = self.get_window(main_window)
|
||||
# havetoupdate = Util.fix_will_settings_tx_fees(wallet.db)
|
||||
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):
|
||||
_logger.debug("HOOK close wallet")
|
||||
for _winid, win in self.bal_windows.items():
|
||||
if win.wallet == wallet:
|
||||
win.on_close()
|
||||
|
||||
@hook
|
||||
def init_keystore(self):
|
||||
_logger.debug("init keystore")
|
||||
|
||||
@hook
|
||||
def daemon_wallet_loaded(self, boh, wallet):
|
||||
_logger.debug("daemon wallet loaded")
|
||||
|
||||
def get_window(self, window):
|
||||
window=window.top_level_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=None, wallet=None):
|
||||
|
||||
d = BalDialog(window, self, self.get_window_title("Settings"))
|
||||
d.setMinimumSize(100, 200)
|
||||
qicon = read_QPixmap_from_bytes(self.read_file("icons/bal16x16.png"))
|
||||
lbl_logo = QLabel()
|
||||
lbl_logo.setPixmap(qicon)
|
||||
|
||||
# heir_ping_willexecutors = BalCheckBox(self.PING_WILLEXECUTORS)
|
||||
# heir_ask_ping_willexecutors = BalCheckBox(self.ASK_PING_WILLEXECUTORS)
|
||||
# heir_no_willexecutor = BalCheckBox(self.NO_WILLEXECUTOR)
|
||||
|
||||
def on_multiverse_change():
|
||||
self.update_all()
|
||||
|
||||
# heir_enable_multiverse = BalCheckBox(self.ENABLE_MULTIVERSE,on_multiverse_change)
|
||||
|
||||
heir_hide_replaced = BalCheckBox(self.HIDE_REPLACED, on_multiverse_change)
|
||||
|
||||
heir_hide_invalidated = BalCheckBox(self.HIDE_INVALIDATED, on_multiverse_change)
|
||||
heir_repush = QPushButton("Rebroadcast transactions")
|
||||
heir_repush.clicked.connect(partial(self.broadcast_transactions, True))
|
||||
bal_mode = QComboBox()
|
||||
options = ["Easy", "Advanced", "Experimental"]
|
||||
bal_mode.addItems(options)
|
||||
|
||||
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,
|
||||
"Calendar App",
|
||||
BalLineEdit(self.CALENDAR_APP),
|
||||
3,
|
||||
"Default app used to open calendar",
|
||||
)
|
||||
add_widget(
|
||||
grid,
|
||||
"Event summary",
|
||||
BalLineEdit(self.EVENT_SUMMARY),
|
||||
4,
|
||||
(
|
||||
"Default message to be used in event summary\n"
|
||||
"Variables:\n"
|
||||
" $wallet_name: name of wallet\n"
|
||||
" $heirs_complete: list of heirs name,address,amount\n"
|
||||
#" $will_details_complete: will details(id transaction, mining fees, willexecutor, willexecutor fees, locktime)\n"
|
||||
)
|
||||
)
|
||||
add_widget(
|
||||
grid,
|
||||
"Event sescription",
|
||||
BalTextEdit(self.EVENT_DESCRIPTION),
|
||||
5,
|
||||
(
|
||||
"Default message to be used in event description\n"
|
||||
"Variables:\n"
|
||||
" $wallet_name: name of wallet\n"
|
||||
" $heirs_complete: list of heirs name,address,amount\n"
|
||||
#" $will_details_complete: will details(id transaction, mining fees, willexecutor, willexecutor fees, locktime)\n"
|
||||
)
|
||||
)
|
||||
#add_widget(grid, "Bal Mode", bal_mode, 4, "choose bal mode")
|
||||
|
||||
# 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 Exception:
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user