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.
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/workflows/main.yml +1 -1
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/HISTORY.md +26 -0
- {config_cli_gui-0.3.4/src/config_cli_gui.egg-info → config_cli_gui-0.3.6}/PKG-INFO +1 -1
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/config.yaml +18 -24
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/usage/config.md +20 -23
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/_version.py +3 -3
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/config.py +116 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/gui.py +65 -16
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/logging.py +80 -6
- config_cli_gui-0.3.6/src/config_cli_gui/persistence.py +75 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6/src/config_cli_gui.egg-info}/PKG-INFO +1 -1
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/SOURCES.txt +3 -1
- config_cli_gui-0.3.6/src/config_cli_gui.egg-info/scm_file_list.json +80 -0
- config_cli_gui-0.3.6/src/config_cli_gui.egg-info/scm_version.json +8 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/cli/cli_example.py +10 -5
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/config/config_example.py +12 -83
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/gui/gui_example.py +60 -10
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/test_cli.py +2 -2
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/test_config_manager.py +1 -1
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/test_generic_cli.py +5 -5
- config_cli_gui-0.3.4/tests/example_project/gui/config.yaml +0 -48
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/FUNDING.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/actions/setup-environment/action.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/dependabot.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/init.sh +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/release_message.sh +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/update_funding.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/workflows/release.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.github/workflows/update_readme.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.gitignore +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.idea/runConfigurations/config_generate.xml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.idea/runConfigurations/example_project_cli.xml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.idea/runConfigurations/example_project_gui.xml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.pre-commit-config.yaml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/.readthedocs.yaml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/LICENSE +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/Makefile +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/README.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/.nav.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/_static/img/favicon.png +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/_static/img/logo.png +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/_static/img/settings_dlg.png +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/_static/img/settings_doc.png +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/css/custom.css +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/develop/contributing.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/develop/make_windows.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/develop/naming_convention.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/funding/funding.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/getting-started/install.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/getting-started/virtual-environment.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/index.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/docs/usage/cli.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/mkdocs.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/pyproject.toml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/scripts/show_filelist.ps1 +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/scripts/show_tree.ps1 +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/scripts/show_tree.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/scripts/update_readme.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/setup.cfg +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/cli.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/configtypes/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/configtypes/color.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/configtypes/font.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/configtypes/vector.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui/docs.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/dependency_links.txt +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/entry_points.txt +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/requires.txt +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/src/config_cli_gui.egg-info/top_level.txt +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/template.yml.url +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/__main__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/cli/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/cli/__main__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/config/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/core/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/core/base.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/example.gpx +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/gui/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/example_project/gui/__main__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/tests/test_docs.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.6}/uv.lock +0 -0
|
@@ -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,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=
|
|
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:
|
|
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
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 6)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
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(
|
|
159
|
-
|
|
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(
|
|
259
|
-
|
|
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=
|
|
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=
|
|
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}")
|