mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Keep the rebuild away from a window a thread is still writing to
deleteLater destroyed a window whose model boxes a daemon thread was still reporting into: a running download died with a RuntimeError, the .part stayed on disk and the UI said nothing. Dropping the reference is how the ordinary close path lets a window go, and the closures a thread holds keep the object alive until it is done, so the rebuild now does the same. While the transcriber or either model box is working the rebuild is skipped altogether; _built_language keeps the old language, so the next quiet save asks for it by itself. The new window is also placed and turned to the old tab before it is shown, so it no longer comes up at the default size and jumps. And tests/test_ui.py now says that a save with a changed language emits language_changed, and one without does not.
This commit is contained in:
+19
-6
@@ -1103,14 +1103,19 @@ class Dikte:
|
|||||||
|
|
||||||
def open_settings(self):
|
def open_settings(self):
|
||||||
if self.settings_window is None:
|
if self.settings_window is None:
|
||||||
|
self._make_settings()
|
||||||
|
self.settings_window.show()
|
||||||
|
self.settings_window.raise_()
|
||||||
|
self.settings_window.activateWindow()
|
||||||
|
|
||||||
|
def _make_settings(self):
|
||||||
|
"""Build the window without showing it, so a caller that knows where
|
||||||
|
it belongs can place it first."""
|
||||||
self.settings_window = SettingsWindow(self.conf, self.meetings)
|
self.settings_window = SettingsWindow(self.conf, self.meetings)
|
||||||
self.settings_window.applied.connect(self._apply_settings)
|
self.settings_window.applied.connect(self._apply_settings)
|
||||||
self.settings_window.language_changed.connect(self._reopen_settings)
|
self.settings_window.language_changed.connect(self._reopen_settings)
|
||||||
self.settings_window.update_found.connect(self._found_update)
|
self.settings_window.update_found.connect(self._found_update)
|
||||||
self.settings_window.finished.connect(self._settings_closed)
|
self.settings_window.finished.connect(self._settings_closed)
|
||||||
self.settings_window.show()
|
|
||||||
self.settings_window.raise_()
|
|
||||||
self.settings_window.activateWindow()
|
|
||||||
|
|
||||||
def _settings_closed(self, *_):
|
def _settings_closed(self, *_):
|
||||||
# Don't drop the object while its own signal is still being delivered.
|
# Don't drop the object while its own signal is still being delivered.
|
||||||
@@ -1135,11 +1140,19 @@ class Dikte:
|
|||||||
# would drop the reference to the new window a moment after it is made.
|
# would drop the reference to the new window a moment after it is made.
|
||||||
old.finished.disconnect(self._settings_closed)
|
old.finished.disconnect(self._settings_closed)
|
||||||
old.close()
|
old.close()
|
||||||
old.deleteLater()
|
# No deleteLater: a daemon thread of the old window's may still be
|
||||||
|
# running, and a closure holding self is what keeps the object alive
|
||||||
|
# until the thread is done. Dropping the reference is how the ordinary
|
||||||
|
# close path lets a window go, and it is enough here too.
|
||||||
self.settings_window = None
|
self.settings_window = None
|
||||||
self.open_settings()
|
self._make_settings()
|
||||||
self.settings_window.tabs.setCurrentIndex(tab)
|
# Placed and turned to the old tab before it is shown, so the new
|
||||||
|
# window does not come up at the default size and jump.
|
||||||
self.settings_window.setGeometry(geometry)
|
self.settings_window.setGeometry(geometry)
|
||||||
|
self.settings_window.tabs.setCurrentIndex(tab)
|
||||||
|
self.settings_window.show()
|
||||||
|
self.settings_window.raise_()
|
||||||
|
self.settings_window.activateWindow()
|
||||||
|
|
||||||
def _apply_local(self):
|
def _apply_local(self):
|
||||||
"""Pass the local settings on, and hold the models ready if asked to.
|
"""Pass the local settings on, and hold the models ready if asked to.
|
||||||
|
|||||||
+14
-1
@@ -1804,9 +1804,22 @@ class SettingsWindow(QDialog):
|
|||||||
# waits until the box is dismissed, so the window is not pulled out
|
# waits until the box is dismissed, so the window is not pulled out
|
||||||
# from under a dialog it is holding up.
|
# from under a dialog it is holding up.
|
||||||
QMessageBox.information(self, t("Dikte Settings"), t("Saved successfully."))
|
QMessageBox.information(self, t("Dikte Settings"), t("Saved successfully."))
|
||||||
if i18n.language() != self._built_language:
|
if i18n.language() != self._built_language and not self._work_in_flight():
|
||||||
self.language_changed.emit()
|
self.language_changed.emit()
|
||||||
|
|
||||||
|
def _work_in_flight(self):
|
||||||
|
"""A daemon thread of this window's is still running.
|
||||||
|
|
||||||
|
Replacing the window now would let it be collected, taking the C++
|
||||||
|
side of the model boxes down with it, and the thread's next progress
|
||||||
|
report would land on a deleted object. The stale labels stand until a
|
||||||
|
later save finds the window quiet; _built_language keeps the old
|
||||||
|
language, so that save asks for the rebuild by itself.
|
||||||
|
"""
|
||||||
|
return (self.transcriber.busy
|
||||||
|
or self.local_whisper._downloading
|
||||||
|
or self.local_llm._downloading)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _select_data(combo, value):
|
def _select_data(combo, value):
|
||||||
index = combo.findData(value)
|
index = combo.findData(value)
|
||||||
|
|||||||
@@ -240,6 +240,21 @@ class Settings(DikteTest):
|
|||||||
with self.subTest(key=key):
|
with self.subTest(key=key):
|
||||||
self.assertEqual(stored[key], value)
|
self.assertEqual(stored[key], value)
|
||||||
|
|
||||||
|
def test_only_a_save_that_changed_the_language_says_so(self):
|
||||||
|
# The owner answers language_changed by replacing the window, so a
|
||||||
|
# save that left the language alone must keep quiet.
|
||||||
|
i18n = settings_ui.i18n
|
||||||
|
self.addCleanup(i18n.set_language, i18n.language())
|
||||||
|
window = self.window(cfg.Config())
|
||||||
|
heard = []
|
||||||
|
window.language_changed.connect(lambda: heard.append(True))
|
||||||
|
window._save()
|
||||||
|
self.assertEqual(heard, [])
|
||||||
|
other = "en" if i18n.language() == "tr" else "tr"
|
||||||
|
window._select_data(window.ui_language, other)
|
||||||
|
window._save()
|
||||||
|
self.assertEqual(heard, [True])
|
||||||
|
|
||||||
def test_the_model_box_on_screen_belongs_to_whoever_cleans_up(self):
|
def test_the_model_box_on_screen_belongs_to_whoever_cleans_up(self):
|
||||||
"""An OpenRouter id and a Claude alias are not the same field."""
|
"""An OpenRouter id and a Claude alias are not the same field."""
|
||||||
window = self.window(cfg.Config())
|
window = self.window(cfg.Config())
|
||||||
|
|||||||
Reference in New Issue
Block a user