config-cli-gui 0.3.4__tar.gz → 0.3.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/workflows/main.yml +1 -1
  2. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/HISTORY.md +26 -0
  3. {config_cli_gui-0.3.4/src/config_cli_gui.egg-info → config_cli_gui-0.3.6}/PKG-INFO +1 -1
  4. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/config.yaml +18 -24
  5. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/usage/config.md +20 -23
  6. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/_version.py +3 -3
  7. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/config.py +116 -0
  8. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/gui.py +65 -16
  9. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/logging.py +80 -6
  10. config_cli_gui-0.3.6/src/config_cli_gui/persistence.py +75 -0
  11. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6/src/config_cli_gui.egg-info}/PKG-INFO +1 -1
  12. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/SOURCES.txt +3 -1
  13. config_cli_gui-0.3.6/src/config_cli_gui.egg-info/scm_file_list.json +80 -0
  14. config_cli_gui-0.3.6/src/config_cli_gui.egg-info/scm_version.json +8 -0
  15. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/cli/cli_example.py +10 -5
  16. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/config/config_example.py +12 -83
  17. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/gui/gui_example.py +60 -10
  18. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/test_cli.py +2 -2
  19. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/test_config_manager.py +1 -1
  20. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/test_generic_cli.py +5 -5
  21. config_cli_gui-0.3.4/tests/example_project/gui/config.yaml +0 -48
  22. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/FUNDING.yml +0 -0
  23. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  24. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  25. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  26. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/actions/setup-environment/action.yml +0 -0
  27. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/dependabot.yml +0 -0
  28. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/init.sh +0 -0
  29. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/release_message.sh +0 -0
  30. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/update_funding.py +0 -0
  31. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/workflows/release.yml +0 -0
  32. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/workflows/update_readme.yml +0 -0
  33. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.gitignore +0 -0
  34. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.idea/runConfigurations/config_generate.xml +0 -0
  35. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.idea/runConfigurations/example_project_cli.xml +0 -0
  36. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.idea/runConfigurations/example_project_gui.xml +0 -0
  37. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.pre-commit-config.yaml +0 -0
  38. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.readthedocs.yaml +0 -0
  39. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/LICENSE +0 -0
  40. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/Makefile +0 -0
  41. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/README.md +0 -0
  42. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/.nav.yml +0 -0
  43. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/_static/img/favicon.png +0 -0
  44. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/_static/img/logo.png +0 -0
  45. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/_static/img/settings_dlg.png +0 -0
  46. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/_static/img/settings_doc.png +0 -0
  47. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/css/custom.css +0 -0
  48. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/develop/contributing.md +0 -0
  49. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/develop/make_windows.md +0 -0
  50. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/develop/naming_convention.md +0 -0
  51. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/funding/funding.md +0 -0
  52. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/getting-started/install.md +0 -0
  53. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/getting-started/virtual-environment.md +0 -0
  54. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/index.md +0 -0
  55. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/usage/cli.md +0 -0
  56. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/mkdocs.yml +0 -0
  57. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/pyproject.toml +0 -0
  58. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/scripts/show_filelist.ps1 +0 -0
  59. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/scripts/show_tree.ps1 +0 -0
  60. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/scripts/show_tree.py +0 -0
  61. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/scripts/update_readme.py +0 -0
  62. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/setup.cfg +0 -0
  63. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/__init__.py +0 -0
  64. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/__init__.py +0 -0
  65. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/cli.py +0 -0
  66. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/configtypes/__init__.py +0 -0
  67. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/configtypes/color.py +0 -0
  68. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/configtypes/font.py +0 -0
  69. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/configtypes/vector.py +0 -0
  70. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/docs.py +0 -0
  71. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/dependency_links.txt +0 -0
  72. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/entry_points.txt +0 -0
  73. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/requires.txt +0 -0
  74. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/top_level.txt +0 -0
  75. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/template.yml.url +0 -0
  76. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/__init__.py +0 -0
  77. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/__init__.py +0 -0
  78. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/__main__.py +0 -0
  79. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/cli/__init__.py +0 -0
  80. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/cli/__main__.py +0 -0
  81. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/config/__init__.py +0 -0
  82. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/core/__init__.py +0 -0
  83. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/core/base.py +0 -0
  84. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/example.gpx +0 -0
  85. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/gui/__init__.py +0 -0
  86. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/gui/__main__.py +0 -0
  87. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/test_docs.py +0 -0
  88. {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/uv.lock +0 -0
@@ -63,7 +63,7 @@ jobs:
63
63
  fail-fast: false
64
64
  matrix:
65
65
  python-version: [ 3.11 ]
66
- os: [ ubuntu-latest, macos-latest ]
66
+ os: [ ubuntu-latest ]
67
67
  runs-on: ${{ matrix.os }}
68
68
  steps:
69
69
  - uses: actions/checkout@v4
@@ -4,6 +4,32 @@ Changelog
4
4
 
5
5
  (unreleased)
6
6
  ------------
7
+ - Dynamically set app name for config persistence. [Paul Magister]
8
+ - Dynamically set app name for config persistence. [Paul Magister]
9
+
10
+
11
+ 0.3.5 (2026-06-18)
12
+ ------------------
13
+ - Docs: Update HISTORY.md for release 0.3.5. [Paul Magister]
14
+ - Improve gui. [Paul Magister]
15
+ - Fix gui log issue: even better fix. [Paul Magister]
16
+ - Fix gui log issue: [Paul Magister]
17
+
18
+ Previously the logger handler attempted to call the passed object like a function; GuiLogWriter provides write(), not a call. Because emit() swallowed exceptions, you saw nothing in the GUI log.
19
+ Now connect_gui_logging detects objects with .write() and registers the writer.write callable with the UI handler. GuiLogHandler.emit calls that callable, which writes to the text widget and the text appears.
20
+ - Improve config consistency: correctly consider logging parameters.
21
+ [Paul Magister]
22
+ - Improve config usage consistency: allways load last config. Move
23
+ AppConfig into config.py since it is used in the core lib component.
24
+ [Paul Magister]
25
+ - Save last opened config to start there on the next session, move theme
26
+ to app category. [Paul Magister]
27
+ - Do not run tests on macos. [Paul Magister]
28
+
29
+
30
+ 0.3.4 (2026-06-18)
31
+ ------------------
32
+ - Docs: Update HISTORY.md for release 0.3.4. [Paul Magister]
7
33
  - Bugfix: init color display properly. [Paul Magister]
8
34
  - Use ttkbootstrap gui styles. [Paul Magister]
9
35
  - Improve GUI scaling and font sizes. [Paul Magister]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: config-cli-gui
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: Feature-rich Python project template for config-cli-gui.
5
5
  Author: pamagister
6
6
  Requires-Python: <3.12,>=3.11
@@ -1,3 +1,16 @@
1
+ app:
2
+ # Date format to use | type=str
3
+ date_format: '%Y-%m-%d'
4
+ # Logging level for the application | type=str | choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
5
+ log_level: INFO
6
+ # Maximum log file size in MB before rotation | type=int
7
+ log_file_max_size: 2
8
+ # Enable logging to file | type=bool | choices=[True, False]
9
+ enable_file_logging: true
10
+ # Enable logging to console | type=bool | choices=[True, False]
11
+ enable_console_logging: true
12
+ # GUI theme setting supported by ttkbootstrap | type=str | choices=['cosmo', 'flatly', 'litera', 'minty', 'lumen', 'sandstone', 'yeti', 'pulse', 'united', 'darkly', 'superhero', 'solar', 'cyborg', 'vapor', 'simplex']
13
+ theme: superhero
1
14
  cli:
2
15
  # Path to input (file or folder) | type=str | [CLI]
3
16
  input: ''
@@ -9,26 +22,7 @@ cli:
9
22
  extract_waypoints: true
10
23
  # Include elevation data in waypoints | type=bool | [CLI] | choices=[True, False]
11
24
  elevation: false
12
- app:
13
- # Date format to use | type=str
14
- date_format: '%Y-%m-%d'
15
- # Logging level for the application | type=str | choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
16
- log_level: INFO
17
- # Maximum log file size in MB before rotation | type=int
18
- log_file_max_size: 10
19
- # Number of backup log files to keep | type=int
20
- log_backup_count: 5
21
- # Log message format style | type=str | choices=['simple', 'detailed', 'json']
22
- log_format: detailed
23
- # Maximum number of worker threads | type=int
24
- max_workers: 4
25
- # Enable logging to file | type=bool | choices=[True, False]
26
- enable_file_logging: true
27
- # Enable logging to console | type=bool | choices=[True, False]
28
- enable_console_logging: true
29
25
  gui:
30
- # GUI theme setting suppoerted by ttkbootstrap | type=str | choices=['cosmo', 'flatly', 'litera', 'minty', 'lumen', 'sandstone', 'yeti', 'pulse', 'united', 'darkly', 'superhero', 'solar', 'cyborg', 'vapor', 'simplex']
31
- theme: darkly
32
26
  # Default window width | type=int
33
27
  window_width: 800
34
28
  # Default window height | type=int
@@ -40,21 +34,21 @@ gui:
40
34
  # Maximum number of log lines to keep in GUI | type=int
41
35
  max_log_lines: 1000
42
36
  # Point in 2D space | type=Vector
43
- point2D: (7, 11)
37
+ point2D: (7.0, 11.0)
44
38
  # Point in 3D space | type=Vector
45
39
  point3D: (1.2, 3.4, 5.6)
46
40
  misc:
47
41
  # Example integer | type=int
48
42
  some_numeric: 42
49
43
  # Example vector 2D | type=Vector
50
- some_vector2d: (1, 2)
44
+ some_vector2d: (1.0, 2.0)
51
45
  # Example vector 3D | type=Vector
52
46
  some_vector3d: (1.1, 2.2, 3.3)
53
- # Path to the file to use | type=PosixPath
47
+ # Path to the file to use | type=str
54
48
  some_file: some_file.txt
55
49
  # Color setting for the application | type=Color
56
50
  some_color: '#ff0000'
57
51
  # Date setting for the application | type=datetime
58
- some_date: '2025-12-31T10:30:45'
52
+ some_date: '2025-12-31T10:30:00'
59
53
  # Font setting for the application | type=Font
60
- some_font: 'DejaVuSans.ttf, 12, #0000ff'
54
+ some_font: 'DejaVuSans.ttf, 12.0, #0000ff'
@@ -3,6 +3,17 @@
3
3
  These parameters are available to configure the behavior of your application.
4
4
  The parameters in the cli category can be accessed via the command line interface.
5
5
 
6
+ ## Category "app"
7
+
8
+ | Name | Type | Description | Default | Choices |
9
+ |------------------------|------|---------------------------------------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
10
+ | date_format | str | Date format to use | '%Y-%m-%d' | - |
11
+ | log_level | str | Logging level for the application | 'INFO' | ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] |
12
+ | log_file_max_size | int | Maximum log file size in MB before rotation | 2 | - |
13
+ | enable_file_logging | bool | Enable logging to file | True | [True, False] |
14
+ | enable_console_logging | bool | Enable logging to console | True | [True, False] |
15
+ | theme | str | GUI theme setting supported by ttkbootstrap | 'darkly' | ['cosmo', 'flatly', 'litera', 'minty', 'lumen', 'sandstone', 'yeti', 'pulse', 'united', 'darkly', 'superhero', 'solar', 'cyborg', 'vapor', 'simplex'] |
16
+
6
17
  ## Category "cli"
