backlogops-gui 0.6__tar.gz → 0.8__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 (55) hide show
  1. {backlogops_gui-0.6 → backlogops_gui-0.8}/PKG-INFO +7 -6
  2. {backlogops_gui-0.6 → backlogops_gui-0.8}/README_pypi.md +2 -2
  3. {backlogops_gui-0.6 → backlogops_gui-0.8}/backlogops_gui.egg-info/PKG-INFO +7 -6
  4. {backlogops_gui-0.6 → backlogops_gui-0.8}/backlogops_gui.egg-info/SOURCES.txt +1 -13
  5. backlogops_gui-0.8/backlogops_gui.egg-info/requires.txt +5 -0
  6. {backlogops_gui-0.6 → backlogops_gui-0.8}/setup.py +5 -4
  7. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/application.py +2 -2
  8. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/backlog_dialogs.py +1 -1
  9. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/backlog_window.py +2 -2
  10. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/choice_dialogs.py +1 -1
  11. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/format_dialogs.py +1 -1
  12. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/jira_dialogs.py +1 -1
  13. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/key_list_box.py +1 -1
  14. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/modal_dialog.py +2 -2
  15. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/report_windows.py +1 -1
  16. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/token_dialog.py +1 -1
  17. backlogops_gui-0.6/backlogops_gui.egg-info/requires.txt +0 -4
  18. backlogops_gui-0.6/src/backlogops_gui/auto_scroll.py +0 -37
  19. backlogops_gui-0.6/src/backlogops_gui/close_binding.py +0 -47
  20. backlogops_gui-0.6/src/backlogops_gui/gui_style.py +0 -70
  21. backlogops_gui-0.6/src/backlogops_gui/gui_wizard.py +0 -138
  22. backlogops_gui-0.6/src/backlogops_gui/wizard_calendar.py +0 -170
  23. backlogops_gui-0.6/src/backlogops_gui/wizard_form.py +0 -670
  24. backlogops_gui-0.6/src/backlogops_gui/wizard_path.py +0 -177
  25. backlogops_gui-0.6/src/backlogops_gui/wizard_pick_row.py +0 -154
  26. backlogops_gui-0.6/src/backlogops_gui/wizard_prefill.py +0 -138
  27. backlogops_gui-0.6/src/backlogops_gui/wizard_table.py +0 -259
  28. backlogops_gui-0.6/src/backlogops_gui/wizard_typed.py +0 -286
  29. backlogops_gui-0.6/src/backlogops_gui/wizard_window.py +0 -364
  30. {backlogops_gui-0.6 → backlogops_gui-0.8}/LICENSE.txt +0 -0
  31. {backlogops_gui-0.6 → backlogops_gui-0.8}/backlogops_gui.egg-info/dependency_links.txt +0 -0
  32. {backlogops_gui-0.6 → backlogops_gui-0.8}/backlogops_gui.egg-info/entry_points.txt +0 -0
  33. {backlogops_gui-0.6 → backlogops_gui-0.8}/backlogops_gui.egg-info/top_level.txt +0 -0
  34. {backlogops_gui-0.6 → backlogops_gui-0.8}/pyproject.toml +0 -0
  35. {backlogops_gui-0.6 → backlogops_gui-0.8}/setup.cfg +0 -0
  36. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/__init__.py +0 -0
  37. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/__main__.py +0 -0
  38. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/_migrate_warn.py +0 -0
  39. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/backlog_actions.py +0 -0
  40. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/backlog_io.py +0 -0
  41. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/blog_version_reporter.py +0 -0
  42. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/file_choosers.py +0 -0
  43. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/jira_actions.py +0 -0
  44. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/jira_base.py +0 -0
  45. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/jira_order.py +0 -0
  46. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/jira_rank.py +0 -0
  47. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/jira_read.py +0 -0
  48. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/jira_rename.py +0 -0
  49. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/jira_update.py +0 -0
  50. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/jira_write.py +0 -0
  51. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/log_buffer.py +0 -0
  52. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/py.typed +0 -0
  53. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/python_version.py +0 -0
  54. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/table_view.py +0 -0
  55. {backlogops_gui-0.6 → backlogops_gui-0.8}/src/backlogops_gui/tcltk_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backlogops-gui
