vibefpmgui.window.options

The project's configuration file (vibefpmgui.build_configs.ConfigFile): which it is, and the GUI's options and New configuration's Build options in it (see vibefpmgui.project_options): used while the project is open, written back (when the window closes or another project is opened, if they changed), and applied to the window when another project is opened.

A project whose file holds options (it has one that can be read) uses them; without, the app's settings as before (nothing is written in a project only looked at). A file made while the project is open (its first configuration saved) holds its options from then on.

New configuration's Build options are one Configuration: the file's "new_configuration", or, without options, the app's settings ("profile", "compiler"… and the project's "features/").

  1"""The project's configuration file (`vibefpmgui.build_configs.ConfigFile`):
  2which it is, and the GUI's options and New configuration's Build options in
  3it (see `vibefpmgui.project_options`): used while the project is open,
  4written back (when the window closes or another project is opened, if they
  5changed), and applied to the window when another project is opened.
  6
  7A project whose file holds options (it has one that can be read) uses them;
  8without, the app's settings as before (nothing is written in a project only
  9looked at). A file made while the project is open (its first configuration
 10saved) holds its options from then on.
 11
 12New configuration's Build options are one `Configuration`: the file's
 13"new_configuration", or, without options, the app's settings ("profile",
 14"compiler"… and the project's "features/<hash>").
 15"""
 16
 17from __future__ import annotations
 18
 19import hashlib
 20from pathlib import Path
 21
 22from .. import build_configs as bc
 23from .. import project_options as po
 24from .. import spelling
 25from ..code_editor import CodeEditor
 26from ..graph_view import EDGE_STYLES, LAYOUTS, THEME_CHOICES
 27
 28NEW_CONFIGURATION_KEY = "new_configuration"  # (for _apply_options: New configuration's Build options)
 29
 30
 31def project_key(manifest: Path, prefix: str) -> str:
 32    """The app's settings key for a project's ``prefix`` (its session, build
 33    configurations, features, test durations): "prefix/" and a short hash
 34    of its fpm.toml's path. (A name, not security: SHA-1 is fine.)"""
 35    digest = hashlib.sha1(str(manifest).encode(), usedforsecurity=False).hexdigest()[:16]
 36    return f"{prefix}/{digest}"
 37
 38
 39class ProjectOptionsMixin:
 40    """The project's configuration file and its options (a part of
 41    `vibefpmgui.main_window.MainWindow`)."""
 42
 43    config_file: bc.ConfigFile | None = None  # the open project's (it may not exist yet)
 44    _config_manifest: Path | None = None  # the project config_file is for
 45
 46    @property
 47    def config_path(self) -> Path | None:
 48        return self.config_file.path if self.config_file is not None else None
 49
 50    def _project_key(self, prefix: str) -> str:
 51        """The app's settings key for this project's ``prefix`` (see `project_key`)."""
 52        return project_key(self.manifest_path, prefix)
 53
 54    def options_in_use(self) -> bool:
 55        """The project's options are its file's (not the app's settings)."""
 56        return self.settings.options is not None
 57
 58    # ------------------------------------------------------------ the file
 59    def _config_file_for(self, manifest: Path) -> bc.ConfigFile:
 60        """A project's configuration file, read: the one opened for it last
 61        (Build options → ⋯ → Open Configuration File…), or the one beside its fpm.toml."""
 62        remembered = self.settings.app.value(project_key(manifest, "build_config_file"), "") or ""
 63        path = Path(remembered) if remembered and Path(remembered).is_file() else bc.default_path(manifest)
 64        return bc.ConfigFile(path).read()
 65
 66    def _use_config_file(self, file: bc.ConfigFile | None, manifest: Path | None, options: dict | None = None):
 67        """Use ``file`` (the project ``manifest``'s) and its options (or
 68        ``options``) from now on."""
 69        self.config_file, self._config_manifest = file, manifest
 70        keys = {name: project_key(manifest, name) for name in po.PER_PROJECT} if manifest is not None else {}
 71        if options is None and file is not None:
 72            options = file.options
 73        self.settings.use(options, manifest.parent if options is not None else None, keys)
 74
 75    def _config_file_saved(self):
 76        """A configuration was saved: a file just made holds the project's options from now on."""
 77        file = self.config_file
 78        if file is not None and file.options is None:
 79            file.start_options()
 80            self._use_config_file(file, self._config_manifest)
 81
 82    def _switch_config_file(self, manifest: Path, force: bool = False):
 83        """Before another project is opened (or, ``force``, another
 84        configuration file of this one): this one's options written, the
 85        other's used and applied. (Neither with options: nothing to do but
 86        keep New configuration's, which are this project's.)"""
 87        if manifest == self._config_manifest and not force:
 88            return
 89        file = self._config_file_for(manifest)
 90        involved = self.options_in_use() or file.options is not None
 91        if involved:
 92            self._store_window_options()
 93            self._write_config_file()
 94        elif self.manifest_path is not None:
 95            self._keep_free_options(self.free_options())
 96        self._use_config_file(file, manifest)
 97        if involved:
 98            self._apply_options()
 99