7
18
 
8
19
  | Name | Type | Description | Default | Choices |
@@ -13,31 +24,17 @@ The parameters in the cli category can be accessed via the command line interfac
13
24
  | extract_waypoints | bool | Extract starting points of each track as waypoint | True | [True, False] |
14
25
  | elevation | bool | Include elevation data in waypoints | False | [True, False] |
15
26
 
16
- ## Category "app"
17
-
18
- | Name | Type | Description | Default | Choices |
19
- |------------------------|------|---------------------------------------------|------------|---------------------------------------------------|
20
- | date_format | str | Date format to use | '%Y-%m-%d' | - |
21
- | log_level | str | Logging level for the application | 'INFO' | ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] |
22
- | log_file_max_size | int | Maximum log file size in MB before rotation | 10 | - |
23
- | log_backup_count | int | Number of backup log files to keep | 5 | - |
24
- | log_format | str | Log message format style | 'detailed' | ['simple', 'detailed', 'json'] |
25
- | max_workers | int | Maximum number of worker threads | 4 | - |
26
- | enable_file_logging | bool | Enable logging to file | True | [True, False] |
27
- | enable_console_logging | bool | Enable logging to console | True | [True, False] |
28
-
29
27
  ## Category "gui"
30
28
 
31
- | Name | Type | Description | Default | Choices |
32
- |-------------------|--------|------------------------------------------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
33
- | theme | str | GUI theme setting suppoerted by ttkbootstrap | 'darkly' | ['cosmo', 'flatly', 'litera', 'minty', 'lumen', 'sandstone', 'yeti', 'pulse', 'united', 'darkly', 'superhero', 'solar', 'cyborg', 'vapor', 'simplex'] |
34
- | window_width | int | Default window width | 800 | - |
35
- | window_height | int | Default window height | 600 | - |
36
- | log_window_height | int | Height of the log window in pixels | 200 | - |
37
- | auto_scroll_log | bool | Automatically scroll to the newest log entries | True | [True, False] |
38
- | max_log_lines | int | Maximum number of log lines to keep in GUI | 1000 | - |
39
- | point2D | Vector | Point in 2D space | Vector(7, 11) | - |
40
- | point3D | Vector | Point in 3D space | Vector(1.2, 3.4, 5.6) | - |
29
+ | Name | Type | Description | Default | Choices |
30
+ |-------------------|--------|------------------------------------------------|-----------------------|---------------|
31
+ | window_width | int | Default window width | 800 | - |
32
+ | window_height | int | Default window height | 600 | - |
33
+ | log_window_height | int | Height of the log window in pixels | 200 | - |
34
+ | auto_scroll_log | bool | Automatically scroll to the newest log entries | True | [True, False] |
35
+ | max_log_lines | int | Maximum number of log lines to keep in GUI | 1000 | - |
36
+ | point2D | Vector | Point in 2D space | Vector(7, 11) | - |
37
+ | point3D | Vector | Point in 3D space | Vector(1.2, 3.4, 5.6) | - |
41
38
 
