From 19b08eb280df18c4059606b3c24d3d0230db7c43 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 18 May 2023 09:44:12 +1000 Subject: [PATCH] Store separate state/geometry for each Qt minor version Quite a few users have been experiencing crashes recently that were resolved by resetting their window positions/states. I presume this is related to Qt updates, as there have been previous instances where old state caused glitchy behaviour or crashes after a Qt upgrade. The browser headers are now also reset when resetting window positions in the preferences. --- qt/aqt/browser/table/table.py | 10 ++----- qt/aqt/preferences.py | 5 ++-- qt/aqt/utils.py | 51 ++++++++++++++++++++++------------- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/qt/aqt/browser/table/table.py b/qt/aqt/browser/table/table.py index 9d99ccc21..fde1fcb98 100644 --- a/qt/aqt/browser/table/table.py +++ b/qt/aqt/browser/table/table.py @@ -321,17 +321,11 @@ class Table: hh.setCascadingSectionResizes(False) def _save_header(self) -> None: - saveHeader( - self._view.horizontalHeader(), self._state.GEOMETRY_KEY_PREFIX + "31" - ) + saveHeader(self._view.horizontalHeader(), self._state.GEOMETRY_KEY_PREFIX) def _restore_header(self) -> None: self._view.horizontalHeader().blockSignals(True) - # Qt 6.3.1 won't allow headers to be clicked when restoring state from a previous - # version, so we need to bump the key. - restoreHeader( - self._view.horizontalHeader(), self._state.GEOMETRY_KEY_PREFIX + "31" - ) + restoreHeader(self._view.horizontalHeader(), self._state.GEOMETRY_KEY_PREFIX) self._set_column_sizes() self._set_sort_indicator() self._view.horizontalHeader().blockSignals(False) diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 7d0229b5a..633a5bce6 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -1,6 +1,7 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import re from typing import Any, cast import anki.lang @@ -311,9 +312,9 @@ class Preferences(QDialog): self.mw.set_theme(Theme(index)) def on_reset_window_sizes(self) -> None: - suffixes = ["Geom", "State", "Splitter"] + regexp = re.compile(r"(Geom(etry)?|State|Splitter|Header)(\d+.\d+)?$") for key in list(self.prof.keys()): - if any(key.endswith(suffix) for suffix in suffixes): + if regexp.search(key): del self.prof[key] showInfo(tr.preferences_reset_window_sizes_complete()) diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 97bf00bb1..32b9215b1 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -2,6 +2,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from __future__ import annotations +import enum import inspect import os import re @@ -693,11 +694,29 @@ def getSaveFile( return file +class _QtStateKeyKind(enum.Enum): + HEADER = enum.auto() + SPLITTER = enum.auto() + STATE = enum.auto() + GEOMETRY = enum.auto() + + +def _qt_state_key(kind: _QtStateKeyKind, key: str) -> str: + """Construct a key used to save/restore geometry, state, etc. + + Adds Qt version number to key so that different data is saved per Qt version, + preventing crashes and bugs when restoring data saved with a different Qt version. + """ + qt_suffix = f"{qtmajor}.{qtminor}" if qtmajor > 5 else "" + return f"{key}{kind.name.capitalize()}{qt_suffix}" + + def saveGeom(widget: QWidget, key: str) -> None: # restoring a fullscreen window is buggy # (at the time of writing; Qt 6.2.2 and 5.15) if not widget.isFullScreen(): - aqt.mw.pm.profile[f"{key}Geom"] = widget.saveGeometry() + key = _qt_state_key(_QtStateKeyKind.GEOMETRY, key) + aqt.mw.pm.profile[key] = widget.saveGeometry() def restoreGeom( @@ -706,7 +725,7 @@ def restoreGeom( adjustSize: bool = False, default_size: tuple[int, int] | None = None, ) -> None: - key += "Geom" + key = _qt_state_key(_QtStateKeyKind.GEOMETRY, key) if existing_geom := aqt.mw.pm.profile.get(key): widget.restoreGeometry(existing_geom) ensureWidgetInScreenBoundaries(widget) @@ -745,39 +764,35 @@ def ensureWidgetInScreenBoundaries(widget: QWidget) -> None: def saveState(widget: QFileDialog | QMainWindow, key: str) -> None: - key += "State" + key = _qt_state_key(_QtStateKeyKind.STATE, key) aqt.mw.pm.profile[key] = widget.saveState() def restoreState(widget: QFileDialog | QMainWindow, key: str) -> None: - key += "State" - if aqt.mw.pm.profile.get(key): - widget.restoreState(aqt.mw.pm.profile[key]) + key = _qt_state_key(_QtStateKeyKind.STATE, key) + if data := aqt.mw.pm.profile.get(key): + widget.restoreState(data) def saveSplitter(widget: QSplitter, key: str) -> None: - key += "Splitter" + key = _qt_state_key(_QtStateKeyKind.SPLITTER, key) aqt.mw.pm.profile[key] = widget.saveState() def restoreSplitter(widget: QSplitter, key: str) -> None: - key += "Splitter" - if aqt.mw.pm.profile.get(key): - widget.restoreState(aqt.mw.pm.profile[key]) - - -def _header_key(key: str) -> str: - # not compatible across major versions - qt_suffix = f"Qt{qtmajor}" if qtmajor > 5 else "" - return f"{key}Header{qt_suffix}" + key = _qt_state_key(_QtStateKeyKind.SPLITTER, key) + if data := aqt.mw.pm.profile.get(key): + widget.restoreState(data) def saveHeader(widget: QHeaderView, key: str) -> None: - aqt.mw.pm.profile[_header_key(key)] = widget.saveState() + key = _qt_state_key(_QtStateKeyKind.HEADER, key) + aqt.mw.pm.profile[key] = widget.saveState() def restoreHeader(widget: QHeaderView, key: str) -> None: - if state := aqt.mw.pm.profile.get(_header_key(key)): + key = _qt_state_key(_QtStateKeyKind.HEADER, key) + if state := aqt.mw.pm.profile.get(key): widget.restoreState(state)