add bal/gui

This commit is contained in:
bot
2026-06-20 09:48:56 -04:00
parent b9c00446c4
commit 574efa7539
11 changed files with 5610 additions and 0 deletions

119
bal/gui/qt/window_utils.py Normal file
View File

@@ -0,0 +1,119 @@
"""
bal.gui.qt.window_utils
=======================
Centralized window/dialog presentation helpers.
The original plugin opened dialogs inconsistently: some with ``exec()``
(modal, stays on top) and some with ``show()`` (modeless, can fall *behind*
the main Electrum window). It also relied on a per-instance ``self.parent``
attribute that shadows :meth:`QWidget.parent`, and it never gave non-modal
dialogs focus, so they could disappear behind Electrum.
To fix this *without changing the business logic*, all the "how is this window
shown / focused / parented" concerns are collected here. The rest of the GUI
code just calls these helpers, so the behaviour is consistent and easy to
audit.
None of these helpers change *what* a dialog does — only its parenting,
modality and z-order/focus.
"""
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QWidget
def top_level_of(widget):
"""Return the proper top-level window to use as a dialog parent.
Electrum widgets expose ``top_level_window()``; when available we use it so
the dialog is anchored to the real top-level Electrum window (and therefore
stays in front of it). Falls back to the widget's own ``window()`` or the
widget itself.
"""
if widget is None:
return None
# Electrum's MessageBoxMixin / ElectrumWindow provide top_level_window().
tlw = getattr(widget, "top_level_window", None)
if callable(tlw):
try:
return tlw()
except Exception:
pass
# Plain QWidget: window() returns the top-level container.
if isinstance(widget, QWidget):
try:
return widget.window()
except Exception:
pass
return widget
def bring_to_front(dialog):
"""Make a *visible* dialog actually appear in front and take focus.
``raise_()`` alone is not enough on some window managers (notably Windows):
without ``activateWindow()`` the dialog can stay behind the main window.
"""
try:
dialog.raise_()
dialog.activateWindow()
except Exception:
pass
def stop_thread(thread):
"""Safely stop and join an Electrum ``TaskThread`` if present.
The original code commented out thread teardown, leaving background
threads running after a dialog closed (which could touch destroyed widgets
or keep network connections open until Electrum was restarted). This
stops the thread and waits for it to finish, guarding against ``None`` and
any teardown error.
"""
if thread is None:
return
try:
thread.stop()
except Exception:
pass
try:
thread.wait()
except Exception:
pass
def show_modal(dialog):
"""Show *dialog* modally and return the result of ``exec()``.
Modal dialogs always stay in front of their parent, which is the desired
behaviour for the plugin's editing/confirmation dialogs.
"""
try:
dialog.setWindowModality(Qt.WindowModality.WindowModal)
except Exception:
pass
bring_to_front(dialog)
return dialog.exec()
def show_on_top(dialog, *, modal_to_window=True):
"""Show *dialog* non-modally but guaranteed in front of Electrum.
Use this for the few dialogs that must remain non-modal (e.g. the
transaction dialog the user may want to keep open alongside the wallet).
It sets window-modality (so it stays above its parent window without
blocking the whole application) and gives it focus.
Set ``modal_to_window=False`` for a completely modeless window.
"""
try:
if modal_to_window:
dialog.setWindowModality(Qt.WindowModality.WindowModal)
else:
dialog.setWindowModality(Qt.WindowModality.NonModal)
except Exception:
pass
dialog.show()
bring_to_front(dialog)
return dialog