42
39
  ## Category "misc"
43
40
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.3.4'
22
- __version_tuple__ = version_tuple = (0, 3, 4)
21
+ __version__ = version = '0.3.6'
22
+ __version_tuple__ = version_tuple = (0, 3, 6)
23
23
 
24
- __commit_id__ = commit_id = 'gf109662f6'
24
+ __commit_id__ = commit_id = 'g7d530e99f'
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import logging
2
3
  from abc import ABC, abstractmethod
3
4
  from collections.abc import Iterable
4
5
  from dataclasses import dataclass
@@ -9,6 +10,7 @@ from typing import Any
9
10
  import yaml
10
11
  from pydantic import BaseModel
11
12
 
13
+ from config_cli_gui import persistence
12
14
  from config_cli_gui.configtypes.color import Color
13
15
  from config_cli_gui.configtypes.font import Font
14
16
  from config_cli_gui.configtypes.vector import Vector
@@ -108,6 +110,74 @@ class ConfigSerializer:
108
110
  return value
109
111
 
110
112
 
113
+ class AppConfig(ConfigCategory):
114
+ """Application-specific configuration parameters (centralized).
115
+
116
+ This category was moved from example project to the core library so that
117
+ every project uses a consistent, non-editable application configuration
118
+ section. It contains general application settings such as logging and
119
+ theme preferences.
120
+ """
121
+
122
+ def get_category_name(self) -> str:
123
+ return "app"
124
+
125
+ date_format: ConfigParameter = ConfigParameter(
126
+ name="date_format",
127
+ value="%Y-%m-%d",
128
+ help="Date format to use",
129
+ )
130
+
131
+ log_level: ConfigParameter = ConfigParameter(
132
+ name="log_level",
133
+ value="INFO",
134
+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
135
+ help="Logging level for the application",
136
+ )
137
+
138
+ log_file_max_size: ConfigParameter = ConfigParameter(
139
+ name="log_file_max_size",
140
+ value=2,
141
+ help="Maximum log file size in MB before rotation",
142
+ )
143
+
144
+ enable_file_logging: ConfigParameter = ConfigParameter(
145
+ name="enable_file_logging",
146
+ value=True,
147
+ help="Enable logging to file",
148
+ )
149
+
150
+ enable_console_logging: ConfigParameter = ConfigParameter(
151
+ name="enable_console_logging",
152
+ value=True,
153
+ help="Enable logging to console",
154
+ )
155
+
156
+ # ttkbootstrap.Style().theme_names()
157
+ theme: ConfigParameter = ConfigParameter(
158
+ name="theme",
159
+ value="darkly",
160
+ choices=[
161
+ "cosmo",
162
+ "flatly",
163
+ "litera",
164
+ "minty",
165
+ "lumen",
166
+ "sandstone",
167
+ "yeti",
168
+ "pulse",
169
+ "united",
170
+ "darkly",
171
+ "superhero",
172
+ "solar",
173
+ "cyborg",
174
+ "vapor",
175
+ "simplex",
176
+ ],
177
+ help="GUI theme setting supported by ttkbootstrap",
178
+ )
179
+
180
+
111
181
  class ConfigManager:
