Skip to content

Commit 573a36d

Browse files
schubsenclaude
andcommitted
Mark the project as unsaved when the analysis config changes
Publish every change of a single analysis setting through a StaticAnalysisConfigChanged notifier and let the quick save button observe it. Loading a project replaces the whole configuration without passing the notifier, so a load is not mistaken for an unsaved change. Fix the quick save button of the webui along the way: writing to the style dict never reached the browser, because only calling props or style schedules the update. Restore the color the button was built with instead of removing the prop, which would render the button white. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 90ea01b commit 573a36d

5 files changed

Lines changed: 156 additions & 20 deletions

File tree

‎OTAnalytics/application/use_cases/static_analysis_config.py‎

Lines changed: 106 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,31 @@
99
CurrentExportConfigState,
1010
CurrentStaticAnalysisConfigState,
1111
)
12+
from OTAnalytics.domain.observer import OBSERVER, Subject
13+
14+
15+
class StaticAnalysisConfigChanged:
16+
"""Notify observers that a value of the static analysis config was changed.
17+
18+
Only changes made by the user are published. Loading a project replaces the
19+
whole configuration without passing this notifier, so a load does not look
20+
like an unsaved change.
21+
"""
22+
23+
def __init__(self) -> None:
24+
self._subject = Subject[None]()
25+
26+
def register(self, observer: OBSERVER[None]) -> None:
27+
"""Listen to changes of the static analysis config.
28+
29+
Args:
30+
observer (OBSERVER[None]): the observer to be registered.
31+
"""
32+
self._subject.register(observer)
33+
34+
def notify(self) -> None:
35+
"""Notify all observers about a changed static analysis config."""
36+
self._subject.notify(None)
1237

1338

1439
class GetDoEventsOfCurrentAnalysisConfig:
@@ -37,10 +62,17 @@ class SetDoEventsInCurrentAnalysisConfig:
3762
Args:
3863
state (CurrentStaticAnalysisConfigState): the state holding the analysis
3964
settings.
65+
config_changed (StaticAnalysisConfigChanged): notifies observers about
66+
the change.
4067
"""
4168

42-
def __init__(self, state: CurrentStaticAnalysisConfigState) -> None:
69+
def __init__(
70+
self,
71+
state: CurrentStaticAnalysisConfigState,
72+
config_changed: StaticAnalysisConfigChanged,
73+
) -> None:
4374
self._state = state
75+
self._config_changed = config_changed
4476

4577
def set(self, do_events: bool) -> None:
4678
"""Set whether events are to be detected.
@@ -49,6 +81,7 @@ def set(self, do_events: bool) -> None:
4981
do_events (bool): True if events are to be detected.
5082
"""
5183
self._state.do_events = do_events
84+
self._config_changed.notify()
5285

5386

5487
class GetDoCountingOfCurrentAnalysisConfig:
@@ -77,10 +110,17 @@ class SetDoCountingInCurrentAnalysisConfig:
77110
Args:
78111
state (CurrentStaticAnalysisConfigState): the state holding the analysis
79112
settings.
113+
config_changed (StaticAnalysisConfigChanged): notifies observers about
114+
the change.
80115
"""
81116

82-
def __init__(self, state: CurrentStaticAnalysisConfigState) -> None:
117+
def __init__(
118+
self,
119+
state: CurrentStaticAnalysisConfigState,
120+
config_changed: StaticAnalysisConfigChanged,
121+
) -> None:
83122
self._state = state
123+
self._config_changed = config_changed
84124

85125
def set(self, do_counting: bool) -> None:
86126
"""Set whether counts are to be created.
@@ -89,6 +129,7 @@ def set(self, do_counting: bool) -> None:
89129
do_counting (bool): True if counts are to be created.
90130
"""
91131
self._state.do_counting = do_counting
132+
self._config_changed.notify()
92133

93134