100    def _write_config_file(self):
101        """Write the project's options to its file, if they changed."""
102        file = self.config_file
103        if file is None or not self.options_in_use():
104            return
105        try:
106            file.write_options(self.settings.complete_options(), self._saved_free_options())
107        except (OSError, bc.ConfigError) as exc:
108            self.statusBar().showMessage(f"Couldn't save the options in {file.path.name}: {exc}", 8000)
109
110    def reload_config_file(self):
111        """The configuration file was saved in the editor: its configurations
112        read again, and the options changed there (and New configuration, if
113        it was) used and applied; the window's other options, changed since
114        it was read, stay (and the configuration shown)."""
115        file = self.config_file
116        if file is None or self.manifest_path is None:
117            return
118        changed, new_changed = file.reread()
119        if not (changed or new_changed):
120            return
121        if not self.options_in_use():  # (it couldn't be read when the project was opened: fixed now)
122            self._use_config_file(file, self._config_manifest)
123            self._apply_options()
124            return
125        options = dict(self.settings.options)
126        for key in changed:
127            if key in file.options:
128                options[key] = file.options[key]
129            else:
130                options.pop(key, None)
131        self._use_config_file(file, self._config_manifest, options)
132        self._apply_options(changed | ({NEW_CONFIGURATION_KEY} if new_changed else set()))
133
134    # ------------------------------------------------------------ New configuration
135    def _saved_free_options(self) -> bc.Configuration:
136        """New configuration's Build options as kept (not as shown: see
137        free_options): the file's, or the app's settings."""
138        file, manifest = self.config_file, self._config_manifest  # (the project the settings are for)
139        if self.options_in_use() and file is not None and file.new_configuration is not None:
140            return file.new_configuration
141        s = self.settings.app
142        features = (s.value(project_key(manifest, "features"), [], type=list) or []) if manifest else []
143        return bc.Configuration(
144            "", profile=s.value("profile", bc.DEFAULT_PROFILE) or bc.DEFAULT_PROFILE,
145            features=[str(f) for f in features], compiler=s.value("compiler", "") or "",
146            flags=s.value("flags", "") or "", target=s.value("target", "") or "",
147            extra_args=s.value("extra", "") or "", program_args=s.value("program_args", "") or "",
148            parallel_tests=max(1, s.value("test_jobs", 1, type=int)), coverage=s.value("coverage", False, type=bool),
149            verbose=s.value("verbose", False, type=bool))
150
151    def _keep_free_options(self, config: bc.Configuration):
152        """Keep New configuration's Build options: in the file (written with
153        the options), or the app's settings."""
154        file = self.config_file
155        if self.options_in_use() and file is not None:
156            file.new_configuration = bc.Configuration.from_json(config.to_json(named=False), name="")
157            return
158        s = self.settings.app
159        for key, value in (("profile", config.profile), ("compiler", config.compiler), ("flags", config.flags),
160                           ("extra", config.extra_args), ("target", config.target),
161                           ("program_args", config.program_args), ("verbose", config.verbose),
162                           ("coverage", config.coverage), ("test_jobs", config.parallel_tests)):
163            s.setValue(key, value)
164        if self._config_manifest is not None:
165            s.setValue(project_key(self._config_manifest, "features"), list(config.features))
166
167    def _apply_build_options(self):
168        """Build options' fields: New configuration's, as kept (a saved
169        configuration shown stays: they're shown when it's left)."""
170        free = self._saved_free_options()
171        self._wanted_profile = free.profile
172        if self.config_shown():
173            self._free_options = free
174        else:
175            self._show_options(free)
176
177    # ------------------------------------------------------------ the window
178    def _toggles(self) -> list[tuple]:
179        """(action, settings key, default) of the View menu's on/off choices."""
180        return [(self.use_editor_action, "use_editor", True), (self.lint_action, "lint", True),
181                (self.lsp_diagnostics_action, "lsp_diagnostics", True), (self.spelling_action, "spelling", True),
182                (self.gremlins_action, "gremlins", True), (self.minimap_action, "minimap", True),
183                (self.blame_action, "git_blame", False),
184                (self.coverage_action, "show_coverage", True), (self.matches_action, "highlight_matches", True),
185                (self.word_wrap_action, "word_wrap", False), (self.fortls_action, "fortls", True),
186                (self.trim_on_save_action, "trim_on_save", True)]
187
188    def _store_window_options(self, front: list[str] | None = None):
189        """Every option as the window has it now, in the settings (the
190        project's options, or the app's), and New configuration's Build
191        options: those changed are there already, not those never changed (a
192        file's options have them all). ``front``: the panels in front of
193        their stacks (as they were before closing began: see closeEvent).
194        (Collapsed panels are opened first: a layout is saved with them open.)"""
195        s = self.settings
196        for action, key, _default in self._toggles():
197            s.setValue(key, action.isChecked())
198        s.setValue("spelling_language", spelling.language)
199        s.setValue("indent_width", CodeEditor.indent_width)
200        s.setValue("indent_tabs", CodeEditor.use_tabs)
201        s.setValue("tab_rows_wrap", self.tab_rows_actions[True].isChecked())
202        for key, actions in self.graph_style_actions.items():
203            chosen = next((value for value, action in actions.items() if action.isChecked()), None)
204            if chosen is not None:
205                s.setValue(f"graph_{key}", chosen)
206        s.setValue("graph_dependencies_dev", self.dependencies_dev_action.isChecked())
207        s.setValue("complexity_tests", self.complexity_panel.tests_check.isChecked())
208        s.setValue("complexity_dependencies", self.complexity_panel.dependencies_check.isChecked())
209        s.setValue("coverage_tests", self.coverage_panel.tests_check.isChecked())
210        for source, button in self.problems_panel.buttons.items():
211            s.setValue(f"problems/{source}", button.isChecked())
212        self.search_panel._save_options()
213        front = self.front_panels() if front is None else front
214        self.snap_closed.expand_all()
215        s.setValue("panels", self.saveState(self.LAYOUT_VERSION))
216        s.setValue("panels_front", front)
217        self._keep_free_options(self.free_options())
218
219    def _apply_options(self, only: set[str] | None = None):
220        """Options, from the settings, to the window: every one (another
221        project's: the window was made with the first's), or ``only`` those
222        (changed in the file, in the editor; NEW_CONFIGURATION_KEY: New
223        configuration's Build options)."""
224        s = self.settings
225
226        def wanted(*keys: str) -> bool:
227            return only is None or any(key in only for key in keys)
228
229        if wanted(NEW_CONFIGURATION_KEY):
230            self._apply_build_options()
231        # View
232        for action, key, default in self._toggles():
233            if wanted(key):
234                action.setChecked(s.value(key, default, type=bool))  # (its handler applies it)
235        if wanted("spelling_language"):
236            language = str(s.value("spelling_language", "en") or "en")
237            if language != spelling.language:
238                self.set_spelling_language(language)
239        if wanted("indent_width", "indent_tabs"):
240            width = s.value("indent_width", 4, type=int)
241            use_tabs = s.value("indent_tabs", False, type=bool)
242            if (width, use_tabs) != (CodeEditor.indent_width, CodeEditor.use_tabs):
243                self.set_indentation(width, use_tabs)
244        if wanted("tab_rows_wrap"):
245            self.set_tab_rows(s.value("tab_rows_wrap", False, type=bool), save=False)
246        # Graph
247        if wanted("graph_layout"):
248            self._set_graph_layout(s.value("graph_layout", next(iter(LAYOUTS))))
249        if wanted("graph_theme"):
250            self._set_graph_theme(s.value("graph_theme", next(iter(THEME_CHOICES))))
251        if wanted("graph_edges"):
252            self._set_graph_edges(s.value("graph_edges", next(iter(EDGE_STYLES))))
253        if wanted("graph_dependencies_dev"):
254            dev = s.value("graph_dependencies_dev", False, type=bool)
255            if only is None:  # (another project: it's loaded next anyway)
256                self.dependencies_dev_action.blockSignals(True)
257            self.dependencies_dev_action.setChecked(dev)
258            self.dependencies_dev_action.blockSignals(False)
259        # Panels' own choices
260        if wanted("complexity_tests"):
261            self.complexity_panel.tests_check.setChecked(s.value("complexity_tests", False, type=bool))
262        if wanted("complexity_dependencies"):
263            self.complexity_panel.dependencies_check.setChecked(s.value("complexity_dependencies", False, type=bool))
264        if wanted("coverage_tests"):
265            self.coverage_panel.tests_check.setChecked(s.value("coverage_tests", False, type=bool))
266        for source, button in self.problems_panel.buttons.items():
267            if wanted(f"problems/{source}"):
268                button.setChecked(s.value(f"problems/{source}", True, type=bool))
269        if any(wanted(key) for key in po.OPTIONS if key.startswith("search/")):
270            self.search_panel._restore_options()
271        if wanted("panels", "panels_front"):
272            layout = s.value("panels")
273            if layout:
274                self.restore_layout(layout, s.value("panels_front", [], type=list) or [])
275        if only is not None and "build_configuration" in only and not self._editing:
276            # (Changed in the file. Another project's: chosen when its configurations are listed.)
277            self.select_configuration(s.value("build_configuration", "") or "")
NEW_CONFIGURATION_KEY = 'new_configuration'
def project_key(manifest: pathlib.Path, prefix: str) -> str:
32def project_key(manifest: Path, prefix: str) -> str:
33    """The app's settings key for a project's ``prefix`` (its session, build
34    configurations, features, test durations): "prefix/" and a short hash
35    of its fpm.toml's path. (A name, not security: SHA-1 is fine.)"""
36    digest = hashlib.sha1(str(manifest).encode(), usedforsecurity=False).hexdigest()[:16]
37    return f"{prefix}/{digest}"