3
- Version: 0.6
3
+ Version: 0.8
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,9 +21,10 @@ 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>=0.6
25
- Requires-Dist: argcomplete>=3.7.0
26
- Requires-Dist: wizard-ui-bridge>=1.1
24
+ Requires-Dist: backlogops>=0.8
25
+ Requires-Dist: argcomplete>=3.7.1
26
+ Requires-Dist: wizard-ui-bridge>=1.3
27
+ Requires-Dist: wizard-tk-bridge>=1.3
27
28
  Requires-Dist: versionreporter>=0.4
28
29
  Dynamic: author
29
30
  Dynamic: license-file
@@ -349,10 +350,10 @@ one with the list of releases. You will want to use the menus.
349
350
 
350
351
  ## Test summary
351
352
 
352
- - Test result: 2450 passed, 9 deselected in 39s
353
+ - Test result: 2173 passed, 1 deselected in 35s
353
354
  - No flake8 warnings.
354
355
  - No mypy errors found.
355
356
  - No pylint warnings.
356
357
  - No python layout warnings.
357
- - Built version(s): 0.6
358
+ - Built version(s): 0.8
358
359
  - Build and test using Python 3.14.6
@@ -317,10 +317,10 @@ one with the list of releases. You will want to use the menus.
317
317
 
318
318
  ## Test summary
319
319
 
320
- - Test result: 2450 passed, 9 deselected in 39s
320
+ - Test result: 2173 passed, 1 deselected in 35s
321
321
  - No flake8 warnings.
322
322
  - No mypy errors found.
323
323
  - No pylint warnings.
324
324
  - No python layout warnings.
325
- - Built version(s): 0.6
325
+ - Built version(s): 0.8
326
326
  - Build and test using Python 3.14.6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backlogops-gui
3
- Version: 0.6
3
+ Version: 0.8
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,9 +21,10 @@ 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>=0.6
25
- Requires-Dist: argcomplete>=3.7.0
26
- Requires-Dist: wizard-ui-bridge>=1.1
24
+ Requires-Dist: backlogops>=0.8
25
+ Requires-Dist: argcomplete>=3.7.1
26
+ Requires-Dist: wizard-ui-bridge>=1.3
27
+ Requires-Dist: wizard-tk-bridge>=1.3
27
28
  Requires-Dist: versionreporter>=0.4
28
29
  Dynamic: author
29
30
  Dynamic: license-file
@@ -349,10 +350,10 @@ one with the list of releases. You will want to use the menus.
349
350
 
350
351
  ## Test summary
351
352
 
352
- - Test result: 2450 passed, 9 deselected in 39s
353
+ - Test result: 2173 passed, 1 deselected in 35s
353
354
  - No flake8 warnings.
354
355
  - No mypy errors found.
355
356
  - No pylint warnings.
356
357
  - No python layout warnings.
357
- - Built version(s): 0.6
358
+ - Built version(s): 0.8
358
359
  - Build and test using Python 3.14.6
@@ -12,18 +12,14 @@ src/backlogops_gui/__init__.py
12
12
  src/backlogops_gui/__main__.py
13
13
  src/backlogops_gui/_migrate_warn.py
14
14
  src/backlogops_gui/application.py
15
- src/backlogops_gui/auto_scroll.py
16
15
  src/backlogops_gui/backlog_actions.py
17
16
  src/backlogops_gui/backlog_dialogs.py
18
17
  src/backlogops_gui/backlog_io.py
19
18
  src/backlogops_gui/backlog_window.py
20
19
  src/backlogops_gui/blog_version_reporter.py
21
20
  src/backlogops_gui/choice_dialogs.py
22
- src/backlogops_gui/close_binding.py
23
21
  src/backlogops_gui/file_choosers.py
24
22
  src/backlogops_gui/format_dialogs.py
25
- src/backlogops_gui/gui_style.py
26
- src/backlogops_gui/gui_wizard.py
27
23
  src/backlogops_gui/jira_actions.py
28
24
  src/backlogops_gui/jira_base.py
29
25
  src/backlogops_gui/jira_dialogs.py
@@ -41,12 +37,4 @@ src/backlogops_gui/python_version.py
41
37
  src/backlogops_gui/report_windows.py
42
38
  src/backlogops_gui/table_view.py
43
39
  src/backlogops_gui/tcltk_version.py