112
182
  """Manages loading, saving, and accessing configuration categories."""
113
183
 
@@ -119,7 +189,11 @@ class ConfigManager:
119
189
  ):
120
190
  self._categories: dict[str, ConfigCategory] = {}
121
191
  self._serializer = ConfigSerializer()
192
+ self.app: AppConfig = AppConfig()
193
+ # provide a default hook for application name retrieval; projects may
194
+ # override `get_app_name` on their concrete ConfigManager subclass.
122
195
 
196
+ categories = (self.app, *categories)
123
197
  for category in categories:
124
198
  if not isinstance(category, ConfigCategory):
125
199
  raise TypeError(f"Expected ConfigCategory instance, got {type(category)}")
@@ -130,6 +204,14 @@ class ConfigManager:
130
204
 
131
205
  self.apply_overrides(overrides)
132
206
 
207
+ def get_app_name(self) -> str:
208
+ """Return the application identifier used by persistence helpers.
209
+
210
+ Projects can override this method in their concrete ConfigManager to
211
+ provide a different name "my-app-name"
212
+ """
213
+ return "config-cli-gui"
214
+
133
215
  def add_category(self, name: str, category: ConfigCategory) -> None:
134
216
  """Register a new configuration category."""
135
217
  self._categories[name] = category
@@ -167,6 +249,18 @@ class ConfigManager:
167
249
  data = yaml.safe_load(f) if path.suffix.lower() in [".yml", ".yaml"] else json.load(f)