The app's settings key for a project's prefix (its session, build configurations, features, test durations): "prefix/" and a short hash of its fpm.toml's path. (A name, not security: SHA-1 is fine.)

class ProjectOptionsMixin:
 40class ProjectOptionsMixin:
 41    """The project's configuration file and its options (a part of
 42    `vibefpmgui.main_window.MainWindow`)."""
 43
 44    config_file: bc.ConfigFile | None = None  # the open project's (it may not exist yet)
 45    _config_manifest: Path | None = None  # the project config_file is for
 46
 47    @property
 48    def config_path(self) -> Path | None:
 49        return self.config_file.path if self.config_file is not None else None
 50
 51    def _project_key(self, prefix: str) -> str:
 52        """The app's settings key for this project's ``prefix`` (see `project_key`)."""
 53        return project_key(self.manifest_path, prefix)
 54
 55    def options_in_use(self) -> bool:
 56        """The project's options are its file's (not the app's settings)."""
 57        return self.settings.options is not None
 58
 59    # ------------------------------------------------------------ the file
 60    def _config_file_for(self, manifest: Path) -> bc.ConfigFile:
 61        """A project's configuration file, read: the one opened for it last
 62        (Build options → ⋯ → Open Configuration File…), or the one beside its fpm.toml."""
 63        remembered = self.settings.app.value(project_key(manifest, "build_config_file"), "") or ""
 64        path = Path(remembered) if remembered and Path(remembered).is_file() else bc.default_path(manifest)
 65        return bc.ConfigFile(path).read()
 66
 67    def _use_config_file(self, file: bc.ConfigFile | None, manifest: Path | None, options: dict | None = None):
 68        """Use ``file`` (the project ``manifest``'s) and its options (or
 69        ``options``) from now on."""
 70        self.config_file, self._config_manifest = file, manifest
 71        keys = {name: project_key(manifest, name) for name in po.PER_PROJECT} if manifest is not None else {}
 72        if options is None and file is not None:
 73            options = file.options
 74        self.settings.use(options, manifest.parent if options is not None else None, keys)
 75
 76    def _config_file_saved(self):
 77        """A configuration was saved: a file just made holds the project's options from now on."""
 78        file = self.config_file
 79        if file is not None and file.options is None:
 80            file.start_options()
 81            self._use_config_file(file, self._config_manifest)
 82
 83    def _switch_config_file(self, manifest: Path, force: bool = False):
 84        """Before another project is opened (or, ``force``, another
 85        configuration file of this one): this one's options written, the
 86        other's used and applied. (Neither with options: nothing to do but
 87        keep New configuration's, which are this project's.)"""
 88        if manifest == self._config_manifest and not force:
 89            return
 90        file = self._config_file_for(manifest)
 91        involved = self.options_in_use() or file.options is not None
 92        if involved:
 93            self._store_window_options()
 94            self._write_config_file()
 95        elif self.manifest_path is not None:
 96            self._keep_free_options(self.free_options())
 97        self._use_config_file(file, manifest)
 98        if involved:
 99            self._apply_options()