44
- src/backlogops_gui/token_dialog.py
45
- src/backlogops_gui/wizard_calendar.py
46
- src/backlogops_gui/wizard_form.py
47
- src/backlogops_gui/wizard_path.py
48
- src/backlogops_gui/wizard_pick_row.py
49
- src/backlogops_gui/wizard_prefill.py
50
- src/backlogops_gui/wizard_table.py
51
- src/backlogops_gui/wizard_typed.py
52
- src/backlogops_gui/wizard_window.py
40
+ src/backlogops_gui/token_dialog.py
@@ -0,0 +1,5 @@
1
+ backlogops>=0.8
2
+ argcomplete>=3.7.1
3
+ wizard-ui-bridge>=1.3
4
+ wizard-tk-bridge>=1.3
5
+ versionreporter>=0.4
@@ -5,7 +5,7 @@ from setuptools import setup
5
5
 
6
6
  setup(
7
7
  name='backlogops-gui',
8
- version='0.6',
8
+ version='0.8',
9
9
  description='Graphical user interface for backlog operations.',
10
10
  author='Tom Björkholm',
11
11
  author_email='klausuler_linnet0q@icloud.com',
@@ -19,9 +19,10 @@ setup(
19
19
  ]
20
20
  },
21
21
  install_requires=[
22
- 'backlogops >= 0.6',
23
- 'argcomplete >= 3.7.0',
24
- 'wizard-ui-bridge >= 1.1',
22
+ 'backlogops >= 0.8',
23
+ 'argcomplete >= 3.7.1',
24
+ 'wizard-ui-bridge >= 1.3',
25
+ 'wizard-tk-bridge >= 1.3',
25
26
  'versionreporter >= 0.4'
26
27
  ]
27
28
  )
@@ -39,6 +39,7 @@ from typing import Callable, Optional, TextIO, TypeVar
39
39
  import argcomplete
40
40
  from config_as_json import Config, migrate_cfg
41
41
  from config_as_json.file_extension import fix_file_extension
