forked from bitcoinafterlife/bal-electrum-plugin
120 lines
3.8 KiB
Python
120 lines
3.8 KiB
Python
"""
|
|
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
|