100
101    def _write_config_file(self):
102        """Write the project's options to its file, if they changed."""
103        file = self.config_file
104        if file is None or not self.options_in_use():
105            return
106        try:
107            file.write_options(self.settings.complete_options(), self._saved_free_options())
108        except (OSError, bc.ConfigError) as exc:
109            self.statusBar().showMessage(f"Couldn't save the options in {file.path.name}: {exc}", 8000)
110
111    def reload_config_file(self):
112        """The configuration file was saved in the editor: its configurations
113        read again, and the options changed there (and New configuration, if
114        it was) used and applied; the window's other options, changed since
115        it was read, stay (and the configuration shown)."""
116        file = self.config_file
117        if file is None or self.manifest_path is None:
118            return
119        changed, new_changed = file.reread()
120        if not (changed or new_changed):
121            return
122        if not self.options_in_use():  # (it couldn't be read when the project was opened: fixed now)
123            self._use_config_file(file, self._config_manifest)
124            self._apply_options()
125            return
126        options = dict(self.settings.options)
127        for key in changed:
128            if key in file.options:
129                options[key] = file.options[key]
130            else:
131                options.pop(key, None)
132        self._use_config_file(file, self._config_manifest, options)
133        self._apply_options(changed | ({NEW_CONFIGURATION_KEY} if new_changed else set()))
134
135    # ------------------------------------------------------------ New configuration
136    def _saved_free_options(self) -> bc.Configuration:
137        """New configuration's Build options as kept (not as shown: see
138        free_options): the file's, or the app's settings."""
139        file, manifest = self.config_file, self._config_manifest  # (the project the settings are for)
140        if self.options_in_use() and file is not None and file.new_configuration is not None:
141            return file.new_configuration
142        s = self.settings.app
143        features = (s.value(project_key(manifest, "features"), [], type=list) or []) if manifest else []
144        return bc.Configuration(
145            "", profile=s.value("profile", bc.DEFAULT_PROFILE) or bc.DEFAULT_PROFILE,
146            features=[str(f) for f in features], compiler=s.value("compiler", "") or "",
147            flags=s.value("flags", "") or "", target=s.value("target", "") or "",
148            extra_args=s.value("extra", "") or "", program_args=s.value("program_args", "") or "",
149            parallel_tests=max(1, s.value("test_jobs", 1, type=int)), coverage=s.value("coverage", False, type=bool),
150            verbose=s.value("verbose", False, type=bool))
151
152    def _keep_free_options(self, config: bc.Configuration):
153        """Keep New configuration's Build options: in the file (written with
154        the options), or the app's settings."""
155        file = self.config_file
156        if self.options_in_use() and file is not None:
157            file.new_configuration = bc.Configuration.from_json(config.to_json(named=False), name="")
158            return
159        s = self.settings.app
160        for key, value in (("profile", config.profile), ("compiler", config.compiler), ("flags", config.flags),
161                           ("extra", config.extra_args), ("target", config.target),
162                           ("program_args", config.program_args), ("verbose", config.verbose),
163                           ("coverage", config.coverage), ("test_jobs", config.parallel_tests)):
164            s.setValue(key, value)
165        if self._config_manifest is not None:
166            s.setValue(project_key(self._config_manifest, "features"), list(config.features))
167
168    def _apply_build_options(self):
169        """Build options' fields: New configuration's, as kept (a saved
170        configuration shown stays: they're shown when it's left)."""
171        free = self._saved_free_options()
172        self._wanted_profile = free.profile
173        if self.config_shown():
174            self._free_options = free
175        else:
176            self._show_options(free)
177
178    # ------------------------------------------------------------ the window
179    def _toggles(self) -> list[tuple]:
180        """(action, settings key, default) of the View menu's on/off choices."""
181        return [(self.use_editor_action, "use_editor", True), (self.lint_action, "lint", True),
182                (self.lsp_diagnostics_action, "lsp_diagnostics", True), (self.spelling_action, "spelling", True),
183                (self.gremlins_action, "gremlins", True), (self.minimap_action, "minimap", True),
184                (self.blame_action, "git_blame", False),
185                (self.coverage_action, "show_coverage", True), (self.matches_action, "highlight_matches", True),
186                (self.word_wrap_action, "word_wrap", False), (self.fortls_action, "fortls", True),
187                (self.trim_on_save_action, "trim_on_save", True)]
188
189    def _store_window_options(self, front: list[str] | None = None):
190        """Every option as the window has it now, in the settings (the
191        project's options, or the app's), and New configuration's Build
192        options: those changed are there already, not those never changed (a
193        file's options have them all). ``front``: the panels in front of
194        their stacks (as they were before closing began: see closeEvent).
195        (Collapsed panels are opened first: a layout is saved with them open.)"""
196        s = self.settings
197        for action, key, _default in self._toggles():
198            s.setValue(key, action.isChecked())
199        s.setValue("spelling_language", spelling.language)
200        s.setValue("indent_width", CodeEditor.indent_width)
201        s.setValue("indent_tabs", CodeEditor.use_tabs)
202        s.setValue("tab_rows_wrap", self.tab_rows_actions[True].isChecked())
203        for key, actions in self.graph_style_actions.items():
204            chosen = next((value for value, action in actions.items() if action.isChecked()), None)
205            if chosen is not None:
206                s.setValue(f"graph_{key}", chosen)
207        s.setValue("graph_dependencies_dev", self.dependencies_dev_action.isChecked())
208        s.setValue("complexity_tests", self.complexity_panel.tests_check.isChecked())
209        s.setValue("complexity_dependencies", self.complexity_panel.dependencies_check.isChecked())
210        s.setValue("coverage_tests", self.coverage_panel.tests_check.isChecked())
211        for source, button in self.problems_panel.buttons.items():
212            s.setValue(f"problems/{source}", button.isChecked())
213        self.search_panel._save_options()
214        front = self.front_panels() if front is None else front
215        self.snap_closed.expand_all()
216        s.setValue("panels", self.saveState(self.LAYOUT_VERSION))
217        s.setValue("panels_front", front)
218        self._keep_free_options(self.free_options())
219
220    def _apply_options(self, only: set[str] | None = None):
221        """Options, from the settings, to the window: every one (another
222        project's: the window was made with the first's), or ``only`` those
223        (changed in the file, in the editor; NEW_CONFIGURATION_KEY: New
224        configuration's Build options)."""
225        s = self.settings
226
227        def wanted(*keys: str) -> bool:
228            return only is None or any(key in only for key in keys)
229
230        if wanted(NEW_CONFIGURATION_KEY):
231            self._apply_build_options()
232        # View
233        for action, key, default in self._toggles():
234            if wanted(key):
235                action.setChecked(s.value(key, default, type=bool))  # (its handler applies it)
236        if wanted("spelling_language"):
237            language = str(s.value("spelling_language", "en") or "en")
238            if language != spelling.language:
239                self.set_spelling_language(language)
240        if wanted("indent_width", "indent_tabs"):
241            width = s.value("indent_width", 4, type=int)
242            use_tabs = s.value("indent_tabs", False, type=bool)
243            if (width, use_tabs) != (CodeEditor.indent_width, CodeEditor.use_tabs):
244                self.set_indentation(width, use_tabs)
245        if wanted("tab_rows_wrap"):
246            self.set_tab_rows(s.value("tab_rows_wrap", False, type=bool), save=False)
247        # Graph
248        if wanted("graph_layout"):
249            self._set_graph_layout(s.value("graph_layout", next(iter(LAYOUTS))))
250        if wanted("graph_theme"):
251            self._set_graph_theme(s.value("graph_theme", next(iter(THEME_CHOICES))))
252        if wanted("graph_edges"):
253            self._set_graph_edges(s.value("graph_edges", next(iter(EDGE_STYLES))))
254        if wanted("graph_dependencies_dev"):
255            dev = s.value("graph_dependencies_dev", False, type=bool)
256            if only is None:  # (another project: it's loaded next anyway)
257                self.dependencies_dev_action.blockSignals(True)
258            self.dependencies_dev_action.setChecked(dev)
259            self.dependencies_dev_action.blockSignals(False)
260        # Panels' own choices
261        if wanted("complexity_tests"):
262            self.complexity_panel.tests_check.setChecked(s.value("complexity_tests", False, type=bool))
263        if wanted("complexity_dependencies"):
264            self.complexity_panel.dependencies_check.setChecked(s.value("complexity_dependencies", False, type=bool))
265        if wanted("coverage_tests"):
266            self.coverage_panel.tests_check.setChecked(s.value("coverage_tests", False, type=bool))
267        for source, button in self.problems_panel.buttons.items():
268            if wanted(f"problems/{source}"):
269                button.setChecked(s.value(f"problems/{source}", True, type=bool))
270        if any(wanted(key) for key in po.OPTIONS if key.startswith("search/")):
271            self.search_panel._restore_options()
272        if wanted("panels", "panels_front"):
273            layout = s.value("panels")
274            if layout:
275                self.restore_layout(layout, s.value("panels_front", [], type=list) or [])
276        if only is not None and "build_configuration" in only and not self._editing:
277            # (Changed in the file. Another project's: chosen when its configurations are listed.)
278            self.select_configuration(s.value("build_configuration", "") or "")

