mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-03 04:17:58 +03:00
Refine Qt top-level widget lifetimes
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
+121
-150
@@ -65,29 +65,6 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
_OpenDialogs = {}
|
||||
|
||||
|
||||
def _releaseOpenDialog(key, *_args):
|
||||
"""Release the temporary strong reference for a finished dialog."""
|
||||
_OpenDialogs.pop(key, None)
|
||||
|
||||
|
||||
def _retainOpenDialog(dialog):
|
||||
"""Keep a shown dialog alive until it finishes or is destroyed."""
|
||||
key = id(dialog)
|
||||
_OpenDialogs[key] = dialog
|
||||
|
||||
if not getattr(dialog, '_furiousOpenDialogLifetimeConnected', False):
|
||||
release = functools.partial(_releaseOpenDialog, key)
|
||||
|
||||
dialog.finished.connect(release)
|
||||
dialog.destroyed.connect(release)
|
||||
dialog._furiousOpenDialogLifetimeConnected = True
|
||||
|
||||
return key
|
||||
|
||||
|
||||
def moveToCenter(widget, parent=None):
|
||||
"""Move to center."""
|
||||
geometry = widget.frameGeometry()
|
||||
@@ -134,11 +111,32 @@ class AppQComboBox(Mixins.QTranslatable, QComboBox):
|
||||
class AppQDialog(Mixins.QTranslatable, Mixins.ConnectionAware, QDialog):
|
||||
"""Present the app Qt dialog."""
|
||||
|
||||
_openDialogs = {}
|
||||
|
||||
@staticmethod
|
||||
def _releaseOpenDialog(key, *_args):
|
||||
"""Release an asynchronously opened dialog after it finishes."""
|
||||
AppQDialog._openDialogs.pop(key, None)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the AppQDialog."""
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._firstShowCall = True
|
||||
@callOnceOnly
|
||||
def connect(key):
|
||||
"""Connect the lifetime release signals once."""
|
||||
release = functools.partial(AppQDialog._releaseOpenDialog, key)
|
||||
|
||||
self.finished.connect(release)
|
||||
self.destroyed.connect(release)
|
||||
|
||||
@callOnceOnly
|
||||
def firstShow():
|
||||
"""Apply the first-show sizing once."""
|
||||
self.setWidthAndHeight()
|
||||
|
||||
self._connectOnce = connect
|
||||
self._firstShow = firstShow
|
||||
|
||||
if PLATFORM != 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
@@ -156,27 +154,29 @@ class AppQDialog(Mixins.QTranslatable, Mixins.ConnectionAware, QDialog):
|
||||
return super().exec()
|
||||
|
||||
def open(self):
|
||||
"""Open the app Qt dialog asynchronously."""
|
||||
self.show()
|
||||
"""Open and retain the dialog until it finishes or is destroyed."""
|
||||
key = id(self)
|
||||
AppQDialog._openDialogs[key] = self
|
||||
|
||||
return super().open()
|
||||
|
||||
def show(self):
|
||||
"""Show and position the app Qt dialog."""
|
||||
key = _retainOpenDialog(self)
|
||||
self._connectOnce(key)
|
||||
|
||||
try:
|
||||
super().show()
|
||||
self.show()
|
||||
|
||||
return super().open()
|
||||
except Exception:
|
||||
_releaseOpenDialog(key)
|
||||
# Any non-exit exceptions
|
||||
|
||||
AppQDialog._releaseOpenDialog(key)
|
||||
|
||||
raise
|
||||
|
||||
if PLATFORM == 'Darwin':
|
||||
if self._firstShowCall:
|
||||
self.setWidthAndHeight()
|
||||
def show(self):
|
||||
"""Show and position the app Qt dialog."""
|
||||
super().show()
|
||||
|
||||
self._firstShowCall = False
|
||||
if PLATFORM == 'Darwin':
|
||||
self._firstShow()
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
@@ -218,7 +218,7 @@ class AppQGroupBox(Mixins.QTranslatable, QGroupBox):
|
||||
self.setTitle(_(self.title()))
|
||||
|
||||
|
||||
class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
|
||||
class AppQHeaderView(Mixins.CleanupOnExit, QHeaderView):
|
||||
"""Represent app q header view."""
|
||||
|
||||
def sectionSizeSettingsEmpty(self):
|
||||
@@ -250,7 +250,6 @@ class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
|
||||
self.sectionSizeTable = {}
|
||||
|
||||
self.setSectionsClickable(True)
|
||||
# self.setStyleSheet(self.getStyleSheet(AppHue.currentColor()))
|
||||
self.setFont(QFont(AppFontName()))
|
||||
|
||||
# self.sectionResized.connect(self.handleSectionResized)
|
||||
@@ -308,23 +307,6 @@ class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
|
||||
else:
|
||||
self.setSectionResizeMode(index, AppQHeaderView.ResizeMode.Stretch)
|
||||
|
||||
@staticmethod
|
||||
def getStyleSheet(color):
|
||||
"""Return style sheet."""
|
||||
return f'QHeaderView::section:hover {{ background-color: {color}; }}'
|
||||
|
||||
def disconnectedCallback(self):
|
||||
# self.setStyleSheet(self.getStyleSheet(AppHue.disconnectedColor()))
|
||||
|
||||
"""Update the app q header view for a disconnected state."""
|
||||
pass
|
||||
|
||||
def connectedCallback(self):
|
||||
# self.setStyleSheet(self.getStyleSheet(AppHue.connectedColor()))
|
||||
|
||||
"""Update the app q header view for a connected state."""
|
||||
pass
|
||||
|
||||
# Legacy method. Not used
|
||||
# @QtCore.Slot(int, int, int)
|
||||
# def handleSectionResized(self, index: int, oldSize: int, newSize: int):
|
||||
@@ -371,44 +353,18 @@ class AppQLineEdit(Mixins.QTranslatable, QLineEdit):
|
||||
self.setPlaceholderText(_(self.placeholderText()))
|
||||
|
||||
|
||||
class AppQListWidget(Mixins.ConnectionAware, QListWidget):
|
||||
class AppQListWidget(QListWidget):
|
||||
"""Provide the app Qt list widget."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the AppQListWidget."""
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# self.setSelectionColor(AppHue.disconnectedColor())
|
||||
|
||||
def setSelectionColor(self, color):
|
||||
"""Set selection color."""
|
||||
self.setStyleSheet(
|
||||
f'QListWidget::item:selected {{'
|
||||
f' background: {color};'
|
||||
f'}}'
|
||||
f''
|
||||
f'QListWidget::item:hover {{'
|
||||
f' background: {color};'
|
||||
f'}}'
|
||||
)
|
||||
|
||||
@property
|
||||
def selectedIndex(self):
|
||||
"""Return the selected index value."""
|
||||
return sorted(list(set(index.row() for index in self.selectedIndexes())))
|
||||
|
||||
def disconnectedCallback(self):
|
||||
# self.setSelectionColor(AppHue.disconnectedColor())
|
||||
|
||||
"""Update the app Qt list widget for a disconnected state."""
|
||||
pass
|
||||
|
||||
def connectedCallback(self):
|
||||
# self.setSelectionColor(AppHue.connectedColor())
|
||||
|
||||
"""Update the app Qt list widget for a connected state."""
|
||||
pass
|
||||
|
||||
|
||||
class AppQMainWindow(
|
||||
Mixins.QTranslatable,
|
||||
@@ -418,11 +374,30 @@ class AppQMainWindow(
|
||||
):
|
||||
"""Present the app q main window."""
|
||||
|
||||
_openWindows = {}
|
||||
|
||||
@staticmethod
|
||||
def _releaseOpenWindow(key, *_args):
|
||||
"""Release a shown main window after it closes or is destroyed."""
|
||||
AppQMainWindow._openWindows.pop(key, None)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the AppQMainWindow."""
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._firstShowCall = True
|
||||
@callOnceOnly
|
||||
def connect(key):
|
||||
"""Connect the lifetime release signal once."""
|
||||
release = functools.partial(AppQMainWindow._releaseOpenWindow, key)
|
||||
self.destroyed.connect(release)
|
||||
|
||||
@callOnceOnly
|
||||
def firstShow():
|
||||
"""Apply the first-show sizing once."""
|
||||
self.setWidthAndHeight()
|
||||
|
||||
self._connectOnce = connect
|
||||
self._firstShow = firstShow
|
||||
|
||||
self.setWindowIcon(AppHue.currentWindowIcon())
|
||||
|
||||
@@ -437,14 +412,23 @@ class AppQMainWindow(
|
||||
pass
|
||||
|
||||
def show(self):
|
||||
"""Show and position the app q main window."""
|
||||
super().show()
|
||||
"""Show, position, and retain the window until it closes."""
|
||||
key = id(self)
|
||||
AppQMainWindow._openWindows[key] = self
|
||||
|
||||
self._connectOnce(key)
|
||||
|
||||
try:
|
||||
super().show()
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
AppQMainWindow._releaseOpenWindow(key)
|
||||
|
||||
raise
|
||||
|
||||
if PLATFORM == 'Darwin':
|
||||
if self._firstShowCall:
|
||||
self.setWidthAndHeight()
|
||||
|
||||
self._firstShowCall = False
|
||||
self._firstShow()
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
@@ -454,6 +438,16 @@ class AppQMainWindow(
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
|
||||
def event(self, event):
|
||||
"""Release this window after Qt accepts its close event."""
|
||||
closes = event.type() == QtCore.QEvent.Type.Close
|
||||
result = super().event(event)
|
||||
|
||||
if closes and event.isAccepted():
|
||||
AppQMainWindow._releaseOpenWindow(id(self))
|
||||
|
||||
return result
|
||||
|
||||
def retranslate(self):
|
||||
"""Refresh translated text for the app q main window."""
|
||||
self.setWindowTitle(_(self.windowTitle()))
|
||||
@@ -510,10 +504,27 @@ class AppQMenuBar(QMenuBar):
|
||||
class AppQMessageBox(Mixins.QTranslatable, Mixins.ConnectionAware, QMessageBox):
|
||||
"""Represent app q message box."""
|
||||
|
||||
_openMessageBoxes = {}
|
||||
|
||||
@staticmethod
|
||||
def _releaseOpenMessageBox(key, *_args):
|
||||
"""Release an asynchronously opened message box after it finishes."""
|
||||
AppQMessageBox._openMessageBoxes.pop(key, None)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the AppQMessageBox."""
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@callOnceOnly
|
||||
def connect(key):
|
||||
"""Connect the lifetime release signals once."""
|
||||
release = functools.partial(AppQMessageBox._releaseOpenMessageBox, key)
|
||||
|
||||
self.finished.connect(release)
|
||||
self.destroyed.connect(release)
|
||||
|
||||
self._connectOnce = connect
|
||||
|
||||
self.setWindowIcon(AppHue.currentWindowIcon())
|
||||
|
||||
def moveToCenter(self):
|
||||
@@ -523,15 +534,8 @@ class AppQMessageBox(Mixins.QTranslatable, Mixins.ConnectionAware, QMessageBox):
|
||||
return self
|
||||
|
||||
def show(self):
|
||||
"""Show the message box and retain it until it finishes."""
|
||||
key = _retainOpenDialog(self)
|
||||
|
||||
try:
|
||||
return super().show()
|
||||
except Exception:
|
||||
_releaseOpenDialog(key)
|
||||
|
||||
raise
|
||||
"""Show the app q message box."""
|
||||
return super().show()
|
||||
|
||||
def exec(self):
|
||||
"""Show and execute the app q message box modally."""
|
||||
@@ -541,11 +545,23 @@ class AppQMessageBox(Mixins.QTranslatable, Mixins.ConnectionAware, QMessageBox):
|
||||
return super().exec()
|
||||
|
||||
def open(self):
|
||||
"""Open the app q message box asynchronously."""
|
||||
self.show()
|
||||
self.moveToCenter()
|
||||
"""Open and retain the message box until it finishes or is destroyed."""
|
||||
key = id(self)
|
||||
AppQMessageBox._openMessageBoxes[key] = self
|
||||
|
||||
return super().open()
|
||||
self._connectOnce(key)
|
||||
|
||||
try:
|
||||
self.show()
|
||||
self.moveToCenter()
|
||||
|
||||
return super().open()
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
AppQMessageBox._releaseOpenMessageBox(key)
|
||||
|
||||
raise
|
||||
|
||||
def retranslate(self):
|
||||
"""Refresh translated text for the app q message box."""
|
||||
@@ -669,7 +685,7 @@ class AppQSpinBox(QSpinBox):
|
||||
self.resizeHints()
|
||||
|
||||
|
||||
class AppQTableView(Mixins.ConnectionAware, QTableView):
|
||||
class AppQTableView(QTableView):
|
||||
"""Represent app Qt table view."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -684,27 +700,8 @@ class AppQTableView(Mixins.ConnectionAware, QTableView):
|
||||
self.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Fixed)
|
||||
self.verticalHeader().setDefaultSectionSize(height)
|
||||
|
||||
@staticmethod
|
||||
def getStyleSheet(color):
|
||||
"""Return style sheet."""
|
||||
return f'QTableView {{ selection-background-color: {color}; }}'
|
||||
|
||||
def setSelectionColor(self, color):
|
||||
# self.setStyleSheet(self.getStyleSheet(color))
|
||||
|
||||
"""Set selection color."""
|
||||
pass
|
||||
|
||||
def disconnectedCallback(self):
|
||||
"""Update the app Qt table view for a disconnected state."""
|
||||
self.setSelectionColor(AppHue.disconnectedColor())
|
||||
|
||||
def connectedCallback(self):
|
||||
"""Update the app Qt table view for a connected state."""
|
||||
self.setSelectionColor(AppHue.connectedColor())
|
||||
|
||||
|
||||
class AppQTableWidget(Mixins.ConnectionAware, QTableWidget):
|
||||
class AppQTableWidget(QTableWidget):
|
||||
"""Provide the app Qt table widget."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -719,17 +716,6 @@ class AppQTableWidget(Mixins.ConnectionAware, QTableWidget):
|
||||
"""Return the selected index value."""
|
||||
return sorted(list(set(index.row() for index in self.selectedIndexes())))
|
||||
|
||||
@staticmethod
|
||||
def getStyleSheet(color):
|
||||
"""Return style sheet."""
|
||||
return f'QTableWidget {{ selection-background-color: {color}; }}'
|
||||
|
||||
def setSelectionColor(self, color):
|
||||
# self.setStyleSheet(self.getStyleSheet(color))
|
||||
|
||||
"""Set selection color."""
|
||||
pass
|
||||
|
||||
def activateItemByIndex(self, index, activate):
|
||||
"""Activate item by index."""
|
||||
if activate:
|
||||
@@ -776,14 +762,6 @@ class AppQTableWidget(Mixins.ConnectionAware, QTableWidget):
|
||||
selection, QtCore.QItemSelectionModel.SelectionFlag.Select
|
||||
)
|
||||
|
||||
def disconnectedCallback(self):
|
||||
"""Update the app Qt table widget for a disconnected state."""
|
||||
self.setSelectionColor(AppHue.disconnectedColor())
|
||||
|
||||
def connectedCallback(self):
|
||||
"""Update the app Qt table widget for a connected state."""
|
||||
self.setSelectionColor(AppHue.connectedColor())
|
||||
|
||||
|
||||
class AppQTabWidget(Mixins.QTranslatable, QTabWidget):
|
||||
"""Provide the app q tab widget."""
|
||||
@@ -818,8 +796,6 @@ class AppQToolBar(Mixins.QTranslatable, QToolBar):
|
||||
# Do nothing
|
||||
pass
|
||||
|
||||
self.setStyleSheet(self.getStyleSheet())
|
||||
|
||||
self.actionTriggered.connect(self.showMenuBelow)
|
||||
|
||||
@QtCore.Slot(AppQAction)
|
||||
@@ -847,11 +823,6 @@ class AppQToolBar(Mixins.QTranslatable, QToolBar):
|
||||
if isinstance(menu, AppQMenu):
|
||||
menu.exec(button.mapToGlobal(button.rect().bottomLeft()))
|
||||
|
||||
@staticmethod
|
||||
def getStyleSheet():
|
||||
"""Return style sheet."""
|
||||
return f'QToolBar {{ spacing: 5px; }}'
|
||||
|
||||
def retranslate(self):
|
||||
"""Refresh translated text for the app q tool bar."""
|
||||
self.setWindowTitle(_(self.windowTitle()))
|
||||
|
||||
@@ -66,6 +66,7 @@ class ConnectionProgressBar(Mixins.ConnectionAware, QProgressBar):
|
||||
style = self.style()
|
||||
style.unpolish(self)
|
||||
style.polish(self)
|
||||
|
||||
self.update()
|
||||
|
||||
def start(self, msec: int):
|
||||
|
||||
@@ -1444,7 +1444,6 @@ class ServerTableView(
|
||||
self.proxyModel.sort(-1)
|
||||
|
||||
# Selection
|
||||
self.setSelectionColor(AppHue.disconnectedColor())
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
|
||||
|
||||
@@ -341,7 +341,6 @@ class SubscriptionTableView(Mixins.QTranslatable, AppQTableView):
|
||||
self.setSortingEnabled(False)
|
||||
|
||||
# Selection
|
||||
self.setSelectionColor(AppHue.disconnectedColor())
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
|
||||
|
||||
@@ -30,18 +30,10 @@ from PySide6.QtGui import *
|
||||
from PySide6.QtWidgets import *
|
||||
|
||||
import io
|
||||
import functools
|
||||
import pyqrcode
|
||||
|
||||
__all__ = ['QRCodeWindow']
|
||||
|
||||
_OpenQRCodeWindows = {}
|
||||
|
||||
|
||||
def _releaseOpenQRCodeWindow(key, *_args):
|
||||
"""Release an exported QR-code window after Qt destroys it."""
|
||||
_OpenQRCodeWindows.pop(key, None)
|
||||
|
||||
|
||||
class QRCodeWindow(AppQMainWindow):
|
||||
"""Present the QR code window."""
|
||||
@@ -54,28 +46,12 @@ class QRCodeWindow(AppQMainWindow):
|
||||
self.setFixedSize(640, 640)
|
||||
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
||||
|
||||
self._lifetimeKey = id(self)
|
||||
self.destroyed.connect(
|
||||
functools.partial(_releaseOpenQRCodeWindow, self._lifetimeKey)
|
||||
)
|
||||
|
||||
self.tabWidget = QTabWidget(self)
|
||||
self.tabWidget.setTabsClosable(True)
|
||||
self.tabWidget.tabCloseRequested.connect(self.handleTabCloseRequested)
|
||||
|
||||
self.setCentralWidget(self.tabWidget)
|
||||
|
||||
def show(self):
|
||||
"""Show and retain this unparented window until Qt destroys it."""
|
||||
_OpenQRCodeWindows[self._lifetimeKey] = self
|
||||
|
||||
try:
|
||||
return super().show()
|
||||
except Exception:
|
||||
_releaseOpenQRCodeWindow(self._lifetimeKey)
|
||||
|
||||
raise
|
||||
|
||||
def tabCount(self) -> int:
|
||||
"""Return the tab count value used by the QR code window."""
|
||||
return self.tabWidget.count()
|
||||
|
||||
Reference in New Issue
Block a user