168
250
 
169
251
  self._apply_config_data(data)
252
+ # Persist the information about the last used configuration file so the
253
+ # application can restore the same config on next start.
254
+ try:
255
+ persistence.write_last_used_config(self.get_app_name(), str(path))
256
+ except Exception:
257
+ # Persistence should not break normal config loading.
258
+ pass
259
+ try:
260
+ logging.getLogger("config_cli_gui").info(f"Loaded configuration from: {path}")
261
+ except Exception:
262
+ # Non-fatal: logging may not be configured yet.
263
+ pass
170
264
 
171
265
  def _apply_config_data(self, data: dict[str, Any]) -> None:
172
266
  """Apply loaded data to the configuration parameters."""
@@ -205,6 +299,28 @@ class ConfigManager:
205
299
  if file_format == "yaml":
206
300
  self._append_comments_to_yaml(path)
207
301
 
302
+ # Record the saved config as the last-used config as well.
303
+ try:
304
+ persistence.write_last_used_config(self.get_app_name(), str(path))
305
+ except Exception:
306
+ # Ignore persistence failures when saving
307
+ pass
308
+ try:
309
+ logging.getLogger("config_cli_gui").info(f"Saved configuration to: {path}")
310
+ except Exception:
311
+ # Non-fatal: logging may not be configured yet.
312
+ pass
313
+
314
+ def get_last_used_config(self) -> str | None:
315
+ """Return the last used config file path recorded by the persistence layer.
316
+
317
+ This is a convenience wrapper around persistence.read_last_used_config.
318
+ """
319
+ try:
320
+ return persistence.read_last_used_config(self.get_app_name())
321
+ except Exception:
322
+ return None
323
+
208
324
  def to_dict(self) -> dict[str, Any]:
209
325
  """Convert all configuration categories to a dictionary."""