The project's configuration file and its options (a part of vibefpmgui.main_window.MainWindow).

config_file: vibefpmgui.build_configs.ConfigFile | None = None
config_path: pathlib.Path | None
47    @property
48    def config_path(self) -> Path | None:
49        return self.config_file.path if self.config_file is not None else None
def options_in_use(self) -> bool:
55    def options_in_use(self) -> bool:
56        """The project's options are its file's (not the app's settings)."""
57        return self.settings.options is not None

The project's options are its file's (not the app's settings).

def reload_config_file(self):
111    def reload_config_file(self):
112        """The configuration file was saved in the editor: its configurations
113        read again, and the options changed there (and New configuration, if
114        it was) used and applied; the window's other options, changed since
115        it was read, stay (and the configuration shown)."""
116        file = self.config_file
117        if file is None or self.manifest_path is None:
118            return
119        changed, new_changed = file.reread()
120        if not (changed or new_changed):
121            return
122        if not self.options_in_use():  # (it couldn't be read when the project was opened: fixed now)
123            self._use_config_file(file, self._config_manifest)
124            self._apply_options()
125            return
126        options = dict(self.settings.options)
127        for key in changed:
128            if key in file.options:
129                options[key] = file.options[key]
130            else:
131                options.pop(key, None)
132        self._use_config_file(file, self._config_manifest, options)
133        self._apply_options(changed | ({NEW_CONFIGURATION_KEY} if new_changed else set()))

The configuration file was saved in the editor: its configurations read again, and the options changed there (and New configuration, if it was) used and applied; the window's other options, changed since it was read, stay (and the configuration shown).