94135
class GetNumProcessesOfCurrentAnalysisConfig:
@@ -117,10 +158,17 @@ class SetNumProcessesInCurrentAnalysisConfig:
117158
Args:
118159
state (CurrentStaticAnalysisConfigState): the state holding the analysis
119160
settings.
161+
config_changed (StaticAnalysisConfigChanged): notifies observers about
162+
the change.
120163
"""
121164

122-
def __init__(self, state: CurrentStaticAnalysisConfigState) -> None:
165+
def __init__(
166+
self,
167+
state: CurrentStaticAnalysisConfigState,
168+
config_changed: StaticAnalysisConfigChanged,
169+
) -> None:
123170
self._state = state
171+
self._config_changed = config_changed
124172

125173
def set(self, num_processes: int) -> None:
126174
"""Set the number of processes to be used for the analysis.
@@ -129,6 +177,7 @@ def set(self, num_processes: int) -> None:
129177
num_processes (int): the number of processes.
130178
"""
131179
self._state.num_processes = num_processes
180+
self._config_changed.notify()
132181

133182

134183
class GetLogfileOfCurrentAnalysisConfig:
@@ -157,10 +206,17 @@ class SetLogfileInCurrentAnalysisConfig:
157206
Args:
158207
state (CurrentStaticAnalysisConfigState): the state holding the analysis
159208
settings.
209+
config_changed (StaticAnalysisConfigChanged): notifies observers about
210+
the change.
160211
"""
161212

162-
def __init__(self, state: CurrentStaticAnalysisConfigState) -> None:
213+
def __init__(
214+
self,
215+
state: CurrentStaticAnalysisConfigState,
216+
config_changed: StaticAnalysisConfigChanged,
217+
) -> None:
163218
self._state = state
219+
self._config_changed = config_changed
164220

165221
def set(self, logfile: Path) -> None:
166222
"""Set the logfile the analysis log is written to.
@@ -169,6 +225,7 @@ def set(self, logfile: Path) -> None:
169225
logfile (Path): the logfile.
170226
"""
171227
self._state.logfile = logfile
228+
self._config_changed.notify()
172229

173230

174231
class GetSaveNameOfCurrentAnalysisConfig:
@@ -195,10 +252,17 @@ class SetSaveNameInCurrentAnalysisConfig:
195252
196253
Args:
197254
state (CurrentExportConfigState): the state holding the export settings.
255+
config_changed (StaticAnalysisConfigChanged): notifies observers about
256+
the change.
198257
"""
199258

200-
def __init__(self, state: CurrentExportConfigState) -> None:
259+
def __init__(
260+
self,
261+
state: CurrentExportConfigState,
262+
config_changed: StaticAnalysisConfigChanged,
263+
) -> None:
201264
self._state = state
265+
self._config_changed = config_changed
202266

203267
def set(self, save_name: str) -> None:
204268
"""Set the name used to save analysis results.
@@ -207,6 +271,7 @@ def set(self, save_name: str) -> None:
207271
save_name (str): the save name.
208272
"""
209273
self._state.save_name = save_name
274+
self._config_changed.notify()
210275

211276

212277
class GetSaveSuffixOfCurrentAnalysisConfig:
@@ -233,10 +298,17 @@ class SetSaveSuffixInCurrentAnalysisConfig:
233298
234299
Args:
235300
state (CurrentExportConfigState): the state holding the export settings.
301+
config_changed (StaticAnalysisConfigChanged): notifies observers about
302+
the change.
236303
"""
237304

238-
def __init__(self, state: CurrentExportConfigState) -> None:
305+
def __init__(
306+
self,
307+
state: CurrentExportConfigState,
308+
config_changed: StaticAnalysisConfigChanged,
309+
) -> None:
239310
self._state = state
311+
self._config_changed = config_changed
240312

241313
def set(self, save_suffix: str) -> None:
242314
"""Set the suffix appended to the save name.
@@ -245,6 +317,7 @@ def set(self, save_suffix: str) -> None:
245317
save_suffix (str): the save suffix.
246318
"""
247319
self._state.save_suffix = save_suffix
320+
self._config_changed.notify()
248321

249322

