Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,57 @@ Character substring assignment, complex schema values, nested derived values,
component arrays, nondefault lower bounds, and user-defined formatted I/O are
currently explicit capability boundaries.

## GUI

Install the current checkout with its optional GUI dependencies and a Qt binding
(Python 3.9 or newer):

```bash
python -m pip install '.[gui]' PyQt5
nml-tools gui -i /path/to/schemas -o /path/to/project
```

QtPy also permits other supported Qt bindings. Omit `-i` to use the current
directory, and `-o` to save alongside `nml-config.toml`.

The editor loads each profile's `default_file` from the output directory and
saves directly to that namelist file. Missing values use schema defaults, then
examples or type-specific suggestions. Invalid input is reported. No JSON
configuration or JSON-to-namelist conversion is involved. Selected groups are
rewritten on save; other groups and their comments remain intact.

Each profile has a tab with a namelist list, editable pages, navigation, reset,
cancel, and save actions. The Config tab applies runtime dimensions with Run;
the `+` tab can import a `.nml` file or create another profile. One-element
one-dimensional arrays use inline scalar or derived-object editors while
remaining arrays in the saved namelist. Resizable arrays retain a resize action.

Applications can launch a subset of the configured profiles:

```python
from nml_tools.gui import launch_gui

launch_gui(
schemas_dir="/path/to/schemas",
output_dir="/path/to/project",
file_profiles={"main": ["mainconfig", "time_periods"], "parameter": []},
initial_values={"main": {"mainconfig": {"nDomains": 2}}},
initial_dimensions={"max_domains": 2},
)
```

`file_profiles` is the third argument. `None` or `{}` selects all configured
profiles; an empty list selects every namelist in that profile. Names are
checked case-insensitively and pages retain TOML order. `initial_values` is an
in-memory dictionary of profile/namelist/field values applied over existing
input. Use keyword arguments for values and dimensions when migrating callers
of the previous GUI API.

Named runtime dimensions come from TOML, `initial_dimensions`, or the Config
controls; partial namelist assignments cannot reliably recover them. Existing
Qt applications reuse their QApplication; importing `nml_tools.gui` alone
does not import Qt.

## Error handling

Generated type-bound procedures return integer status codes and accept an
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ dependencies = [
]

[project.optional-dependencies]
gui = [
"guidata; python_version >= '3.9'",
"numpy; python_version >= '3.9'",
"qtpy; python_version >= '3.9'",
]
dev = [
"numpy>=1.24,<3",
"pytest-cov",
Expand Down
28 changes: 28 additions & 0 deletions src/nml_tools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,34 @@ def cli(verbose: int, quiet: int) -> None:
_configure_logging(verbose, quiet)


@cli.command("gui", context_settings=_CONTEXT_SETTINGS)
@click.option(
"--input-path", "-i",
type=click.Path(exists=True, file_okay=False, path_type=Path),
help="Directory containing nml-config.toml and schemas (default: current directory).",
)
@click.option(
"--output-path", "-o",
type=click.Path(file_okay=False, path_type=Path),
help="Directory for namelist files (default: input path).",
)
def gui(input_path: Path | None, output_path: Path | None) -> None:
"""Edit and save namelist files using schema-driven Qt forms."""
try:
from .gui import launch_gui

exit_code = launch_gui(input_path, output_path)
except ImportError as exc:
raise click.ClickException(
"GUI dependencies are unavailable; install 'nml-tools[gui]' "
f"and a Qt binding: {exc}"
) from exc
except (OSError, RuntimeError, ValueError) as exc:
raise click.ClickException(f"failed to start GUI: {exc}") from exc
if exit_code:
raise Exit(exit_code)


@cli.command("generate", context_settings=_CONTEXT_SETTINGS)
@click.option(
"--config",
Expand Down
27 changes: 27 additions & 0 deletions src/nml_tools/gui/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Optional Qt namelist editor; Qt is imported only when launching the GUI."""

from __future__ import annotations

from collections.abc import Mapping
from pathlib import Path
from typing import Any

__all__ = ["launch_gui"]


def launch_gui(
schemas_dir: Path | str | None = None,
output_dir: Path | str | None = None,
file_profiles: Mapping[str, list[str]] | None = None,
initial_values: Mapping[str, Any] | None = None,
initial_dimensions: Mapping[str, int] | None = None,
) -> int:
"""Edit selected profiles; empty lists select all namelists in that profile.

None or an empty mapping selects all configured profiles. Values are nested
by profile, namelist, and field, and override existing namelist input.
Dimensions override TOML defaults. Output defaults to schemas_dir.
"""
from .app import launch_gui as _launch_gui

return _launch_gui(schemas_dir, output_dir, file_profiles, initial_values, initial_dimensions)
Loading