backlogops-gui 0.9__tar.gz → 1.1__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.
- {backlogops_gui-0.9 → backlogops_gui-1.1}/PKG-INFO +14 -11
- {backlogops_gui-0.9 → backlogops_gui-1.1}/README_pypi.md +11 -8
- {backlogops_gui-0.9 → backlogops_gui-1.1}/backlogops_gui.egg-info/PKG-INFO +14 -11
- {backlogops_gui-0.9 → backlogops_gui-1.1}/backlogops_gui.egg-info/requires.txt +2 -2
- {backlogops_gui-0.9 → backlogops_gui-1.1}/setup.py +3 -3
- backlogops_gui-1.1/src/backlogops_gui/config_edit.py +265 -0
- backlogops_gui-0.9/src/backlogops_gui/config_edit.py +0 -226
- {backlogops_gui-0.9 → backlogops_gui-1.1}/LICENSE.txt +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/backlogops_gui.egg-info/SOURCES.txt +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/backlogops_gui.egg-info/dependency_links.txt +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/backlogops_gui.egg-info/entry_points.txt +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/backlogops_gui.egg-info/top_level.txt +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/pyproject.toml +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/setup.cfg +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/__init__.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/__main__.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/_migrate_warn.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/application.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/backlog_actions.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/backlog_dialogs.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/backlog_io.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/backlog_window.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/blog_version_reporter.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/choice_dialogs.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/file_choosers.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/format_dialogs.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/jira_actions.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/jira_base.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/jira_dialogs.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/jira_order.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/jira_rank.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/jira_read.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/jira_rename.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/jira_update.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/jira_write.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/key_list_box.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/log_buffer.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/modal_dialog.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/py.typed +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/python_version.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/report_windows.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/table_view.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/tcltk_version.py +0 -0
- {backlogops_gui-0.9 → backlogops_gui-1.1}/src/backlogops_gui/token_dialog.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops-gui
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1
|
|
4
4
|
Summary: Graphical user interface for backlog operations.
|
|
5
5
|
Author: Tom Björkholm
|
|
6
6
|
Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
|
|
@@ -21,11 +21,11 @@ Classifier: Typing :: Typed
|
|
|
21
21
|
Requires-Python: >=3.12
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE.txt
|
|
24
|
-
Requires-Dist: backlogops>=
|
|
24
|
+
Requires-Dist: backlogops>=1.1
|
|
25
25
|
Requires-Dist: argcomplete>=3.7.2
|
|
26
26
|
Requires-Dist: wizard-ui-bridge>=1.3
|
|
27
27
|
Requires-Dist: wizard-tk-bridge>=1.3
|
|
28
|
-
Requires-Dist: edit-cfg-json-tk
|
|
28
|
+
Requires-Dist: edit-cfg-json-tk>=0.1.0
|
|
29
29
|
Requires-Dist: versionreporter>=0.4
|
|
30
30
|
Dynamic: author
|
|
31
31
|
Dynamic: license-file
|
|
@@ -286,12 +286,15 @@ is in the menus.
|
|
|
286
286
|
closed for vacation, and preset configuration for the inputs or
|
|
287
287
|
outputs you want to use.
|
|
288
288
|
|
|
289
|
-
- Edit configuration...: Show the whole configuration in a folding
|
|
290
|
-
editor, so you can change one value without stepping
|
|
291
|
-
wizard. It asks whether to edit the configuration the
|
|
292
|
-
using or one in a file you pick.
|
|
293
|
-
|
|
294
|
-
|
|
289
|
+
- Edit configuration...: Show the whole configuration in a folding,
|
|
290
|
+
searchable editor, so you can change one value without stepping
|
|
291
|
+
through the wizard. It asks whether to edit the configuration the
|
|
292
|
+
application is using or one in a file you pick. A preset, a Jira
|
|
293
|
+
connection, a person, a team, a level and an entry of any of the maps
|
|
294
|
+
can be added beside the ones that are there, taken out again, and
|
|
295
|
+
moved within a list. Saving validates the configuration first, keeps
|
|
296
|
+
what it writes over as a `.bak` file, and makes what it wrote the
|
|
297
|
+
configuration in use.
|
|
295
298
|
|
|
296
299
|
- Create IO preset file...: Create a stand-alone input or output preset
|
|
297
300
|
configuration file via a wizard.
|
|
@@ -362,10 +365,10 @@ one with the list of releases. You will want to use the menus.
|
|
|
362
365
|
|
|
363
366
|
## Test summary
|
|
364
367
|
|
|
365
|
-
- Test result:
|
|
368
|
+
- Test result: 2298 passed, 1 deselected in 57s
|
|
366
369
|
- No flake8 warnings.
|
|
367
370
|
- No mypy errors found.
|
|
368
371
|
- No pylint warnings.
|
|
369
372
|
- No python layout warnings.
|
|
370
|
-
- Built version(s):
|
|
373
|
+
- Built version(s): 1.1
|
|
371
374
|
- Build and test using Python 3.14.7
|
|
@@ -252,12 +252,15 @@ is in the menus.
|
|
|
252
252
|
closed for vacation, and preset configuration for the inputs or
|
|
253
253
|
outputs you want to use.
|
|
254
254
|
|
|
255
|
-
- Edit configuration...: Show the whole configuration in a folding
|
|
256
|
-
editor, so you can change one value without stepping
|
|
257
|
-
wizard. It asks whether to edit the configuration the
|
|
258
|
-
using or one in a file you pick.
|
|
259
|
-
|
|
260
|
-
|
|
255
|
+
- Edit configuration...: Show the whole configuration in a folding,
|
|
256
|
+
searchable editor, so you can change one value without stepping
|
|
257
|
+
through the wizard. It asks whether to edit the configuration the
|
|
258
|
+
application is using or one in a file you pick. A preset, a Jira
|
|
259
|
+
connection, a person, a team, a level and an entry of any of the maps
|
|
260
|
+
can be added beside the ones that are there, taken out again, and
|
|
261
|
+
moved within a list. Saving validates the configuration first, keeps
|
|
262
|
+
what it writes over as a `.bak` file, and makes what it wrote the
|
|
263
|
+
configuration in use.
|
|
261
264
|
|
|
262
265
|
- Create IO preset file...: Create a stand-alone input or output preset
|
|
263
266
|
configuration file via a wizard.
|
|
@@ -328,10 +331,10 @@ one with the list of releases. You will want to use the menus.
|
|
|
328
331
|
|
|
329
332
|
## Test summary
|
|
330
333
|
|
|
331
|
-
- Test result:
|
|
334
|
+
- Test result: 2298 passed, 1 deselected in 57s
|
|
332
335
|
- No flake8 warnings.
|
|
333
336
|
- No mypy errors found.
|
|
334
337
|
- No pylint warnings.
|
|
335
338
|
- No python layout warnings.
|
|
336
|
-
- Built version(s):
|
|
339
|
+
- Built version(s): 1.1
|
|
337
340
|
- Build and test using Python 3.14.7
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops-gui
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1
|
|
4
4
|
Summary: Graphical user interface for backlog operations.
|
|
5
5
|
Author: Tom Björkholm
|
|
6
6
|
Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
|
|
@@ -21,11 +21,11 @@ Classifier: Typing :: Typed
|
|
|
21
21
|
Requires-Python: >=3.12
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE.txt
|
|
24
|
-
Requires-Dist: backlogops>=
|
|
24
|
+
Requires-Dist: backlogops>=1.1
|
|
25
25
|
Requires-Dist: argcomplete>=3.7.2
|
|
26
26
|
Requires-Dist: wizard-ui-bridge>=1.3
|
|
27
27
|
Requires-Dist: wizard-tk-bridge>=1.3
|
|
28
|
-
Requires-Dist: edit-cfg-json-tk
|
|
28
|
+
Requires-Dist: edit-cfg-json-tk>=0.1.0
|
|
29
29
|
Requires-Dist: versionreporter>=0.4
|
|
30
30
|
Dynamic: author
|
|
31
31
|
Dynamic: license-file
|
|
@@ -286,12 +286,15 @@ is in the menus.
|
|
|
286
286
|
closed for vacation, and preset configuration for the inputs or
|
|
287
287
|
outputs you want to use.
|
|
288
288
|
|
|
289
|
-
- Edit configuration...: Show the whole configuration in a folding
|
|
290
|
-
editor, so you can change one value without stepping
|
|
291
|
-
wizard. It asks whether to edit the configuration the
|
|
292
|
-
using or one in a file you pick.
|
|
293
|
-
|
|
294
|
-
|
|
289
|
+
- Edit configuration...: Show the whole configuration in a folding,
|
|
290
|
+
searchable editor, so you can change one value without stepping
|
|
291
|
+
through the wizard. It asks whether to edit the configuration the
|
|
292
|
+
application is using or one in a file you pick. A preset, a Jira
|
|
293
|
+
connection, a person, a team, a level and an entry of any of the maps
|
|
294
|
+
can be added beside the ones that are there, taken out again, and
|
|
295
|
+
moved within a list. Saving validates the configuration first, keeps
|
|
296
|
+
what it writes over as a `.bak` file, and makes what it wrote the
|
|
297
|
+
configuration in use.
|
|
295
298
|
|
|
296
299
|
- Create IO preset file...: Create a stand-alone input or output preset
|
|
297
300
|
configuration file via a wizard.
|
|
@@ -362,10 +365,10 @@ one with the list of releases. You will want to use the menus.
|
|
|
362
365
|
|
|
363
366
|
## Test summary
|
|
364
367
|
|
|
365
|
-
- Test result:
|
|
368
|
+
- Test result: 2298 passed, 1 deselected in 57s
|
|
366
369
|
- No flake8 warnings.
|
|
367
370
|
- No mypy errors found.
|
|
368
371
|
- No pylint warnings.
|
|
369
372
|
- No python layout warnings.
|
|
370
|
-
- Built version(s):
|
|
373
|
+
- Built version(s): 1.1
|
|
371
374
|
- Build and test using Python 3.14.7
|
|
@@ -5,7 +5,7 @@ from setuptools import setup
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='backlogops-gui',
|
|
8
|
-
version='
|
|
8
|
+
version='1.1',
|
|
9
9
|
description='Graphical user interface for backlog operations.',
|
|
10
10
|
author='Tom Björkholm',
|
|
11
11
|
author_email='klausuler_linnet0q@icloud.com',
|
|
@@ -19,11 +19,11 @@ setup(
|
|
|
19
19
|
]
|
|
20
20
|
},
|
|
21
21
|
install_requires=[
|
|
22
|
-
'backlogops >=
|
|
22
|
+
'backlogops >= 1.1',
|
|
23
23
|
'argcomplete >= 3.7.2',
|
|
24
24
|
'wizard-ui-bridge >= 1.3',
|
|
25
25
|
'wizard-tk-bridge >= 1.3',
|
|
26
|
-
'edit-cfg-json-tk
|
|
26
|
+
'edit-cfg-json-tk >= 0.1.0',
|
|
27
27
|
'versionreporter >= 0.4'
|
|
28
28
|
]
|
|
29
29
|
)
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#! /usr/local/bin/python3
|
|
2
|
+
"""Edit a configuration in a window of the application.
|
|
3
|
+
|
|
4
|
+
The two edit actions of the configuration menu live here.
|
|
5
|
+
:func:`edit_config` opens the configuration the application is using, or one
|
|
6
|
+
in a file the user picks; :func:`edit_preset_file` opens a stand-alone input
|
|
7
|
+
or output preset file, whose direction is detected from the file itself.
|
|
8
|
+
What can be edited, what a save writes, and what each member is for all
|
|
9
|
+
belong to :mod:`backlogops.config_editing`, so the editor of the terminal
|
|
10
|
+
interface shows exactly the same configuration.
|
|
11
|
+
|
|
12
|
+
These are functions taking the application rather than a collaborator
|
|
13
|
+
object, because an editing session keeps nothing between two of them: the
|
|
14
|
+
session belongs to the window and everything else is the application's.
|
|
15
|
+
|
|
16
|
+
The editor is mounted in a :class:`tkinter.Toplevel` this module creates,
|
|
17
|
+
rather than started through ``edit_cfg_json_tk.edit``. That entry point
|
|
18
|
+
creates a ``tkinter.Tk`` and an event loop of its own, which is for an
|
|
19
|
+
application that runs neither yet: a second Tcl interpreter shares nothing
|
|
20
|
+
with the first, and a nested loop would not end when the editor window
|
|
21
|
+
closed, because Tcl runs its loop while any window of the process lives.
|
|
22
|
+
``TkEditorPanel`` is what the library offers for an application that already
|
|
23
|
+
runs Tk. It is given the window as its ``area``, so that the window stays
|
|
24
|
+
this module's — its title names the file being edited, and the editor
|
|
25
|
+
destroys only what it built itself. Its ``parent`` is the other way of
|
|
26
|
+
saying this, and it is not used here: the library would then own the window
|
|
27
|
+
and name it after the configuration class, and the file being edited is
|
|
28
|
+
what a user of this application needs to read there.
|
|
29
|
+
|
|
30
|
+
The window is not made modal. The editor opens dialogs of its own — a file
|
|
31
|
+
chooser for Save as…, one asking for the key of a new entry, a question
|
|
32
|
+
before it overwrites a file and a question before it drops unsaved changes
|
|
33
|
+
— and a grab held by the editor window would keep their clicks and keys
|
|
34
|
+
from reaching them.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Copyright (c) 2026, Tom Björkholm
|
|
38
|
+
# MIT License
|
|
39
|
+
|
|
40
|
+
import tkinter as tk
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import NamedTuple, Optional, TYPE_CHECKING
|
|
43
|
+
from config_as_json import Config, PathOrStr
|
|
44
|
+
from edit_cfg_json import ConfigLoadError, EditModel, default_config
|
|
45
|
+
from edit_cfg_json_tk import TkEditorPanel
|
|
46
|
+
from wizard_tk_bridge.close_binding import bind_close
|
|
47
|
+
from backlogops import (
|
|
48
|
+
BacklogOpsConfig, EDIT_SETTINGS, descriptions_for, io_preset_class)
|
|
49
|
+
from backlogops_gui.choice_dialogs import EditTargetChoice, ask_edit_target
|
|
50
|
+
from backlogops_gui.file_choosers import (
|
|
51
|
+
choose_config_to_edit, choose_preset_to_edit)
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from backlogops_gui.application import BacklogApp
|
|
55
|
+
|
|
56
|
+
EDIT_ERRORS = (ConfigLoadError, ValueError, TypeError, KeyError, OSError)
|
|
57
|
+
"""Errors raised when a configuration cannot be opened for editing."""
|
|
58
|
+
|
|
59
|
+
OPEN_FAIL_TITLE = 'Could not open the configuration'
|
|
60
|
+
NO_CONFIG_TEXT = 'There is no configuration to edit.'
|
|
61
|
+
NOT_SAVED_TEXT = 'The editor was closed without saving anything.'
|
|
62
|
+
EDITOR_TITLE = 'Configuration editor'
|
|
63
|
+
ADOPTED = 'The saved configuration is now the one in use.'
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class EditorWindow(NamedTuple):
|
|
67
|
+
"""One editor window and the panel running the session inside it."""
|
|
68
|
+
|
|
69
|
+
window: tk.Toplevel
|
|
70
|
+
panel: TkEditorPanel
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def editor_window(app: 'BacklogApp', config: Config, title: str, *,
|
|
74
|
+
in_file: Optional[PathOrStr] = None,
|
|
75
|
+
out_file: Optional[PathOrStr] = None) -> EditorWindow:
|
|
76
|
+
"""Create a window of the application with the editor mounted in it.
|
|
77
|
+
|
|
78
|
+
Every way out of the editor — its Close button, its key, the close
|
|
79
|
+
button of the window and the platform close key — goes through the
|
|
80
|
+
editor's own close action, so none of them can drop an unsaved change
|
|
81
|
+
without asking. Closing takes the editor off this window, and the
|
|
82
|
+
window then goes with it.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
app: The application, whose window this one belongs to.
|
|
86
|
+
config: Configuration object of the class to edit, holding the
|
|
87
|
+
values to start from when there is no input file.
|
|
88
|
+
title: Title of the window, saying what is being edited.
|
|
89
|
+
in_file: Configuration file to read, or None to edit the values
|
|
90
|
+
that ``config`` holds.
|
|
91
|
+
out_file: Configuration file a save writes, or None to write the
|
|
92
|
+
input file. With neither, the editor asks the user for one.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
The window and the panel that the editor runs in.
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
ConfigLoadError: The input file cannot be opened for editing. The
|
|
99
|
+
window is taken away again before this is raised.
|
|
100
|
+
"""
|
|
101
|
+
window = tk.Toplevel(app.root)
|
|
102
|
+
window.title(title)
|
|
103
|
+
window.transient(app.root)
|
|
104
|
+
try:
|
|
105
|
+
panel = TkEditorPanel(config, area=window, modal=False,
|
|
106
|
+
on_close=window.destroy,
|
|
107
|
+
descriptions=descriptions_for(config),
|
|
108
|
+
in_file=in_file, out_file=out_file,
|
|
109
|
+
settings=EDIT_SETTINGS, stderr_file=app.log)
|
|
110
|
+
except EDIT_ERRORS:
|
|
111
|
+
window.destroy()
|
|
112
|
+
raise
|
|
113
|
+
window.protocol('WM_DELETE_WINDOW', panel.close)
|
|
114
|
+
bind_close(window, panel.close)
|
|
115
|
+
return EditorWindow(window=window, panel=panel)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def edit_in_window(app: 'BacklogApp', config: Config, title: str, *,
|
|
119
|
+
in_file: Optional[PathOrStr] = None,
|
|
120
|
+
out_file: Optional[PathOrStr] = None) -> EditModel:
|
|
121
|
+
"""Show one configuration in a window of its own until it is closed.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
app, config, title, in_file, out_file: As of :func:`editor_window`.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The model of the session that has just ended, which says what it
|
|
128
|
+
saved.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
ConfigLoadError: The input file cannot be opened for editing.
|
|
132
|
+
"""
|
|
133
|
+
mounted = editor_window(app, config, title, in_file=in_file,
|
|
134
|
+
out_file=out_file)
|
|
135
|
+
mounted.window.wait_window()
|
|
136
|
+
return mounted.panel.model
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def open_editor_window(app: 'BacklogApp', config: Config, title: str, *,
|
|
140
|
+
in_file: Optional[PathOrStr] = None,
|
|
141
|
+
out_file: Optional[PathOrStr] = None
|
|
142
|
+
) -> Optional[EditModel]:
|
|
143
|
+
"""Run one editing session in a window, or report why it cannot run.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
app, config, title, in_file, out_file: As of :func:`editor_window`.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The model of the session that ran, or None when the configuration
|
|
150
|
+
could not be opened for editing, which is then reported.
|
|
151
|
+
"""
|
|
152
|
+
try:
|
|
153
|
+
return edit_in_window(app, config, title, in_file=in_file,
|
|
154
|
+
out_file=out_file)
|
|
155
|
+
except EDIT_ERRORS as error:
|
|
156
|
+
_report_failure(app, error)
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def edit_config(app: 'BacklogApp') -> None:
|
|
161
|
+
"""Edit the configuration in use, or one in a file the user picks.
|
|
162
|
+
|
|
163
|
+
A configuration the editor saved becomes the active configuration,
|
|
164
|
+
whichever of the two was edited, because the user has just said that
|
|
165
|
+
those are the values they want. Cancelling any step, and closing the
|
|
166
|
+
editor without saving, leaves everything as it was.
|
|
167
|
+
"""
|
|
168
|
+
choice = ask_edit_target(app.root)
|
|
169
|
+
if choice is EditTargetChoice.CANCEL:
|
|
170
|
+
return
|
|
171
|
+
model = (_edit_in_use(app) if choice is EditTargetChoice.IN_USE
|
|
172
|
+
else _edit_config_file(app))
|
|
173
|
+
if model is not None:
|
|
174
|
+
_adopt_saved(app, model)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def edit_preset_file(app: 'BacklogApp') -> None:
|
|
178
|
+
"""Edit a stand-alone input or output preset file.
|
|
179
|
+
|
|
180
|
+
The direction of the file is detected from its own contents, so the user
|
|
181
|
+
picks a preset file and nothing else. What the editor writes is a file
|
|
182
|
+
and not a configuration of the application, so nothing is adopted.
|
|
183
|
+
"""
|
|
184
|
+
path = choose_preset_to_edit(app.root)
|
|
185
|
+
if path is None:
|
|
186
|
+
return
|
|
187
|
+
try:
|
|
188
|
+
config = default_config(io_preset_class(path))
|
|
189
|
+
except EDIT_ERRORS as error:
|
|
190
|
+
_report_failure(app, error)
|
|
191
|
+
return
|
|
192
|
+
model = open_editor_window(app, config, f'Edit preset {Path(path).name}',
|
|
193
|
+
in_file=path)
|
|
194
|
+
if model is not None:
|
|
195
|
+
_report_saved(app, model, 'Preset saved')
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _edit_in_use(app: 'BacklogApp') -> Optional[EditModel]:
|
|
199
|
+
"""Edit the configuration the application uses, in a window.
|
|
200
|
+
|
|
201
|
+
A save writes the file the configuration was loaded from, when it came
|
|
202
|
+
from one; a configuration that came from the wizard has no file yet and
|
|
203
|
+
the editor asks for one before it can save.
|
|
204
|
+
"""
|
|
205
|
+
config = app.config
|
|
206
|
+
if config is None:
|
|
207
|
+
app.show_error('No configuration', NO_CONFIG_TEXT)
|
|
208
|
+
return None
|
|
209
|
+
out_file = _loaded_file(app)
|
|
210
|
+
return open_editor_window(app, config, _config_title(out_file),
|
|
211
|
+
out_file=out_file)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _edit_config_file(app: 'BacklogApp') -> Optional[EditModel]:
|
|
215
|
+
"""Edit a configuration file the user picks, in a window."""
|
|
216
|
+
path = choose_config_to_edit(app.root)
|
|
217
|
+
if path is None:
|
|
218
|
+
return None
|
|
219
|
+
return open_editor_window(app, default_config(BacklogOpsConfig),
|
|
220
|
+
_config_title(path), in_file=path)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _config_title(name: Optional[str]) -> str:
|
|
224
|
+
"""Return the window title, naming the file when the session has one."""
|
|
225
|
+
if name is None:
|
|
226
|
+
return 'Edit configuration'
|
|
227
|
+
return f'Edit configuration {Path(name).name}'
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _loaded_file(app: 'BacklogApp') -> Optional[str]:
|
|
231
|
+
"""Return the file the active configuration was loaded from, if any.
|
|
232
|
+
|
|
233
|
+
The source of a configuration is either a file name or a phrase saying
|
|
234
|
+
where else it came from, so only a name that is a file now is a
|
|
235
|
+
destination that a save may write.
|
|
236
|
+
"""
|
|
237
|
+
source = app.config_source
|
|
238
|
+
if source is None or not Path(source).is_file():
|
|
239
|
+
return None
|
|
240
|
+
return source
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _report_failure(app: 'BacklogApp', error: Exception) -> None:
|
|
244
|
+
"""Log and show why a configuration cannot be opened for editing."""
|
|
245
|
+
app.log.write(f'{OPEN_FAIL_TITLE}: {error}\n')
|
|
246
|
+
app.show_error(OPEN_FAIL_TITLE, str(error))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _adopt_saved(app: 'BacklogApp', model: EditModel) -> None:
|
|
250
|
+
"""Make what the editor saved the configuration the application uses."""
|
|
251
|
+
saved = model.saved_config
|
|
252
|
+
if saved is None:
|
|
253
|
+
app.show_info(EDITOR_TITLE, NOT_SAVED_TEXT)
|
|
254
|
+
return
|
|
255
|
+
assert isinstance(saved, BacklogOpsConfig)
|
|
256
|
+
app.adopt_config(saved, str(model.out_file))
|
|
257
|
+
app.show_info('Configuration saved', f'{model.save_message}\n{ADOPTED}')
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _report_saved(app: 'BacklogApp', model: EditModel, title: str) -> None:
|
|
261
|
+
"""Report what the editor wrote, or that it wrote nothing."""
|
|
262
|
+
if model.saved_config is None:
|
|
263
|
+
app.show_info(EDITOR_TITLE, NOT_SAVED_TEXT)
|
|
264
|
+
return
|
|
265
|
+
app.show_info(title, model.save_message)
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
#! /usr/local/bin/python3
|
|
2
|
-
"""Edit a configuration in a window of the application.
|
|
3
|
-
|
|
4
|
-
The two edit actions of the configuration menu live here.
|
|
5
|
-
:func:`edit_config` opens the configuration the application is using, or one
|
|
6
|
-
in a file the user picks; :func:`edit_preset_file` opens a stand-alone input
|
|
7
|
-
or output preset file, whose direction is detected from the file itself.
|
|
8
|
-
What can be edited, what a save writes, and what each member is for all
|
|
9
|
-
belong to :mod:`backlogops.config_editing`, so the editor of the terminal
|
|
10
|
-
interface shows exactly the same configuration.
|
|
11
|
-
|
|
12
|
-
These are functions taking the application rather than a collaborator
|
|
13
|
-
object, because an editing session keeps nothing between two of them: the
|
|
14
|
-
model belongs to the session and everything else is the application's.
|
|
15
|
-
|
|
16
|
-
The editor is mounted in a :class:`tkinter.Toplevel` this module creates,
|
|
17
|
-
rather than started through ``edit_cfg_json_tk.edit``. That entry point
|
|
18
|
-
creates a ``tkinter.Tk`` and an event loop of its own, which is for an
|
|
19
|
-
application that runs neither yet: a second Tcl interpreter shares nothing
|
|
20
|
-
with the first, and a nested loop would not end when the editor window
|
|
21
|
-
closed, because Tcl runs its loop while any window of the process lives.
|
|
22
|
-
``EditorWidgets`` is what the library offers for a window an application
|
|
23
|
-
owns, and it takes the close action as an argument so that the editor never
|
|
24
|
-
destroys a window it did not create.
|
|
25
|
-
|
|
26
|
-
The window is not made modal. The editor opens dialogs of its own — a file
|
|
27
|
-
chooser for Save as…, a question before it overwrites a file, and one asking
|
|
28
|
-
for the key of a new entry — and a grab held by the editor window would keep
|
|
29
|
-
their clicks and keys from reaching them.
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
# Copyright (c) 2026, Tom Björkholm
|
|
33
|
-
# MIT License
|
|
34
|
-
|
|
35
|
-
import tkinter as tk
|
|
36
|
-
from pathlib import Path
|
|
37
|
-
from typing import Callable, NamedTuple, Optional, TYPE_CHECKING
|
|
38
|
-
from edit_cfg_json import EditModel
|
|
39
|
-
from edit_cfg_json_tk.tk_editor import EditorWidgets
|
|
40
|
-
from wizard_tk_bridge.close_binding import bind_close
|
|
41
|
-
from backlogops import (
|
|
42
|
-
BacklogOpsConfig, default_edit_config, edit_model_for, io_preset_class)
|
|
43
|
-
from backlogops_gui.choice_dialogs import EditTargetChoice, ask_edit_target
|
|
44
|
-
from backlogops_gui.file_choosers import (
|
|
45
|
-
choose_config_to_edit, choose_preset_to_edit)
|
|
46
|
-
|
|
47
|
-
if TYPE_CHECKING:
|
|
48
|
-
from backlogops_gui.application import BacklogApp
|
|
49
|
-
|
|
50
|
-
EDIT_ERRORS = (ValueError, TypeError, KeyError, OSError)
|
|
51
|
-
"""Errors raised when a configuration cannot be opened for editing."""
|
|
52
|
-
|
|
53
|
-
OPEN_FAIL_TITLE = 'Could not open the configuration'
|
|
54
|
-
NO_CONFIG_TEXT = 'There is no configuration to edit.'
|
|
55
|
-
NOT_SAVED_TEXT = 'The editor was closed without saving anything.'
|
|
56
|
-
EDITOR_TITLE = 'Configuration editor'
|
|
57
|
-
ADOPTED = 'The saved configuration is now the one in use.'
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class EditorWindow(NamedTuple):
|
|
61
|
-
"""One editor window and the widgets that have to be kept with it.
|
|
62
|
-
|
|
63
|
-
The widgets are carried beside the window because a ``StringVar`` unsets
|
|
64
|
-
its Tcl variable when it is collected, and the field it belongs to would
|
|
65
|
-
then lose both its text and the callback that writes into the model.
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
window: tk.Toplevel
|
|
69
|
-
widgets: EditorWidgets
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def editor_window(parent: tk.Misc, model: EditModel,
|
|
73
|
-
title: str) -> EditorWindow:
|
|
74
|
-
"""Create a window of the application with the editor mounted in it.
|
|
75
|
-
|
|
76
|
-
Every way out of the editor — its Close button, its key, the close
|
|
77
|
-
button of the window and the platform close key — goes through the
|
|
78
|
-
editor's own close action, so none of them can drop an unsaved change
|
|
79
|
-
without asking. Closing destroys this window and nothing else.
|
|
80
|
-
|
|
81
|
-
Args:
|
|
82
|
-
parent: Widget the window belongs to, which is the main window.
|
|
83
|
-
model: Model of the editing session to show.
|
|
84
|
-
title: Title of the window, saying what is being edited.
|
|
85
|
-
|
|
86
|
-
Returns:
|
|
87
|
-
The window and the widgets mounted in it.
|
|
88
|
-
"""
|
|
89
|
-
window = tk.Toplevel(parent)
|
|
90
|
-
window.title(title)
|
|
91
|
-
if isinstance(parent, tk.Wm):
|
|
92
|
-
window.transient(parent)
|
|
93
|
-
widgets = EditorWidgets(parent=window, model=model,
|
|
94
|
-
on_close=window.destroy)
|
|
95
|
-
window.protocol('WM_DELETE_WINDOW', widgets.close_editor)
|
|
96
|
-
bind_close(window, widgets.close_editor)
|
|
97
|
-
return EditorWindow(window=window, widgets=widgets)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def open_editor_window(parent: tk.Misc, model: EditModel, title: str) -> None:
|
|
101
|
-
"""Show one edit model in a window of its own until it is closed.
|
|
102
|
-
|
|
103
|
-
The window and its widgets are held by the local name for as long as
|
|
104
|
-
this call waits for the window, which is as long as they are needed.
|
|
105
|
-
|
|
106
|
-
Args:
|
|
107
|
-
parent: Widget the window belongs to, which is the main window.
|
|
108
|
-
model: Model of the editing session to show.
|
|
109
|
-
title: Title of the window, saying what is being edited.
|
|
110
|
-
"""
|
|
111
|
-
mounted = editor_window(parent, model, title)
|
|
112
|
-
mounted.window.wait_window()
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def edit_config(app: 'BacklogApp') -> None:
|
|
116
|
-
"""Edit the configuration in use, or one in a file the user picks.
|
|
117
|
-
|
|
118
|
-
A configuration the editor saved becomes the active configuration,
|
|
119
|
-
whichever of the two was edited, because the user has just said that
|
|
120
|
-
those are the values they want. Cancelling any step, and closing the
|
|
121
|
-
editor without saving, leaves everything as it was.
|
|
122
|
-
"""
|
|
123
|
-
choice = ask_edit_target(app.root)
|
|
124
|
-
if choice is EditTargetChoice.CANCEL:
|
|
125
|
-
return
|
|
126
|
-
model = (_in_use_model(app) if choice is EditTargetChoice.IN_USE
|
|
127
|
-
else _config_file_model(app))
|
|
128
|
-
if model is None:
|
|
129
|
-
return
|
|
130
|
-
open_editor_window(app.root, model, _config_title(model))
|
|
131
|
-
_adopt_saved(app, model)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def _config_title(model: EditModel) -> str:
|
|
135
|
-
"""Return the window title, naming the file when a save has one."""
|
|
136
|
-
if model.out_file is None:
|
|
137
|
-
return 'Edit configuration'
|
|
138
|
-
return f'Edit configuration {Path(model.out_file).name}'
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def edit_preset_file(app: 'BacklogApp') -> None:
|
|
142
|
-
"""Edit a stand-alone input or output preset file.
|
|
143
|
-
|
|
144
|
-
The direction of the file is detected from its own contents, so the user
|
|
145
|
-
picks a preset file and nothing else. What the editor writes is a file
|
|
146
|
-
and not a configuration of the application, so nothing is adopted.
|
|
147
|
-
"""
|
|
148
|
-
path = choose_preset_to_edit(app.root)
|
|
149
|
-
if path is None:
|
|
150
|
-
return
|
|
151
|
-
model = _built(app, lambda: edit_model_for(
|
|
152
|
-
default_edit_config(io_preset_class(path)), in_file=path,
|
|
153
|
-
stderr_file=app.log))
|
|
154
|
-
if model is None:
|
|
155
|
-
return
|
|
156
|
-
open_editor_window(app.root, model, f'Edit preset {Path(path).name}')
|
|
157
|
-
_report_saved(app, model, 'Preset saved')
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def _in_use_model(app: 'BacklogApp') -> Optional[EditModel]:
|
|
161
|
-
"""Return the model editing the configuration the application uses.
|
|
162
|
-
|
|
163
|
-
A save writes the file the configuration was loaded from, when it came
|
|
164
|
-
from one; a configuration that came from the wizard has no file yet and
|
|
165
|
-
the editor asks for one before it can save.
|
|
166
|
-
"""
|
|
167
|
-
config = app.config
|
|
168
|
-
if config is None:
|
|
169
|
-
app.show_error('No configuration', NO_CONFIG_TEXT)
|
|
170
|
-
return None
|
|
171
|
-
return _built(app, lambda: edit_model_for(config,
|
|
172
|
-
out_file=_loaded_file(app),
|
|
173
|
-
stderr_file=app.log))
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def _config_file_model(app: 'BacklogApp') -> Optional[EditModel]:
|
|
177
|
-
"""Return the model editing a configuration file the user picks."""
|
|
178
|
-
path = choose_config_to_edit(app.root)
|
|
179
|
-
if path is None:
|
|
180
|
-
return None
|
|
181
|
-
return _built(app, lambda: edit_model_for(
|
|
182
|
-
default_edit_config(BacklogOpsConfig), in_file=path,
|
|
183
|
-
stderr_file=app.log))
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def _loaded_file(app: 'BacklogApp') -> Optional[str]:
|
|
187
|
-
"""Return the file the active configuration was loaded from, if any.
|
|
188
|
-
|
|
189
|
-
The source of a configuration is either a file name or a phrase saying
|
|
190
|
-
where else it came from, so only a name that is a file now is a
|
|
191
|
-
destination that a save may write.
|
|
192
|
-
"""
|
|
193
|
-
source = app.config_source
|
|
194
|
-
if source is None or not Path(source).is_file():
|
|
195
|
-
return None
|
|
196
|
-
return source
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def _built(app: 'BacklogApp',
|
|
200
|
-
build: Callable[[], EditModel]) -> Optional[EditModel]:
|
|
201
|
-
"""Return the model that ``build`` makes, reporting a refusal."""
|
|
202
|
-
try:
|
|
203
|
-
return build()
|
|
204
|
-
except EDIT_ERRORS as error:
|
|
205
|
-
app.log.write(f'{OPEN_FAIL_TITLE}: {error}\n')
|
|
206
|
-
app.show_error(OPEN_FAIL_TITLE, str(error))
|
|
207
|
-
return None
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def _adopt_saved(app: 'BacklogApp', model: EditModel) -> None:
|
|
211
|
-
"""Make what the editor saved the configuration the application uses."""
|
|
212
|
-
saved = model.saved_config
|
|
213
|
-
if saved is None:
|
|
214
|
-
app.show_info(EDITOR_TITLE, NOT_SAVED_TEXT)
|
|
215
|
-
return
|
|
216
|
-
assert isinstance(saved, BacklogOpsConfig)
|
|
217
|
-
app.adopt_config(saved, str(model.out_file))
|
|
218
|
-
app.show_info('Configuration saved', f'{model.save_message}\n{ADOPTED}')
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def _report_saved(app: 'BacklogApp', model: EditModel, title: str) -> None:
|
|
222
|
-
"""Report what the editor wrote, or that it wrote nothing."""
|
|
223
|
-
if model.saved_config is None:
|
|
224
|
-
app.show_info(EDITOR_TITLE, NOT_SAVED_TEXT)
|
|
225
|
-
return
|
|
226
|
-
app.show_info(title, model.save_message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|