250323
class GetEventFormatsOfCurrentAnalysisConfig:
@@ -271,10 +344,17 @@ class SetEventFormatsInCurrentAnalysisConfig:
271344
272345
Args:
273346
state (CurrentExportConfigState): the state holding the export settings.
347+
config_changed (StaticAnalysisConfigChanged): notifies observers about
348+
the change.
274349
"""
275350

276-
def __init__(self, state: CurrentExportConfigState) -> None:
351+
def __init__(
352+
self,
353+
state: CurrentExportConfigState,
354+
config_changed: StaticAnalysisConfigChanged,
355+
) -> None:
277356
self._state = state
357+
self._config_changed = config_changed
278358

279359
def set(self, event_formats: set[str]) -> None:
280360
"""Set the formats the event list is exported to.
@@ -283,6 +363,7 @@ def set(self, event_formats: set[str]) -> None:
283363
event_formats (set[str]): the event formats.
284364
"""
285365
self._state.event_formats = event_formats
366+
self._config_changed.notify()
286367

287368

288369
class GetCountIntervalsOfCurrentAnalysisConfig:
@@ -309,10 +390,17 @@ class SetCountIntervalsInCurrentAnalysisConfig:
309390
310391
Args:
311392
state (CurrentExportConfigState): the state holding the export settings.
393+
config_changed (StaticAnalysisConfigChanged): notifies observers about
394+
the change.
312395
"""
313396

314-
def __init__(self, state: CurrentExportConfigState) -> None:
397+
def __init__(
398+
self,
399+
state: CurrentExportConfigState,
400+
config_changed: StaticAnalysisConfigChanged,
401+
) -> None:
315402
self._state = state
403+
self._config_changed = config_changed
316404

317405
def set(self, count_intervals: set[int]) -> None:
318406
"""Set the count intervals in minutes.
@@ -321,6 +409,7 @@ def set(self, count_intervals: set[int]) -> None:
321409
count_intervals (set[int]): the count intervals in minutes.
322410
"""
323411
self._state.count_intervals = count_intervals
412+
self._config_changed.notify()
324413

325414