210
326
  result: dict[str, dict[str, Any]] = {}
@@ -155,8 +155,12 @@ class CalendarDialog:
155
155
  button_frame = ttk.Frame(main_frame)
156
156
  button_frame.pack(fill=tk.X, pady=(10, 0))
157
157
 
158
- ttk.Button(button_frame, text="OK", command=self._on_ok).pack(side=tk.RIGHT, padx=(5, 0))
159
- ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(side=tk.RIGHT)
158
+ ttk.Button(button_frame, text="OK", command=self._on_ok, width=10).pack(
159
+ side=tk.RIGHT, padx=(5, 0)
160
+ )
161
+ ttk.Button(button_frame, text="Cancel", command=self._on_cancel, width=10).pack(
162
+ side=tk.RIGHT
163
+ )
160
164
 
161
165
  def _update_calendar(self):
162
166
  """Update day spinbox based on selected month/year."""
@@ -213,7 +217,7 @@ class GenericSettingsDialog:
213
217
  config_file="config.yaml",
214
218
  ):
215
219
  self.parent = parent
216
- self.config_manager = config_manager
220
+ self.config_manager: ConfigManager = config_manager
217
221
  self.config_file = config_file
218
222
  self.result = None
219
223
  self.widgets = {}
@@ -252,11 +256,15 @@ class GenericSettingsDialog:
252
256
  button_frame.pack(fill=tk.X, pady=(10, 0))
253
257
 
254
258
  # Apply: apply current settings (in-memory) but keep dialog open