42
+ from wizard_tk_bridge import WizardUiBridgeTk
42
43
  from backlogops import (
43
44
  AvailableTeams, BacklogOpsConfig, BacklogReleases, GuiDisplayConfig,
44
45
  InputFormatConfig, Levels, OutputFormatConfig, Status, get_demo_backlog,
@@ -49,7 +50,6 @@ from backlogops_gui.backlog_io import read_backlog
49
50
  from backlogops_gui.backlog_window import (
50
51
  BacklogWindow, BacklogSource, JiraHandlers, current_time)
51
52
  from backlogops_gui.blog_version_reporter import BloGuiVersionReporter
52
- from backlogops_gui.gui_wizard import TkWizardBridge
53
53
  from backlogops_gui.choice_dialogs import (
54
54
  ConfigChoice, PresetKind, SourceChoice, ask_no_config_choice,
55
55
  ask_preset_kind, ask_source_choice)
@@ -273,7 +273,7 @@ class BacklogApp:
273
273
  wizard failure is reported under ``error_title`` and also yields
274
274
  None. The bridge window is always closed afterwards.
275
275
  """
276
- bridge = TkWizardBridge(self.root, self.log)
276
+ bridge = WizardUiBridgeTk(self.root, log=self.log)
277
277
  try:
278
278
  return wizard(bridge, default=default)
279
279
  except EOFError:
@@ -17,8 +17,8 @@ from tkinter import messagebox, ttk
17
17
  from dataclasses import dataclass
18
18
  from datetime import date
19
19
  from typing import Optional, TextIO
20
+ from wizard_tk_bridge.gui_style import style_input
20
21
  from backlogops import DependencyMode, DEFAULT_LEVELS
21
- from backlogops_gui.gui_style import style_input
22
22
  from backlogops_gui.key_list_box import build_key_box, load_keys_into
23
23
  from backlogops_gui.modal_dialog import ModalDialog
24
24
 
@@ -22,18 +22,18 @@ from pathlib import Path
22
22
  from tkinter import messagebox, ttk
23
23
  from typing import Callable, Literal, Optional, TextIO
24
24
  from tableio import ValueFmt
25
+ from wizard_tk_bridge.auto_scroll import auto_hide
26
+ from wizard_tk_bridge.close_binding import CLOSE_ACCELERATOR, bind_close
25
27
  from backlogops import (
26
28
  AddedReleasesToJira, AddedToJira, AvailableTeams, BacklogReleases,
27
29
  GuiDisplayConfig, Levels, OrderedReleasesInJira, OutputFormatConfig,
28
30
  RankedInJira, RenamedReleasesInJira, UpdatedBacklogInJira,
29
31
  UpdatedReleasesInJira, format_order_result, format_rank_result,
30
32
  format_release_result, format_release_updates, format_rename_result)
31
- from backlogops_gui.auto_scroll import auto_hide
32
33
  from backlogops_gui.backlog_actions import (
33
34
  adjust_content, apply_add_result, apply_update_result, estimate_date,
34
35
  extract_keys, order_by_deps, order_by_keys, order_by_release, order_dates,
35
36
  plan_dates, save_backlog, set_plan)
36
- from backlogops_gui.close_binding import CLOSE_ACCELERATOR, bind_close
37
37
  from backlogops_gui.report_windows import show_text_report
38
38
  from backlogops_gui.table_view import (
39
39
  backlog_table, make_table, release_table)
@@ -16,7 +16,7 @@ three are built from the same :class:`ButtonChoiceDialog`.
16
16
  import tkinter as tk
17
17
  from enum import Enum
18
18
  from typing import Generic, Optional, Sequence, TypeVar
19
- from backlogops_gui.close_binding import bind_close
19
+ from wizard_tk_bridge.close_binding import bind_close
20
20
 
21
21
  CHOICE_WRAP = 360
22
22
  NO_CONFIG_TEXT = (
@@ -16,7 +16,7 @@ import tkinter as tk
16
16
  from tkinter import filedialog, ttk
17
17
  from dataclasses import dataclass
18
18
  from typing import Optional, Sequence
19
- from backlogops_gui.gui_style import style_input
19
+ from wizard_tk_bridge.gui_style import style_input
20
20
  from backlogops_gui.modal_dialog import ModalDialog
21
21
 
22
22
  MODE_INFER = 0
@@ -22,8 +22,8 @@ from tkinter import messagebox, ttk
22
22
  from dataclasses import dataclass
23
23
  from collections.abc import Mapping
24
24
  from typing import Optional, Sequence, TextIO
25
+ from wizard_tk_bridge.gui_style import style_input
25
26
  from backlogops import JiraRankAnchor, OnMissingKey, ReleaseRename
26
- from backlogops_gui.gui_style import style_input
27
27
  from backlogops_gui.key_list_box import build_key_box, load_keys_into
28
28
  from backlogops_gui.modal_dialog import ModalDialog
29
29
 
@@ -13,8 +13,8 @@ import tkinter as tk
13
13
  from tkinter import filedialog, messagebox
14
14
  from collections.abc import Callable
15
15
  from typing import TextIO
16
+ from wizard_tk_bridge.gui_style import style_input
16
17
  from backlogops import read_key_list
17
- from backlogops_gui.gui_style import style_input
18
18
 
19
19
  KEY_READ_ERRORS = (ValueError, TypeError, KeyError, OSError)
20
20
 
@@ -12,8 +12,8 @@ store the entered values before the window closes.
12
12
  # MIT License
13
13
 
14
14
  import tkinter as tk
15
- from backlogops_gui.close_binding import bind_close
16
- from backlogops_gui.gui_style import focus_first_input
15
+ from wizard_tk_bridge.close_binding import bind_close
16
+ from wizard_tk_bridge.gui_style import focus_first_input
17
17
 
18
18
 
19
19
  # pylint: disable-next=too-few-public-methods
@@ -12,7 +12,7 @@ created window so a caller or a test can drive or close it.
12
12
 
13
13
  import tkinter as tk
14
14
  from typing import Callable
15
- from backlogops_gui.close_binding import bind_close
15
+ from wizard_tk_bridge.close_binding import bind_close
16
16
 
17
17
 
18
18
  def show_change_list(parent: tk.Misc, title: str, text: str,
@@ -19,7 +19,7 @@ from tkinter import filedialog, messagebox
19
19
  from dataclasses import dataclass
20
20
  from pathlib import Path
21
21
  from typing import Callable, Optional
22
- from backlogops_gui.gui_style import style_input
22
+ from wizard_tk_bridge.gui_style import style_input
23
23
  from backlogops_gui.modal_dialog import ModalDialog
24
24
 
25
25
 
@@ -1,4 +0,0 @@
1
- backlogops>=0.6
2
- argcomplete>=3.7.0
3
- wizard-ui-bridge>=1.1
4
- versionreporter>=0.4
@@ -1,37 +0,0 @@
1
- #! /usr/local/bin/python3
2
- """A scroll command that shows a scrollbar only while it can scroll.
3
-
4
- A table that fits its area needs no scrollbar, and a scrollbar that is
5
- always shown wastes space and hints at hidden content that is not there.
6
- :func:`auto_hide` returns the scroll command for a scrolling widget: the
7
- command hides the scrollbar while the whole range is visible and shows it
8
- again once the widget grows past its area. It works for any widget that
9
- reports its position through an ``xscrollcommand`` or ``yscrollcommand``,
10
- so the wizard table canvas and the read-only backlog tree can share it.
11
- """
12
-
13
- # Copyright (c) 2026, Tom Björkholm
14
- # MIT License
15
-
16
- from tkinter import ttk
17
- from typing import Callable
18
-
19
-
20
- def auto_hide(scrollbar: ttk.Scrollbar
21
- ) -> Callable[[float | str, float | str], None]:
22
- """Return a scroll command that hides the scrollbar when it is full.
23
-
24
- The result is used as a widget's ``xscrollcommand`` or
25
- ``yscrollcommand``. Tk reports the position as two fractions, which it
26
- passes as strings, so the command accepts either a number or its
27
- string form. The scrollbar must be laid out with the grid manager,
28
- whose ``grid_remove`` remembers its cell across the hide.
29
- """
30
- def command(first: float | str, last: float | str) -> None:
31
- """Hide or show the scrollbar, then move its thumb to the view."""
32
- if float(first) <= 0.0 and float(last) >= 1.0:
33
- scrollbar.grid_remove()
34
- else:
35
- scrollbar.grid()
36
- scrollbar.set(first, last)
37
- return command
@@ -1,47 +0,0 @@
1
- #! /usr/local/bin/python3
2
- """Bind the close-window key to a secondary window's close action.
3
-
4
- On macOS the Tk toolkit does not close a window when the user presses
5
- Cmd-W, so every secondary window binds that key here. Cmd-W is bound on
6
- every platform, where it is harmless without a Command key, and Ctrl-W is
7
- added on Windows, its customary close-window shortcut. The bound action
8
- defaults to destroying the window but may be the window's own cancel or
9
- abort handler, so the key behaves exactly like the window close button.
10
- """
11
-
12
- # Copyright (c) 2026, Tom Björkholm
13
- # MIT License
14
-
15
- import sys
16
- import tkinter as tk
17
- from typing import Callable, Optional
18
-
19
- CLOSE_ACCELERATOR = 'Command-W'
20
-
21
-
22
- def _close_events() -> list[str]:
23
- """Return the key patterns that close a window on this platform."""
24
- if sys.platform.startswith('win'):
25
- return ['<Command-w>', '<Control-w>']
26
- return ['<Command-w>']
27
-
28
-
29
- def _perform_close(win: tk.Toplevel,
30
- on_close: Optional[Callable[[], None]]) -> str:
31
- """Run the window's close action and stop further event handling."""
32
- (win.destroy if on_close is None else on_close)()
33
- return 'break'
34
-
35
-
36
- def bind_close(win: tk.Toplevel,
37
- on_close: Optional[Callable[[], None]] = None) -> None:
38
- """Bind the close-window key to run the window's close action.
39
-
40
- Args:
41
- win: The secondary window to close on the key press.
42
- on_close: The close action, defaulting to destroying the window.
43
- A window that cancels or aborts on close passes its own
44
- handler, so the key matches its window close button.
45
- """
46
- for event in _close_events():
47
- win.bind(event, lambda _event: _perform_close(win, on_close))
@@ -1,70 +0,0 @@
1
- #! /usr/local/bin/python3
2
- """Shared look and focus helpers for the Tkinter input windows.
3
-
4
- Editable input widgets blend into the window background on some
5
- platforms, so the user cannot tell an entry, drop-down or list from the
6
- surrounding window. :func:`style_input` gives such a widget a white
7
- field and a thin border so it stands out. :func:`focus_first_input`
8
- puts the keyboard focus on the first editable widget of a window, so the
9
- user can start typing as soon as the window opens.
10
- """
11
-
12
- # Copyright (c) 2026, Tom Björkholm
13
- # MIT License
14
-
15
- import tkinter as tk
16
- from tkinter import ttk
17
- from typing import Optional
18
-
19
- INPUT_BG = 'white'
20
- INPUT_STYLE = 'Input.TCombobox'
21
-
22
-
23
- def style_input(widget: tk.Widget) -> None:
24
- """Make one editable input widget stand out from the background.
25
-
26
- A classic entry, text box or list gets a white field and a thin
27
- solid border. A drop-down keeps its arrow but gets a white field
28
- through a shared ttk style. Any other widget is left unchanged. The
29
- ttk styling is best-effort: a native theme that ignores field colors
30
- leaves the drop-down as it is.
31
- """
32
- if isinstance(widget, ttk.Widget):
33
- if isinstance(widget, ttk.Combobox):
34
- _style_combobox(widget)
35
- return
36
- if isinstance(widget, (tk.Entry, tk.Text, tk.Listbox)):
37
- widget.configure(background=INPUT_BG, relief='solid', borderwidth=1)
38
-
39
-
40
- def _style_combobox(widget: ttk.Combobox) -> None:
41
- """Give a drop-down a white field through a shared ttk style."""
42
- style = ttk.Style(widget)
43
- style.configure(INPUT_STYLE, fieldbackground=INPUT_BG)
44
- style.map(INPUT_STYLE, fieldbackground=[('readonly', INPUT_BG)])
45
- widget.configure(style=INPUT_STYLE)
46
-
47
-
48
- def focus_first_input(window: tk.Misc) -> None:
49
- """Give the keyboard focus to the first editable input, if any."""
50
- target = _first_input(window)
51
- if target is not None:
52
- target.focus_set()
53
-
54
-
55
- def _first_input(parent: tk.Misc) -> Optional[tk.Misc]:
56
- """Return the first editable input under parent, in child order."""
57
- for child in parent.winfo_children():
58
- if _is_input(child):
59
- return child
60
- found = _first_input(child)
61
- if found is not None:
62
- return found
63
- return None
64
-
65
-
66
- def _is_input(widget: tk.Misc) -> bool:
67
- """Return whether the widget is an editable input to fill in."""
68
- if not isinstance(widget, (tk.Entry, tk.Listbox, tk.Text)):
69
- return False
70
- return str(widget.cget('state')) != 'disabled'
@@ -1,138 +0,0 @@
1
- #! /usr/local/bin/python3
2
- """Graphical bridge that drives the synchronous config wizard.
3
-
4
- The backlog-ops configuration wizard asks its questions through a
5
- :class:`WizardUiBridge`. This module provides :class:`TkWizardBridge`, a
6
- concrete bridge that overrides every typed ask method of that base class
7
- with a real Tkinter control, including the GUI-recommended ones: ask_path()
8
- opens a native file or directory picker, and ask_form() shows a whole form
9
- on one screen so the user answers related fields together in any order. All
10
- questions are answered in one reused
11
- :class:`~backlogops_gui.wizard_window.WizardWindow`, so the whole wizard
12
- session happens in a single pop-up that does not jump around the display.
13
- """
14
-
15
- # Copyright (c) 2026, Tom Björkholm
16
- # MIT License
17
-
18
- from typing import Optional, Sequence, TextIO
19
- from pathlib import Path
20
- import tkinter as tk
21
- from wizard_ui_bridge import AnswerFields, AskField, AskFields, PartialCheck, \
22
- PartialFormValidator, PathAskOptions, TableCell, TableColumn, \
23
- WizardUiBridge
24
- from backlogops import NoTextIO
25
- from backlogops_gui.wizard_form import handles_field
26
- from backlogops_gui.wizard_window import WizardWindow
27
-
28
-
29
- class TkWizardBridge(WizardUiBridge):
30
- """Bridge that answers wizard prompts in one reused Tkinter window."""
31
-
32
- def __init__(self, parent: tk.Misc, log: Optional[TextIO] = None) -> None:
33
- """Store the parent window and the optional diagnostics log.
34
-
35
- Args:
36
- parent: The window the wizard window is shown over.
37
- log: Stream that receives low-level wizard diagnostics.
38
- """
39
- self._parent = parent
40
- self._log = log
41
- self._window: Optional[WizardWindow] = None
42
-
43
- def ask_text(self, question: str, re_ask_reason: Optional[str] = None,
44
- nullable: bool = False, *, default: Optional[str] = None,
45
- sensitive: bool = False) -> Optional[str]:
46
- """Ask for free text; see WizardUiBridge.ask_text."""
47
- if sensitive and default is not None:
48
- raise ValueError('default is not allowed for sensitive input')
49
- return self._window_obj().ask_text(question, re_ask_reason, nullable,
50
- default, sensitive)
51
-
52
- # pylint: disable-next=too-many-arguments
53
- def ask_int(self, question: str, re_ask_reason: Optional[str] = None, *,
54
- nullable: bool = False, min_value: Optional[int] = None,
55
- max_value: Optional[int] = None,
56
- default: Optional[int] = None) -> Optional[int]:
57
- """Ask for an integer within optional bounds; see ask_int."""
58
- return self._window_obj().ask_int(question, re_ask_reason, nullable,
59
- min_value, max_value, default)
60
-
61
- def ask_path(self, question: str, re_ask_reason: Optional[str] = None, *,
62
- options: Optional[PathAskOptions] = None) -> Optional[Path]:
63
- """Ask for a path with a native file or directory picker."""
64
- path_options = PathAskOptions() if options is None else options
65
- return self._window_obj().ask_path(question, path_options,
66
- re_ask_reason)
67
-
68
- def ask_yes_no(self, question: str, default: bool,
69
- re_ask_reason: Optional[str] = None) -> bool:
70
- """Ask a yes/no question with dedicated yes and no buttons."""
71
- return self._window_obj().ask_yes_no(question, default, re_ask_reason)
72
-
73
- def ask_choice(self, question: str, *, choices: Sequence[str],
74
- default: Optional[str] = None,
75
- re_ask_reason: Optional[str] = None) -> str:
76
- """Ask the user to pick one choice from a single-selection list."""
77
- return self._window_obj().ask_choice(question, choices, default,
78
- re_ask_reason)
79
-
80
- # pylint: disable-next=too-many-arguments
81
- def ask_multi(self, question: str, *, choices: Sequence[str],
82
- default: Optional[Sequence[str]] = None, min_select: int = 0,
83
- max_select: Optional[int] = None,
84
- re_ask_reason: Optional[str] = None) -> list[str]:
85
- """Ask the user to pick several choices from a multi-selection list."""
86
- return self._window_obj().ask_multi(question, choices, default,
87
- min_select, max_select,
88
- re_ask_reason)
89
-
90
- # pylint: disable-next=too-many-arguments
91
- def ask_table(self, columns: Sequence[TableColumn],
92
- cells: list[list[TableCell]], question: str, *,
93
- re_ask_reason: Optional[str] = None,
94
- partial_check: Optional[PartialCheck] = None,
95
- min_rows: Optional[int] = None,
96
- max_rows: Optional[int] = None) -> list[list[Optional[str]]]:
97
- """Ask the user to fill an editable table of the given rows.
98
-
99
- With both ``min_rows`` and ``max_rows`` given the table has a
100
- variable number of rows: add-row and remove-row buttons grow the
101
- table up to ``max_rows`` and shrink it down to ``min_rows``.
102
- Otherwise the rows given in ``cells`` are fixed and only filled.
103
- """
104
- return self._window_obj().ask_table(columns, cells, question,
105
- re_ask_reason, partial_check,
106
- min_rows, max_rows)
107
-
108
- def ask_form(self, long_question: str, ask_fields: AskFields, *,
109
- re_ask_reason: Optional[str] = None,
110
- partial_validator: Optional[PartialFormValidator] = None) \
111
- -> AnswerFields:
112
- """Ask a whole form on one screen; see WizardUiBridge.ask_form."""
113
- return self._window_obj().ask_form(long_question, ask_fields,
114
- re_ask_reason, partial_validator)
115
-
116
- def supports_form_field(self, field: AskField) -> bool:
117
- """Report that the Tk form shows every current field type."""
118
- return handles_field(field)
119
-
120
- def show(self, message: str) -> None:
121
- """Show an informational message to the user."""
122
- self._window_obj().show(message)
123
-
124
- def error_file(self) -> TextIO:
125
- """Return the stream used for low-level wizard diagnostics."""
126
- return self._log if self._log is not None else NoTextIO()
127
-
128
- def close(self) -> None:
129
- """Close the wizard window when one was opened."""
130
- if self._window is not None:
131
- self._window.close()
132
- self._window = None
133
-
134
- def _window_obj(self) -> WizardWindow:
135
- """Return the wizard window, creating it on first use."""
136
- if self._window is None:
137
- self._window = WizardWindow(self._parent)
138
- return self._window