Files
bal-electrum-plugin/bal/gui/qt/lists.py
2026-06-20 09:48:56 -04:00

996 lines
35 KiB
Python

"""
bal.gui.qt.lists
================
Tree/list views (subclasses of Electrum's ``MyTreeView``) and their toolbars.
* HeirListWidget - editable list of heirs (address / amount / locktime).
* PreviewList - preview of the will transactions before signing.
* WillExecutorListWidget- list of will-executor servers.
* WillExecutorWidget - container combining the list with add/import buttons.
These views call back into the :class:`BalWindow` controller (passed at
construction) for all business actions, so the heavy logic stays in ``window``
and ``dialogs``.
"""
from .common import *
from .common import _, _logger # underscore names are not re-exported by "import *"
from .widgets import BalCheckBox, PercAmountEdit, WillSettingsWidget
from .dialogs import BalBuildWillDialog
class HeirListWidget(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 + 4000
key_role = ROLE_HEIR_KEY
def createEditor(self, parent, option, index):
return QLineEdit(parent)
def setEditorData(self, editor, index):
editor.setText(index.data())
def setModelData(self, editor, model, index):
model.setData(index, editor.text())
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.window.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 Exception:
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())
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:
prior_name = (
(edit_key,) + prior_name[: col - 1] + (text,) + prior_name[col:]
)
try:
self.bal_window.set_heir(prior_name)
except Exception:
pass
try:
self.bal_window.set_heir((edit_key,) + original)
except Exception:
self.update()
def delete_heirs(self, selected_keys):
self.bal_window.delete_heirs(selected_keys)
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()
# ok
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.delete_heirs(selected_keys))
menu.exec(self.viewport().mapToGlobal(position))
def update(self):
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 + self.Columns.NAME
)
items[self.Columns.ADDRESS].setData(
key, self.ROLE_HEIR_KEY + self.Columns.ADDRESS
)
items[self.Columns.AMOUNT].setData(
key, self.ROLE_HEIR_KEY + self.Columns.AMOUNT
)
row_count = self.model().rowCount()
self.model().insertRow(row_count, items)
if key == current_key:
idx = self.model().index(row_count, self.Columns.NAME)
set_current = QPersistentModelIndex(idx)
try:
self.will_settings_widget.on_locktime_change()
except Exception as e:
pass
self.set_current_idx(set_current)
# FIXME refresh loses sort order; so set "default" here:
self.filter()
def refresh_row(self, key, row):
# nothing to update here
pass
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 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())
newHeirButton = QPushButton(_("New Heir"))
newHeirButton.clicked.connect(self.bal_window.new_heir_dialog)
widget = QWidget(self)
layout = QHBoxLayout(widget)
self.will_settings_widget = WillSettingsWidget(self.bal_window, self)
layout.addWidget(self.will_settings_widget)
layout.addWidget(newHeirButton)
toolbar.insertWidget(2, widget)
return toolbar
def build_transactions(self):
# will = self.bal_window.prepare_will()
self.bal_window.prepare_will()
class PreviewList(MyTreeView, MessageBoxMixin):
class Columns(MyTreeView.BaseColumnsEnum):
LOCKTIME = enum.auto()
TXID = enum.auto()
WILLEXECUTOR = enum.auto()
STATUS = enum.auto()
SERVER = enum.auto()
headers = {
Columns.LOCKTIME: _("Locktime"),
Columns.TXID: _("Txid"),
Columns.WILLEXECUTOR: _("Will-Executor"),
Columns.STATUS: _("Status"),
Columns.SERVER: _("Server"),
}
ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 2000
key_role = ROLE_HEIR_KEY
def createEditor(self, parent, option, index):
return QLineEdit(parent)
def setEditorData(self, editor, index):
editor.setText(index.data())
def setModelData(self, editor, model, index):
model.setData(index, editor.text())
def __init__(self, bal_window: "BalWindow", parent, will):
super().__init__(
parent=parent,
main_window=bal_window.window,
stretch_column=self.Columns.TXID,
)
# self._bal_parent = parent
self.bal_window = bal_window
self.decimal_point = bal_window.window.get_decimal_point
if will is not None:
self.will = will
else:
self.will = bal_window.willitems
try:
self.setModel(QStandardItemModel(self))
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder)
except Exception as e:
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):
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),
)
if self.bal_window.bal_plugin.ENABLE_MULTIVERSE.get():
try:
self.importaction = self.menu.addAction(
_("Import"), self.import_will
)
except Exception:
pass
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 Exception:
pass
try:
del self.bal_window.will[key]
except Exception:
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 replace(self, set_current, current_key, txid, bal_tx):
if self.bal_window.bal_plugin._hide_replaced and bal_tx.get_status("REPLACED"):
return False
if self.bal_window.bal_plugin._hide_invalidated and bal_tx.get_status(
"INVALIDATED"
):
return False
if not isinstance(bal_tx, WillItem):
bal_tx = WillItem(bal_tx)
tx = bal_tx.tx
labels = [""] * len(self.Columns)
labels[self.Columns.LOCKTIME] = str(BalTimestamp(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
# Dedicated, always-readable label describing whether the inheritance
# transaction is actually stored on the will-executor servers.
labels[self.Columns.SERVER] = server_status_text(bal_tx)
items = []
for e in labels:
if isinstance(e, list):
try:
items.append(QStandardItem(*e))
except Exception as e:
pass
else:
items.append(QStandardItem(str(e)))
items[-1].setBackground(QColor(status_color(bal_tx)))
# Tooltip on the Server column: shows the will-executor URL (if any)
# plus the current server state, so the user can always inspect details.
try:
items[self.Columns.SERVER].setToolTip(server_status_tooltip(bal_tx))
except Exception as tip_err:
_logger.debug(f"server tooltip error: {tip_err}")
row_count = self.model().rowCount()
self.model().insertRow(row_count, items)
if txid == current_key:
idx = self.model().index(row_count, self.Columns.TXID)
set_current = QPersistentModelIndex(idx)
self.set_current_idx(set_current)
return set_current
def update(self):
try:
self.menu.removeAction(self.importaction)
except Exception:
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():
tmp = self.replace(set_current, current_key, txid, bal_tx)
if tmp:
set_current = tmp
self.sortByColumn(self.Columns.LOCKTIME, Qt.SortOrder.AscendingOrder)
self.setSortingEnabled(True)
try:
self.will_settings_widget.on_locktime_change()
except Exception as _e:
pass
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_window.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)
# The Wizard is the main entry point to create an inheritance, so make
# it stand out: show a bold label next to a slightly larger icon (the
# plain icon-only button was too easy to overlook).
wizard = QPushButton(" " + _("Create your will"))
wizard.setIcon(
read_QIcon_from_bytes(
self.bal_window.bal_plugin.read_file("icons/wizard.png")
)
)
wizard.setIconSize(QSize(28, 28))
wizard.setMinimumHeight(40)
wizard.setStyleSheet("QPushButton{font-weight:bold;}")
# Tooltip so the button is self-explanatory when hovered.
wizard.setToolTip(_("Wizard - Build your will"))
wizard.clicked.connect(self.bal_window.init_wizard)
# display = QPushButton(_("Display"))
# display.clicked.connect(self.bal_window.preview_modal_dialog)
refresh = QPushButton()
refresh.setIcon(
read_QIcon_from_bytes(
self.bal_window.bal_plugin.read_file("icons/reload.png")
)
)
# Tooltip so the icon is self-explanatory when hovered.
refresh.setToolTip(_("Check"))
refresh.clicked.connect(self.check)
widget = QWidget(self)
hlayout = QHBoxLayout(widget)
hlayout.setContentsMargins(0, 0, 0, 0)
self.will_settings_widget = WillSettingsWidget(self.bal_window, self)
# Toolbar order (left -> right):
# Wizard | Delivery time | Check Alive | Calendar | Check (refresh)
# The Wizard button goes first (leftmost); the settings widget already
# lays out delivery/check-alive/calendar in that order internally.
hlayout.addWidget(wizard)
hlayout.addWidget(self.will_settings_widget)
hlayout.addWidget(refresh)
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):
self.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):
close_window = BalBuildWillDialog(self.bal_window)
close_window.build_will_task()
will = {}
for wid, w in self.bal_window.willitems.items():
# Query the will-executor server for every valid will that HAS a
# will-executor assigned and is not yet CHECKED. Previously only
# transactions already marked PUSHED were checked, so a will that
# had actually been sent in the past but whose saved status still
# read "New" (not PUSHED) was skipped and the Check button reported
# "nothing to do". Will.needs_server_check now also includes such
# non-PUSHED wills, so the server can confirm the transaction is
# present and correct the status (see set_check_willexecutor).
if Will.needs_server_check(w):
will[wid] = w
if will:
self.bal_window.check_transactions(will)
self.update()
def invalidate_will(self):
self.bal_window.invalidate_will()
self.update()
# class PreviewDialog(BalDialog, MessageBoxMixin):
# def __init__(self, bal_window, will):
# self._bal_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, self.will)
#
# try:
# self.bal_window.init_class_variables()
# except Exception as e:
# _logger.error(f"PreviewDialog Exception: {e}")
# self.check_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()
class WillExecutorListWidget(MyTreeView):
class Columns(MyTreeView.BaseColumnsEnum):
SELECTED = enum.auto()
URL = enum.auto()
STATUS = enum.auto()
BASE_FEE = enum.auto()
INFO = enum.auto()
ADDRESS = enum.auto()
headers = {
Columns.SELECTED: _(""),
Columns.URL: _("Url"),
Columns.STATUS: _("S"),
Columns.BASE_FEE: _("Base fee"),
Columns.INFO: _("Info"),
Columns.ADDRESS: _("Default Address"),
}
filter_columns = [Columns.URL]
ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 3000
ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 3001
key_role = ROLE_HEIR_KEY
def __init__(self, parent: "WillExecutorWidget"):
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._bal_parent = parent
try:
self.setModel(QStandardItemModel(self))
self.sortByColumn(self.Columns.SELECTED, Qt.SortOrder.AscendingOrder)
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
except Exception:
pass
self.setSortingEnabled(True)
self.std_model = self.model()
self.config = parent.bal_plugin.config
self.get_decimal_point = parent.bal_plugin.get_decimal_point
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._bal_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._bal_parent.willexecutors_list[k]
self._bal_parent.update_willexecutors(wout)
self._bal_parent.save_willexecutors()
self.update()
def get_edit_key_from_coordinate(self, row, col):
role = self.ROLE_HEIR_KEY + col
a = self.get_role_data_from_coordinate(row, col, role=role)
return a
def delete(self, selected_keys):
for key in selected_keys:
del self._bal_parent.willexecutors_list[key]
self._bal_parent.save_willexecutors()
self.update()
def select(self, selected_keys):
for wid, w in self._bal_parent.willexecutors_list.items():
if wid in selected_keys:
w["selected"] = True
self._bal_parent.save_willexecutors()
self.update()
def deselect(self, selected_keys):
for wid, w in self._bal_parent.willexecutors_list.items():
if wid in selected_keys:
w["selected"] = False
self._bal_parent.save_willexecutors()
self.update()
def on_edited(self, idx, edit_key, *, text):
# prior_name = self._bal_parent.willexecutors_list[edit_key]
col = idx.column()
try:
if col == self.Columns.URL:
self._bal_parent.willexecutors_list[text] = self._bal_parent.willexecutors_list[
edit_key
]
del self._bal_parent.willexecutors_list[edit_key]
if col == self.Columns.BASE_FEE:
self._bal_parent.willexecutors_list[edit_key]["base_fee"] = (
Util.encode_amount(text, self.get_decimal_point())
)
if col == self.Columns.ADDRESS:
self._bal_parent.willexecutors_list[edit_key]["address"] = text
if col == self.Columns.INFO:
self._bal_parent.willexecutors_list[edit_key]["info"] = text
self._bal_parent.save_willexecutors()
self.update()
except Exception:
pass
def update(self):
if self._bal_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._bal_parent.willexecutors_list.items():
labels = [""] * len(self.Columns)
labels[self.Columns.URL] = url
if Willexecutors.is_selected(value):
labels[self.Columns.SELECTED] = [
read_QIcon_from_bytes(
self._bal_parent.bal_plugin.read_file("icons/confirmed.png")
),
"",
]
else:
labels[self.Columns.SELECTED] = ""
labels[self.Columns.BASE_FEE] = Util.decode_amount(
value.get("base_fee", 0), self.get_decimal_point()
)
if str(value.get("status", 0)) == "200":
labels[self.Columns.STATUS] = [
read_QIcon_from_bytes(
self._bal_parent.bal_plugin.read_file(
"icons/status_connected.png"
)
),
"",
]
else:
labels[self.Columns.STATUS] = [
read_QIcon_from_bytes(
self._bal_parent.bal_plugin.read_file("icons/unconfirmed.png")
),
"",
]
labels[self.Columns.ADDRESS] = str(value.get("address", ""))
labels[self.Columns.INFO] = str(value.get("info", ""))
items = []
for e in labels:
if isinstance(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 + self.Columns.URL
)
items[self.Columns.BASE_FEE].setData(
url, self.ROLE_HEIR_KEY + self.Columns.BASE_FEE
)
items[self.Columns.INFO].setData(
url, self.ROLE_HEIR_KEY + self.Columns.INFO
)
items[self.Columns.ADDRESS].setData(
url, self.ROLE_HEIR_KEY + self.Columns.ADDRESS
)
row_count = self.model().rowCount()
self.model().insertRow(row_count, items)
if url == current_key:
idx = self.model().index(row_count, self.Columns.URL)
set_current = QPersistentModelIndex(idx)
self.set_current_idx(set_current)
self.filter()
except Exception as e:
_logger.error(f"error updating willexcutor {e}")
raise 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._bal_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.will_executor_list_widget = WillExecutorListWidget(self)
vbox = QVBoxLayout(self)
vbox.addWidget(self.size_label)
widget = QWidget()
hbox = QHBoxLayout(widget)
hbox.addWidget(QLabel(_("Add transactions without willexecutor")))
heir_no_willexecutor = BalCheckBox(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.will_executor_list_widget)
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.will_executor_list_widget.update()
def add(self):
self.willexecutors_list["http://localhost:8080"] = {
"info": "New Will Executor",
"base_fee": 0,
"status": "-1",
}
self.will_executor_list_widget.update()
def download_list(self, wes=None):
# Both this button and the wizard go through the same code path on
# BalWindow, which shows a "Downloading..." dialog (non-blocking GUI),
# tries the configured + fallback servers, logs the technical details
# and shows a simple message on failure.
def on_success(result):
self.willexecutors_list.update(result)
self.will_executor_list_widget.update()
Willexecutors.save(self.bal_window.bal_plugin, self.willexecutors_list)
self.update()
self.bal_window.download_list(self.bal_window.willexecutors, on_success)
def export_file(self, path):
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"),
self.import_json_file,
self.willexecutors_list.update,
)
def update_willexecutors(self, wes=None):
if not wes:
wes = self.willexecutors_list
self.bal_window.ping_willexecutors(wes, self.save_willexecutors)
def import_json_file(self, path):
data = read_json_file(path)
data = self._validate(data)
self.willexecutors_list.update(data)
self.will_executor_list_widget.update()
# TODO validate willexecutor json import file
def _validate(self, data):
return data
def save_willexecutors(self, wes=None):
if not wes:
wes = self.willexecutors_list
self.willexecutors_list.update(wes)
self.will_executor_list_widget.update()
Willexecutors.save(self.bal_window.bal_plugin, self.willexecutors_list)