255
- ttk.Button(button_frame, text="Apply", command=self._on_apply).pack(
259
+ ttk.Button(button_frame, text="Apply", command=self._on_apply, width=10).pack(
256
260
  side=tk.RIGHT, padx=(5, 0)
257
261
  )
258
- ttk.Button(button_frame, text="OK", command=self._on_ok).pack(side=tk.RIGHT, padx=(5, 0))
259
- ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(side=tk.RIGHT)
262
+ ttk.Button(button_frame, text="OK", command=self._on_ok, width=10).pack(
263
+ side=tk.RIGHT, padx=(5, 0)
264
+ )
265
+ ttk.Button(button_frame, text="Cancel", command=self._on_cancel, width=10).pack(
266
+ side=tk.RIGHT
267
+ )
260
268
 
261
269
  def _create_category_tab(self, category_name: str, category):
262
270
  """Create a tab for a configuration category."""
@@ -297,13 +305,13 @@ class GenericSettingsDialog:
297
305
  continue
298
306
  """
299
307
 
300
- # Create label
301
- label = ttk.Label(parent, text=f"{param.name}:")
302
- label.grid(row=row, column=0, sticky="w", padx=5, pady=2)
308
+ # Create label (fixed width for alignment)
309
+ label = ttk.Label(parent, text=f"{param.name}:", width=28, anchor="w")
310
+ label.grid(row=row, column=0, sticky="w", padx=5, pady=4)
303
311
 
304
312
  # Create appropriate widget based on parameter type
305
313
  widget = self._create_parameter_widget(parent, param)
306
- widget.grid(row=row, column=1, sticky="ew", padx=5, pady=2)
314
+ widget.grid(row=row, column=1, sticky="ew", padx=5, pady=4)
307
315
 
308
316
  # Add tooltip
309
317
  ToolTip(label, param.help)
@@ -407,10 +415,10 @@ class GenericSettingsDialog:
407
415
  if path:
408
416
  var.set(path)
409
417
 
410
- browse_btn = ttk.Button(frame, text="File", command=browse_file)
418
+ browse_btn = ttk.Button(frame, text="File", command=browse_file, width=10)
411
419
  browse_btn.pack(side=tk.RIGHT, padx=(5, 0))
412
420
 
413
- browse_btn = ttk.Button(frame, text="Directory", command=browse_dir)
421
+ browse_btn = ttk.Button(frame, text="Directory", command=browse_dir, width=10)
414
422
  browse_btn.pack(side=tk.RIGHT, padx=(5, 0))
415
423
 
416
424
  frame.var = var
@@ -436,7 +444,7 @@ class GenericSettingsDialog:
436
444
  var.set(color[1])
437
445
  color_display.config(bg=color[1])
438
446
 
439
- pick_btn = ttk.Button(frame, text="Pick", command=pick_color)
447
+ pick_btn = ttk.Button(frame, text="Pick", command=pick_color, width=10)
440
448
  pick_btn.pack(side=tk.LEFT, padx=(5, 0))
441
449
  color_display.config(bg=color_value.to_hex())
442
450
 
@@ -485,7 +493,7 @@ class GenericSettingsDialog:
485
493
  color_var.set(color[1])
486
494
  color_display.config(bg=color[1])
487
495
 
488
- pick_btn = ttk.Button(frame, text="Pick Color", command=pick_color)
496
+ pick_btn = ttk.Button(frame, text="Pick Color", command=pick_color, width=10)
489
497
  pick_btn.pack(side=tk.LEFT, padx=(5, 0))
490
498
 
491
499
  def on_color_change(*args):
@@ -531,7 +539,7 @@ class GenericSettingsDialog:
531
539
  img_label.image = photo
532
540
  img_label.pack()
533
541
 
534
- preview_btn = ttk.Button(frame, text="Preview", command=show_preview)
542
+ preview_btn = ttk.Button(frame, text="Preview", command=show_preview, width=10)
535
543
  preview_btn.pack(side=tk.LEFT, padx=(5, 0))
536
544
 
537
545
  # Store variables in the frame for later access
@@ -584,7 +592,7 @@ class GenericSettingsDialog:
584
592
  if dialog.result:
585
593
  var.set(dialog.result.strftime("%Y-%m-%d %H:%M"))
586
594
 
587
- cal_btn = ttk.Button(frame, text="Calendar", command=pick_datetime)
595
+ cal_btn = ttk.Button(frame, text="Calendar", command=pick_datetime, width=10)
588
596
  cal_btn.pack(side=tk.RIGHT, padx=(5, 0))
589
597
 
590
598
  frame.var = var
@@ -636,7 +644,17 @@ class GenericSettingsDialog:
636
644
  def _on_ok(self):
637
645
  """Handle OK button click."""
638
646
  try:
647
+ # Remember old theme to detect changes
648
+ try:
649
+ old_theme = self.config_manager.app.theme.value
650
+ except Exception:
651
+ old_theme = None
652
+
639
653
  self.__persist_settings()
654
+
655
+ # Apply GUI updates (theme change etc.) after persisting
656
+ self._apply_gui_updates(old_theme)
657
+
640
658
  self.dialog.destroy()
641
659
 
642
660
  except Exception as e:
@@ -698,6 +716,30 @@ class GenericSettingsDialog:
698
716
 
699
717
  self.result = "ok"
700
718
 
719
+ def _apply_gui_updates(self, old_theme=None):
720
+ """Apply GUI-level updates that should happen after settings are persisted.
721
+
722
+ Currently this changes the ttkbootstrap theme if it was modified. The
723
+ logic is centralized here so it can be called from both Apply and OK
724
+ handlers.
725
+ """
726
+ try:
727
+ new_theme = None
728
+ try:
729
+ new_theme = self.config_manager.app.theme.value
730
+ except Exception:
731
+ new_theme = None
732
+
733
+ if new_theme and new_theme != old_theme:
734
+ try:
735
+ ttkbootstrap.Style().theme_use(new_theme)
736
+ except Exception:
737
+ # Best-effort; don't crash the settings dialog if theme switch fails
738
+ pass
739
+ except Exception:
740
+ # Swallow any unexpected errors to keep UI responsive
741
+ pass
742
+
701
743
  def _on_cancel(self):
702
744
  """Handle Cancel button click."""
703
745
  self.result = "cancel"
@@ -710,7 +752,14 @@ class GenericSettingsDialog:
710
752
  applies the theme change live if the GUI theme parameter is present.
711
753
  """
712
754
  try:
755
+ # Remember current theme, persist settings and apply GUI updates
756
+ try:
757
+ old_theme = self.config_manager.app.theme.value
758
+ except Exception:
759
+ old_theme = None
760
+
713
761
  self.__persist_settings()
762
+ self._apply_gui_updates(old_theme)
714
763
 
715
764
  except Exception as e:
716
765
  messagebox.showerror("Error", f"Failed to save configuration: {e}")