* fix(gui): correct window z-order and lifecycle bugs (B1-B10)
Sintomi risolti:
- S1: le finestre del plugin sparivano dietro Electrum
- S2: alcuni meccanismi funzionavano solo dopo chiusura+pulizia di Electrum
La logica di business resta BYTE-IDENTICA (nessuna modifica a bal/core/*):
cambiano solo parent, modalità, ciclo di vita, cleanup e presentazione.
Nuovo modulo bal/gui/qt/window_utils.py con helper centralizzati:
- top_level_of, bring_to_front, stop_thread, show_modal, show_on_top
Fix per bug:
- B1: self.parent -> self._bal_parent (dialogs/lists/widgets); parent = top_level_of(parent)
- B2/B9: .show() -> show_on_top()/show_modal()/bring_to_front()
- B3: init a caldo _setup_window() replica load_wallet (niente 'restart Electrum')
- B4: chiave finestra stabile _window_key() = id(window)
- B5: on_close riscritto (no except:pass, log per-step, reset stato)
- B6: BalBlockingWaitingDialog ripristina processEvents()
- B7/B8: closeEvent/hideEvent -> stop_thread() (stop+wait) + super()
- B10: uso di window.tools_menu (no ricerca per titolo localizzato '&Tools')
Test: smoke + gui_fixes (regressione B1-B10) + external_zip tutti verdi.
Doc aggiornata: DIAGNOSI_GUI.md marca B1-B10 come FIXED.
* fix(gui): do not kill task thread on dialog close (download list regression)
The B7/B8 change added stop_thread() to BalDialog.closeEvent/hideEvent.
But Electrum's TaskThread.on_done runs cb_done (often self.accept, which
closes the waiting dialog) BEFORE cb_result (on_success, which updates the
will-executor list). Stopping/joining the thread inside closeEvent therefore
tore the thread down before on_success ran, silently dropping the downloaded
will-executor list ('Download List' appeared to do nothing).
Restore the original safe behavior: the base BalDialog no longer stops the
thread on close/hide (matching the original plugin, which deliberately left
this commented out). Long-lived dialogs that own a thread still stop it
explicitly in their own handlers.
Adds a regression test asserting BalDialog.closeEvent/hideEvent never call
stop_thread.
* fix(gui): restore modal exec for waiting dialog + surface download failures
Two changes to fix 'Download List' doing nothing:
1) BalWaitingDialog.exe() now keeps the original application-modal exec()
(only adding raise/activate for visibility). The earlier switch to
window-modal could interfere with how the TaskThread result (on_success,
which populates the will-executor list) is delivered via a queued signal
while the modal loop is spinning.
2) BalWindow.download_list now logs how many entries were received and, when
the result is empty (the core download_list swallows errors and returns {}),
shows a warning to the user instead of failing silently. This makes any
future network/parse failure visible in the Electrum log and to the user.
Business logic in bal/core/* is unchanged.
* fix(gui): show the real download error reason in the warning popup
When 'Download List' fails, the core download_list returns {} and the cause
was only visible in the Electrum log (which is hard to capture). The GUI now
re-issues the same raw request when the result is empty and shows the actual
exception/reason in the warning popup (e.g. SSL error, timeout, empty server
response). Pure GUI-side diagnostics; bal/core/* logic unchanged.
* fix(gui): download will-executor list synchronously like the original
ROOT CAUSE: the original plugin's 'Download List' button called
Willexecutors.download_list() DIRECTLY on the GUI thread
(qt.py: WillExecutorWidget.download_list). The refactor instead routed the
button through BalWaitingDialog + TaskThread. Electrum's
Network.send_http_on_proxy behaves differently depending on the calling
thread, and on the user's setup the TaskThread path timed out ('No response
from the server'), while the original direct call worked fine.
FIX: WillExecutorWidget.download_list now performs the same direct,
synchronous download as the original, updates and saves the list, and shows a
warning only on genuine failure. The wizard download path (which the original
also ran via TaskThread) is left unchanged.
Compared against upstream original source (kaibot/bal-electrum-plugin):
send_request, handle_response, download_list core logic are byte-identical;
only the GUI call site is restored to the original behaviour.
* diag(gui): detailed download diagnostics + hardcoded-URL fallback
The will-executor download still times out on the user's setup even with the
direct (original-style) GUI-thread call, and the URL/request are byte-identical
to the working original. To pinpoint the real cause, the Download List button
now:
- logs whether a Network instance is present;
- tries the configured WELIST_SERVER URL AND falls back to the original
hardcoded https://welist.bitcoin-after.life/ endpoint (so a stale/bad config
value can't break it);
- shows the EXACT URL(s) tried and the precise exception per attempt in the
warning popup instead of a generic timeout message.
bal/core networking remains unchanged.
* diag(gui): add direct-HTTPS control probe to download diagnostics
When the Electrum-network download fails, also run a plain urllib HTTPS GET
(bypassing Electrum's Network/proxy layer) and show its result in the popup.
This distinguishes a real connectivity/DNS/firewall problem from an
Electrum-network-state problem, so we can finally pinpoint why the request
times out only in this build.
* fix(gui): unify wizard + button download on one synchronous path with diagnostics
The 'No response from the server (timeout?)' popup was coming from the WIZARD
download path in window.py (BalWaitingDialog + TaskThread), which was never
switched to the original direct call - only the list button had been.
Both paths now share BalWindow.fetch_will_executors_list(): a direct,
synchronous GUI-thread download (like the original), trying the configured
server then the hardcoded fallback, with full diagnostics (exact URL/error per
attempt + a direct-HTTPS control probe) shown in the failure popup.
This both fixes the wizard timeout and guarantees the same diagnostic popup
('Details (via Electrum network)' + 'Direct connection test') regardless of
which UI entry point is used.
* fix(gui): clean up will-executor download (waiting dialog + simple message)
Root cause of the 'download not working' reports was environmental (the user's
network/ISP was resetting the connection to the IPv6/IPv4 host; a VPN fixes
it), NOT a plugin bug. Final cleanup of the diagnostic code:
- download_list (button + wizard) again uses BalWaitingDialog + TaskThread so
the GUI is not frozen and shows a 'Downloading will-executors list...' dialog.
- Keep the configured + hardcoded-fallback server URLs and detailed per-attempt
diagnostics, but write them to the Electrum log only.
- On failure the user now sees a simple English message explaining it is most
likely a connection/firewall issue (a VPN often helps), instead of a technical
timeout/exception dump.
- Removed the urllib control probe from the user-facing popup.
bal/core networking unchanged.
---------
Co-authored-by: GenSpark AI Developer <ai@genspark.dev>
1156 lines
40 KiB
Python
1156 lines
40 KiB
Python
"""
|
|
bal.gui.qt.dialogs
|
|
==================
|
|
|
|
All modal/non-modal dialogs of the plugin.
|
|
|
|
* BalDialog - common base dialog (icon, close handling).
|
|
* BalWizard* (Dialog/Widget) - the step-by-step "create your will" wizard.
|
|
* BalWaitingDialog /
|
|
BalBlockingWaitingDialog - progress dialogs for background tasks.
|
|
* BalBuildWillDialog - the central build/sign/push/broadcast flow.
|
|
* WillDetailDialog - shows the full will tree for one wallet.
|
|
* WillExecutorDialog - manage the list of will-executor servers.
|
|
|
|
To keep the dialogs verbatim while avoiding import cycles with the list views,
|
|
the few list classes they reference are imported lazily inside the methods that
|
|
use them (see ``lists`` imports below).
|
|
"""
|
|
|
|
from .common import *
|
|
from .common import _, _logger # underscore names are not re-exported by "import *"
|
|
from .widgets import (BalCheckBox, BalLineEdit, BalTextEdit, BalTxFeesWidget,
|
|
LockTimeWidget, PercAmountEdit, ThresholdTimeWidget,
|
|
WillSettingsWidget, WillWidget)
|
|
from .calendar import BalCalendar
|
|
# NOTE: list views (HeirListWidget, PreviewList, WillExecutorWidget) are
|
|
# imported lazily where needed to avoid a dialogs<->lists import cycle.
|
|
|
|
|
|
class BalDialog(QDialog,MessageBoxMixin):
|
|
_stopping = False
|
|
def __init__(self, parent, bal_plugin, title=None, icon="icons/bal16x16.png"):
|
|
import signal
|
|
from PyQt6.QtCore import QMetaObject, Qt
|
|
from PyQt6.QtWidgets import QApplication
|
|
def handler(signum, frame):
|
|
QMetaObject.invokeMethod(self, "close", Qt.ConnectionType.QueuedConnection)
|
|
|
|
#signal.signal(signal.SIGINT, handler)
|
|
# NOTE: do NOT store this as ``self.parent`` - that would shadow
|
|
# QWidget.parent() and can make the dialog disappear behind Electrum.
|
|
self._bal_parent = parent
|
|
self.thread = None
|
|
# Anchor the dialog to the *top-level* Electrum window so it always
|
|
# stays in front of it (instead of falling behind).
|
|
super().__init__(top_level_of(parent))
|
|
if title:
|
|
self.setWindowTitle(title)
|
|
# WindowModalDialog.__init__(self,parent)
|
|
self.setWindowIcon(read_QIcon_from_bytes(bal_plugin.read_file(icon)))
|
|
|
|
def closeEvent(self, event):
|
|
self._stopping = True
|
|
# NOTE: we deliberately do NOT stop ``self.thread`` here.
|
|
#
|
|
# Electrum's ``TaskThread`` delivers results via ``on_done`` which calls
|
|
# ``cb_done`` (often ``self.accept`` -> closes this dialog) *before*
|
|
# ``cb_result`` (``on_success`` -> e.g. updating the will-executor
|
|
# list). If we stop/join the thread inside ``closeEvent`` the close
|
|
# triggered by ``accept`` tears the thread down *before* ``on_success``
|
|
# runs, so the downloaded data is silently dropped. The original plugin
|
|
# left this commented out for exactly this reason; subclasses that own a
|
|
# genuinely long-lived thread stop it explicitly in their own close
|
|
# handler.
|
|
super().closeEvent(event)
|
|
|
|
def hideEvent(self, event):
|
|
self._stopping = True
|
|
super().hideEvent(event)
|
|
|
|
|
|
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._bal_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)
|
|
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):
|
|
self.bal_window.update_all()
|
|
pass
|
|
|
|
def on_reject(self):
|
|
pass
|
|
|
|
def on_close(self):
|
|
self.bal_window.update_all()
|
|
pass
|
|
|
|
def closeEvent(self, event):
|
|
self._stopping = True
|
|
# self.bal_window.heir_list_widget.will_settings_widget.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._bal_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._bal_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):
|
|
# Lazy import to avoid a dialogs<->lists import cycle (lists imports
|
|
# BalBuildWillDialog from this module at load time).
|
|
from .lists import HeirListWidget
|
|
self.heir_list_widget = HeirListWidget(self.bal_window, self)
|
|
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_export.clicked.connect(self.export_to_file)
|
|
widget = QWidget()
|
|
vbox = QVBoxLayout(widget)
|
|
vbox.addWidget(self.heir_list_widget)
|
|
vbox.addLayout(Buttons(button_add, button_import, button_export))
|
|
return widget
|
|
|
|
def import_from_file(self):
|
|
self.bal_window.import_heirs()
|
|
self.heir_list_widget.update()
|
|
|
|
def export_to_file(self):
|
|
self.bal_window.export_heirs()
|
|
|
|
def add_heir(self):
|
|
self.bal_window.new_heir_dialog()
|
|
self.heir_list_widget.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 do_nothing():
|
|
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"),
|
|
self.import_json_file,
|
|
do_nothing,
|
|
)
|
|
|
|
if index < 2:
|
|
|
|
def on_success(willexecutors):
|
|
def ping_on_success(result):
|
|
ping_on_done()
|
|
|
|
def ping_on_failure(exec_info):
|
|
ping_on_done()
|
|
|
|
def ping_on_done():
|
|
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
|
|
)
|
|
|
|
self.bal_window.ping_willexecutors(
|
|
self.bal_window.willexecutors, ping_on_success, ping_on_failure
|
|
)
|
|
|
|
self.bal_window.download_list(self.bal_window.willexecutors, on_success)
|
|
|
|
elif index == 3:
|
|
# TODO DO NOTHING
|
|
pass
|
|
|
|
self.bal_window.will_list_widget.update()
|
|
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):
|
|
# Lazy import to avoid a dialogs<->lists import cycle.
|
|
from .lists import WillExecutorWidget
|
|
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()
|
|
layout = QVBoxLayout(widget)
|
|
|
|
layout.addWidget(WillSettingsWidget(self.bal_window, self, "v"))
|
|
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)
|
|
# IMPORTANT: keep the *application-modal* exec() of the original code.
|
|
# This dialog is driven by a TaskThread whose result (on_success, e.g.
|
|
# populating the will-executor list) is delivered via a queued signal
|
|
# while exec() spins the modal event loop. Switching to window-modal
|
|
# changed how the modal loop interacts with that delivery and could
|
|
# cause the downloaded list to never be applied. We only add the
|
|
# raise/activate so the dialog stays visible, without altering modality.
|
|
bring_to_front(self)
|
|
self.exec()
|
|
|
|
def hello(self):
|
|
pass
|
|
|
|
def finished(self):
|
|
pass
|
|
|
|
|
|
def on_accepted(self):
|
|
pass
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
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 (window-modal + on top so it is actually visible)
|
|
show_on_top(self)
|
|
# Refresh the GUI so the popup is painted (and message_label drawn)
|
|
# BEFORE we block the GUI thread running the task; otherwise the popup
|
|
# appears empty/frozen.
|
|
from PyQt6.QtWidgets import QApplication
|
|
QApplication.processEvents()
|
|
QApplication.processEvents()
|
|
try:
|
|
# block and run given task
|
|
task()
|
|
finally:
|
|
# close popup
|
|
self.accept()
|
|
|
|
|
|
class BalBuildWillDialog(BalDialog):
|
|
updatemessage = pyqtSignal()
|
|
COLOR_WARNING = "#cfa808"
|
|
COLOR_ERROR = "#ff0000"
|
|
COLOR_OK = "#05ad05"
|
|
|
|
def __init__(self, bal_window, parent=None):
|
|
if not parent:
|
|
parent = bal_window.window
|
|
BalDialog.__init__(self, parent, bal_window.bal_plugin, _("Building Will"))
|
|
# (parent already stored as self._bal_parent by BalDialog.__init__)
|
|
self.updatemessage.connect(self.msg_update)
|
|
self.bal_window = bal_window
|
|
self.bal_plugin = bal_window.bal_plugin
|
|
self.message_label = QLabel(_("Building Will:"))
|
|
self.vbox = QVBoxLayout(self)
|
|
self.vbox.addWidget(self.message_label, 0)
|
|
self.qwidget = QWidget(self)
|
|
self.vbox.addWidget(self.qwidget, 1)
|
|
self.labelsbox = QVBoxLayout(self.qwidget)
|
|
self.setMinimumWidth(600)
|
|
self.setMinimumHeight(100)
|
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
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,
|
|
)
|
|
# exec() already shows the dialog modally; route through the helper so
|
|
# it is window-modal and brought to the front (no separate show()).
|
|
show_modal(self)
|
|
|
|
def task_phase1(self):
|
|
if self._stopping:
|
|
return
|
|
txs = None
|
|
_logger.debug("close plugin phase 1 started")
|
|
varrow = self.msg_set_status("checking variables")
|
|
try:
|
|
self.bal_window.init_class_variables()
|
|
except CheckAliveError as cae:
|
|
fee_per_byte = self.bal_window.will_settings.get("baltx_fees", 1)
|
|
tx = Will.invalidate_will(
|
|
self.bal_window.willitems, self.bal_window.wallet, fee_per_byte
|
|
)
|
|
if tx:
|
|
_logger.debug(
|
|
"during phase1 CAE: {}, Continue to invalidate".format(cae)
|
|
)
|
|
self.msg_set_status("checking variables",varrow, "Check Alive Threshold Passed: you have to Invalidate your old Will",self.COLOR_ERROR)
|
|
else:
|
|
raise cae
|
|
return None, tx
|
|
except NoHeirsException:
|
|
self.msg_set_status("checking variables", varrow,"No Heirs",self.COLOR_ERROR)
|
|
#self.msg_set_checking("No Heirs")
|
|
return False, None
|
|
except Exception as e:
|
|
raise e
|
|
try:
|
|
_logger.debug("checking variables")
|
|
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(),
|
|
)
|
|
_logger.debug("variables ok")
|
|
self.msg_set_status("checking variables:", varrow, "Ok", self.COLOR_OK)
|
|
except AmountException:
|
|
self.msg_set_checking(
|
|
self.msg_warning(
|
|
"In the inheritance process, "
|
|
+ "the entire wallet will always be fully emptied. \n"
|
|
+ "Your settings require an adjustment of the amounts"
|
|
)
|
|
)
|
|
|
|
self.msg_set_checking()
|
|
have_to_build = False
|
|
try:
|
|
self.bal_window.check_will()
|
|
self.msg_set_checking(self.msg_ok())
|
|
except WillExpiredException:
|
|
_logger.debug("expired")
|
|
self.msg_set_checking("Expired")
|
|
fee_per_byte = self.bal_window.will_settings.get("baltx_fees", 1)
|
|
return None, Will.invalidate_will(
|
|
self.bal_window.willitems, self.bal_window.wallet, fee_per_byte
|
|
)
|
|
except NoHeirsException as e:
|
|
_logger.debug("no heirs")
|
|
self.msg_set_checking("No Heirs")
|
|
except NotCompleteWillException as e:
|
|
_logger.debug(f"not complete {e} true")
|
|
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:
|
|
_logger.debug(f"message: {message}")
|
|
self.msg_set_checking(message)
|
|
else:
|
|
self.msg_set_checking("New")
|
|
|
|
if have_to_build:
|
|
self.msg_set_building()
|
|
try:
|
|
txs = self.bal_window.build_will()
|
|
if not txs:
|
|
self.msg_set_building(
|
|
_("Balance is too low, or CheckAlive is in the past.Skipped"),
|
|
color = self.COLOR_ERROR,
|
|
)
|
|
return False, None
|
|
|
|
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(self.msg_ok())
|
|
except WillExecutorNotPresent:
|
|
self.msg_set_status(
|
|
_("Will-Executor excluded"), None, _("Skipped"), self.COLOR_ERROR
|
|
)
|
|
|
|
except Exception as e:
|
|
self.msg_set_building(self.msg_error(e))
|
|
return False, None
|
|
|
|
# excluded_heirs = []
|
|
for wid in Will.only_valid(self.bal_window.willitems):
|
|
heirs = self.bal_window.willitems[wid].heirs
|
|
for hid, heir in heirs.items():
|
|
if "DUST" in str(heir[HEIR_REAL_AMOUNT]):
|
|
self.msg_set_status(
|
|
f"{hid},{heir[HEIR_DUST_AMOUNT]} is DUST",
|
|
None,
|
|
f"Excluded from will {wid}",
|
|
self.COLOR_WARNING,
|
|
)
|
|
|
|
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, txs
|
|
|
|
def on_accept(self):
|
|
self.bal_window.update_all()
|
|
pass
|
|
|
|
def on_accept_phase2(self):
|
|
self.bal_window.update_all()
|
|
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):
|
|
if self._stopping:
|
|
return
|
|
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(self.msg_ok())
|
|
if not txid:
|
|
_logger.debug(f"should not be none txid: {txid}")
|
|
|
|
except TxBroadcastError as e:
|
|
_logger.error(f"fail to broadcast transaction:{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):
|
|
if self._stopping:
|
|
return
|
|
self.msg_set_pushing(_("Broadcasting"))
|
|
retry = False
|
|
try:
|
|
|
|
willexecutors = Willexecutors.get_willexecutor_transactions(
|
|
self.bal_window.willitems
|
|
)
|
|
for url, willexecutor in willexecutors.items():
|
|
if self._stopping:
|
|
return
|
|
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"]:
|
|
if self._stopping:
|
|
return
|
|
row = self.msg_edit_row(
|
|
"checking {} - {} : {}".format(
|
|
self.bal_window.willitems[wid].we["url"], wid, "Waiting"
|
|
)
|
|
)
|
|
self.bal_plugin = self.bal_window.bal_plugin
|
|
w = self.bal_window.willitems[wid]
|
|
|
|
w.set_check_willexecutor(
|
|
Willexecutors.check_transaction(wid, w.we["url"])
|
|
)
|
|
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(f"loop push 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:
|
|
pass
|
|
# self.loop_push()
|
|
|
|
def invalidate_task(self, password, bal_window, tx):
|
|
if self._stopping:
|
|
return
|
|
_logger.debug(f"invalidate tx: {tx}")
|
|
# fee_per_byte = bal_window.will_settings.get("baltx_fees", 1)
|
|
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 Exception("tx not complete")
|
|
else:
|
|
raise Exception("not tx")
|
|
except Exception as e:
|
|
(f"exception:{e}")
|
|
self.msg_set_invalidating(f"Error: {e}")
|
|
raise Exception("Impossible to sign") from e
|
|
|
|
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_success_phase1(self, result):
|
|
if self._stopping:
|
|
return
|
|
self.have_to_sign, tx = list(result)
|
|
# if not tx:
|
|
# self.msg_edit_row(self.msg_error("Error, no tx was built"))
|
|
# return
|
|
_logger.debug("have to sign {}".format(self.have_to_sign))
|
|
password = None
|
|
if self.have_to_sign is None:
|
|
_logger.debug("have to invalidate")
|
|
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, password, self.bal_window, tx),
|
|
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._stopping = True
|
|
# Stop AND join the thread, then propagate the close event (previously
|
|
# it neither waited nor called super().closeEvent()).
|
|
stop_thread(getattr(self, "thread", None))
|
|
super().closeEvent(event)
|
|
|
|
def task_phase2(self, password):
|
|
if self._stopping:
|
|
return
|
|
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(self.msg_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(self.msg_ok())
|
|
|
|
except Exception as e:
|
|
# td = traceback.format_exc()
|
|
self.msg_set_pushing(self.msg_error(e))
|
|
self.msg_edit_row(self.msg_ok())
|
|
self.wait(5)
|
|
|
|
def on_error(self, error):
|
|
_logger.error(error)
|
|
pass
|
|
|
|
def on_error_phase1(self, error):
|
|
self.bal_window.update_all()
|
|
a, b, c = error
|
|
self.msg_edit_row(self.msg_error(f"Error: {b}"))
|
|
_logger.error(f"error phase1: {b}")
|
|
button=QPushButton(_("Close"))
|
|
button.clicked.connect(self.close)
|
|
self.vbox.addWidget(button)
|
|
self.resize(self.vbox.sizeHint()+button.sizeHint()*2)
|
|
self.repaint()
|
|
def on_error_phase2(self, error):
|
|
self.bal_window.upade_all()
|
|
a, b, c = error
|
|
self.msg_edit_row(self.msg_error(f"Error: {b}"))
|
|
_logger.error(f"error phase2: {b}")
|
|
|
|
def msg_set_checking(self, status="Waiting", 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,color=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, color
|
|
)
|
|
|
|
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 "<font color='{}'>{}</font>".format(self.COLOR_ERROR, e)
|
|
|
|
def msg_ok(self, e="Ok"):
|
|
return "<font color='{}'>{}</font>".format(self.COLOR_OK, e)
|
|
|
|
def msg_warning(self, e):
|
|
return "<font color='{}'>{}</font".format(self.COLOR_WARNING, e)
|
|
|
|
def msg_set_status(self, msg, row=None, status=None, color=None):
|
|
status = "Wait" if status is None else status
|
|
if color is None:
|
|
line = f"{_(msg)}:\t{status}"
|
|
else:
|
|
line = "<font color={}>{}:\t{}</font>".format(color, _(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):
|
|
try:
|
|
self.labels[row] = line
|
|
except Exception:
|
|
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:
|
|
pass
|
|
self.updatemessage.emit()
|
|
|
|
# def clear_layout(self,layout):
|
|
# while layout.count():
|
|
# item = layout.takeAt(0)
|
|
# w = item.widget()
|
|
# if w:
|
|
# w.setParent(None)
|
|
# w.deleteLater()
|
|
|
|
# def msg_update(self):
|
|
# self.clear_layout(self.labelsbox)
|
|
# for label in self.labels:
|
|
# label=label.replace("\n","<br>")
|
|
# qlabel=QLabel(label)
|
|
# qlabel.setWordWrap(True)
|
|
# self.labelsbox.addWidget(qlabel)
|
|
|
|
# self.labelsbox.activate()
|
|
# self.qwidget.setMinimumSize(self.labelsbox.sizeHint())
|
|
# self.qwidget.adjustSize()
|
|
# from PyQt6.QtWidgets import QApplication
|
|
# QApplication.processEvents()
|
|
#
|
|
# self.adjustSize()
|
|
def msg_update(self):
|
|
full_text = "<br><br>".join(self.labels).replace("\n", "<br>")
|
|
self.message_label.setText(full_text)
|
|
self.message_label.adjustSize()
|
|
# self.setMinimumHeight(len(self.labels)*40)
|
|
self.resize(self.sizeHint())
|
|
|
|
def get_text(self):
|
|
return self.message_label.text()
|
|
|
|
pass
|
|
|
|
|
|
|
|
class WillDetailDialog(BalDialog):
|
|
def __init__(self, bal_window):
|
|
|
|
self.will = bal_window.willitems
|
|
self.threshold = bal_window.will_settings["real_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.window.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: ") + str(BalTimestamp(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 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)
|
|
|
|
# Lazy import to avoid a dialogs<->lists import cycle.
|
|
from .lists import WillExecutorWidget
|
|
vbox = QVBoxLayout(self)
|
|
self.will_executor_list_widget = WillExecutorWidget(
|
|
self, self.bal_window, self.willexecutors_list
|
|
)
|
|
vbox.addWidget(self.will_executor_list_widget)
|
|
|
|
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()
|
|
# raise_() alone does not grab focus on some window managers (Windows);
|
|
# activateWindow() ensures the dialog actually comes to the front.
|
|
bring_to_front(self)
|
|
|
|
def closeEvent(self, event):
|
|
event.accept()
|
|
|
|
|