mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-05 05:17:59 +03:00
Destroy action-owned submenus
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
@@ -53,7 +53,9 @@ primitives; pages and services consume them without creating parallel registries
|
||||
arbitrary worker calls to the GUI thread; choose an explicit queued owner-thread delivery boundary. A surviving
|
||||
Python wrapper can already be natively invalid, so callback freshness and `shiboken6.isValid()` address different
|
||||
failure modes. Neither replaces the strong owner required while asynchronous UI remains active.
|
||||
- `AppQAction.callback` is strong by design, so the action owner cannot outlive the captured receiver.
|
||||
- `AppQAction.callback` is strong by design, so the action owner cannot outlive the captured receiver. An action
|
||||
also owns a submenu supplied without a QWidget parent and schedules its native deletion when the action dies;
|
||||
`QAction.setMenu()` alone does not establish parent ownership. Explicitly parented menus retain their chosen owner.
|
||||
- Every `QNetworkReply` has one manager/context owner, one freshness rule, and one terminal deletion path. Request
|
||||
context must also be released when native destruction skips `finished`, including manager-first teardown with
|
||||
retained Python wrappers. Use the shared network-manager tracking boundary; cleanup must not capture a reply
|
||||
|
||||
@@ -147,6 +147,12 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
|
||||
# Create reference
|
||||
self._menu = menu
|
||||
|
||||
if menu.parent() is None:
|
||||
# setMenu() associates a submenu without giving it a Qt parent.
|
||||
# This action owns otherwise unparented menus; explicit widget
|
||||
# parents retain their own lifetime. deleteLater is a native slot.
|
||||
self.destroyed.connect(menu.deleteLater)
|
||||
|
||||
# Some old version PySide6 does not have setMenu method
|
||||
# for QAction. Protect it. Currently only used in TrayIcon
|
||||
if hasattr(self, 'setMenu') and useSetMenu:
|
||||
|
||||
@@ -220,6 +220,35 @@ class QtLifetimeTest(unittest.TestCase):
|
||||
|
||||
self.assertAllDestroyed(references, destroyed, 60)
|
||||
|
||||
def testActionOwnedMenusDieWithActionAndPreserveExplicitOwners(self):
|
||||
"""QAction's menu association must not strand an unparented native menu."""
|
||||
references = []
|
||||
destroyed = []
|
||||
|
||||
for explicitOwner in (False, True):
|
||||
for _ in range(30):
|
||||
owner = QWidget()
|
||||
menu = AppQMenu(parent=owner if explicitOwner else None)
|
||||
action = AppQAction('Menu fixture', menu=menu, parent=owner)
|
||||
references.append(weakref.ref(menu))
|
||||
menu.destroyed.connect(lambda *_args: destroyed.append(True))
|
||||
|
||||
action.deleteLater()
|
||||
processQtEvents()
|
||||
|
||||
self.assertFalse(isValid(action))
|
||||
try:
|
||||
self.assertEqual(isValid(menu), explicitOwner)
|
||||
finally:
|
||||
if isValid(menu):
|
||||
menu.deleteLater()
|
||||
owner.deleteLater()
|
||||
processQtEvents()
|
||||
|
||||
del action, menu, owner
|
||||
|
||||
self.assertAllDestroyed(references, destroyed, 60)
|
||||
|
||||
def testReopenedDialogSurvivesPreviousPresentationCleanup(self):
|
||||
"""A queued finish must not release the next asynchronous presentation."""
|
||||
destroyed = []
|
||||
|
||||
Reference in New Issue
Block a user