326415
class GetCountingEventOfCurrentAnalysisConfig:
@@ -347,10 +436,17 @@ class SetCountingEventInCurrentAnalysisConfig:
347436
348437
Args:
349438
state (CurrentExportConfigState): the state holding the export settings.
439+
config_changed (StaticAnalysisConfigChanged): notifies observers about
440+
the change.
350441
"""
351442

352-
def __init__(self, state: CurrentExportConfigState) -> None:
443+
def __init__(
444+
self,
445+
state: CurrentExportConfigState,
446+
config_changed: StaticAnalysisConfigChanged,
447+
) -> None:
353448
self._state = state
449+
self._config_changed = config_changed
354450

355451
def set(self, counting_event: CountingEvent) -> None:
356452
"""Set the event a road user is counted at.
@@ -359,3 +455,4 @@ def set(self, counting_event: CountingEvent) -> None:
359455
counting_event (CountingEvent): the counting event.
360456
"""
361457
self._state.counting_event = counting_event
458+
self._config_changed.notify()

‎OTAnalytics/plugin_ui/base_application.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@
165165
RemoveSection,
166166
)
167167
from OTAnalytics.application.use_cases.start_new_project import StartNewProject
168+
from OTAnalytics.application.use_cases.static_analysis_config import (
169+
StaticAnalysisConfigChanged,
170+
)
168171
from OTAnalytics.application.use_cases.suggest_save_path import SavePathSuggester
169172
from OTAnalytics.application.use_cases.track_repository import (
170173
AddAllTracks,
@@ -476,6 +479,15 @@ def current_key_prefix(self) -> CurrentKeyPrefix:
476479
def current_static_analysis_config_state(self) -> CurrentStaticAnalysisConfigState:
477480
return CurrentStaticAnalysisConfigState()
478481

482+
@cached_property
483+
def static_analysis_config_changed(self) -> StaticAnalysisConfigChanged:
484+
"""One notifier for the whole application.
485+
486+
Every use case changing a single analysis setting publishes here, so
487+
observers do not have to know which setting was changed.
488+
"""
489+
return StaticAnalysisConfigChanged()
490+
479491
@cached_property
480492
def get_current_remark(self) -> GetCurrentRemark:
481493
return GetCurrentRemark(self.remark_repository)

‎OTAnalytics/plugin_ui/gui_application.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ def register_observers(self) -> None:
117117
self.load_otconfig.register(self.view_model.update_remark_view)
118118
self.project_updater.register(self.view_model.update_quick_save_button)
119119
self.track_file_repository.register(self.view_model.update_quick_save_button)
120+
self.static_analysis_config_changed.register(
121+
self.view_model.update_quick_save_button
122+
)
120123
self.project_updater.register(self.view_model.show_current_project)
121124
self.project_updater.register(self.view_model.update_svz_metadata_view)
122125
layer_groups, layers = self.layers

‎OTAnalytics/plugin_ui/nicegui_application.py‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -265,42 +265,53 @@ def project_form(self) -> ProjectForm:
265265
def static_analysis_config_form(self) -> StaticAnalysisConfigForm:
266266
analysis_config_state = self.current_static_analysis_config_state
267267
export_config_state = analysis_config_state.export_config
268+
config_changed = self.static_analysis_config_changed
268269
return StaticAnalysisConfigForm(
269270
resource_manager=self.resource_manager,
270271
get_do_events=GetDoEventsOfCurrentAnalysisConfig(analysis_config_state),
271-
set_do_events=SetDoEventsInCurrentAnalysisConfig(analysis_config_state),
272+
set_do_events=SetDoEventsInCurrentAnalysisConfig(
273+
analysis_config_state, config_changed
274+
),
272275
get_do_counting=GetDoCountingOfCurrentAnalysisConfig(analysis_config_state),
273-
set_do_counting=SetDoCountingInCurrentAnalysisConfig(analysis_config_state),
276+
set_do_counting=SetDoCountingInCurrentAnalysisConfig(
277+
analysis_config_state, config_changed
278+
),
274279
get_save_name=GetSaveNameOfCurrentAnalysisConfig(export_config_state),
275-
set_save_name=SetSaveNameInCurrentAnalysisConfig(export_config_state),
280+
set_save_name=SetSaveNameInCurrentAnalysisConfig(
281+
export_config_state, config_changed
282+
),
276283
get_save_suffix=GetSaveSuffixOfCurrentAnalysisConfig(export_config_state),
277-
set_save_suffix=SetSaveSuffixInCurrentAnalysisConfig(export_config_state),
284+
set_save_suffix=SetSaveSuffixInCurrentAnalysisConfig(
285+
export_config_state, config_changed
286+
),
278287
get_event_formats=GetEventFormatsOfCurrentAnalysisConfig(
279288
export_config_state
280289
),
281290
set_event_formats=SetEventFormatsInCurrentAnalysisConfig(
282-
export_config_state
291+
export_config_state, config_changed
283292
),
284293
get_count_intervals=GetCountIntervalsOfCurrentAnalysisConfig(
285294
export_config_state
286295
),
287296
set_count_intervals=SetCountIntervalsInCurrentAnalysisConfig(
288-
export_config_state
297+
export_config_state, config_changed
289298
),
290299
get_counting_event=GetCountingEventOfCurrentAnalysisConfig(
291300
export_config_state
292301
),
293302
set_counting_event=SetCountingEventInCurrentAnalysisConfig(
294-
export_config_state
303+
export_config_state, config_changed
295304
),
296305
get_num_processes=GetNumProcessesOfCurrentAnalysisConfig(
297306
analysis_config_state
298307
),
299308
set_num_processes=SetNumProcessesInCurrentAnalysisConfig(
300-
analysis_config_state
309+
analysis_config_state, config_changed
301310
),
302311
get_logfile=GetLogfileOfCurrentAnalysisConfig(analysis_config_state),
303-
set_logfile=SetLogfileInCurrentAnalysisConfig(analysis_config_state),
312+
set_logfile=SetLogfileInCurrentAnalysisConfig(
313+
analysis_config_state, config_changed
314+
),
304315
)
305316

306317
@cached_property

0 commit comments

Comments
 (0)