Export command (CSV + ODS), file dialog, error modal, path persistence.
New user-facing features:
- `export connections <file>` writes a tabular dump of every wire pair:
connection, transform, left/right module/part/pin/signal/type/suspect,
mixed-types flag. Dispatch on extension: `.csv` (flat file) or `.ods`
(one sheet per connection). Any other extension shows an error and
writes nothing.
- Bare `export` (or dashboard `[x]`, or palette `export`) opens an
interactive file-picker dialog with a CSV/ODS toggle at the top.
Picking a filter rewrites the filename's extension. Last-used
directory and filename are remembered per-call-site.
- Two new CLI flags on the binary: `--source FILE` to run a script at
boot, `--restore FILE` to restore a snapshot at boot. Combinable.
Reusable infrastructure:
- `OdsWriter` (`src/imports/ods_writer.{hpp,cpp}`): minimal .ods writer
using libzip + pugixml (already in the build for the importer).
Multi-sheet workbook of string cells. ~180 lines, no new dep.
- Generic file-picker dialog (`screen_filedialog.cpp`): one Modal
reused for any "pick a path" interaction via
`OpenFileDialog(title, persist_key, default_filename, filters, cb)`.
Validates the picked extension against the filter whitelist;
unknown ones stay in the dialog with a status message. Persists
(dir, filename) per `persist_key`.
- Generic error modal (`screen_error.cpp`, `ShowError(msg)`): centred
red-titled popup, dismissable with Esc/Enter. Used by the export
failures (open-for-write, ODS save, unknown extension/kind);
ready for adoption elsewhere.
- Per-key path persistence (`SaveLastUsed`/`LoadLastUsed` in
`shell.cpp`): two-line file per key under the user-data dir.
- `UserDataDir()` extracted from the history path helper so the new
per-key persistence shares the same XDG/AppData logic.
- New help-screen topic "Export"; user-facing `doc/user/analysis.md`
gains an "Exporting" section; `DESIGN.md` gains a generics
section covering the dialog / error modal / persistence / ODS
writer; `DumpCommandsMd` now respects the `hidden` flag (the
`connect` alias no longer appears in the auto-gen reference).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
26
DESIGN.md
26
DESIGN.md
@@ -38,11 +38,12 @@ src/
|
||||
analysis.{hpp,cpp} analyze_system → AnalysisReport (diff pairs, buses, anomalies)
|
||||
persist.{hpp,cpp} save / restore (tab-delimited)
|
||||
system.{hpp,cpp} System: owns Modules + Connections, exposes Load()
|
||||
imports/ -- adapters that populate the domain
|
||||
imports/ -- adapters that populate or emit the domain
|
||||
import_base.hpp ImportBase interface
|
||||
import_mentor.{hpp,cpp} Mentor Graphics netlist parser
|
||||
import_altium.{hpp,cpp} Altium netlist parser (`[ ]` parts, `( )` signals)
|
||||
import_ods.{hpp,cpp} ODS spreadsheet pinout (libzip + pugixml)
|
||||
ods_writer.{hpp,cpp} Minimal .ods writer (multi-sheet, string cells)
|
||||
tui/ -- FTXUI shell, split by responsibility
|
||||
tui.{hpp,cpp} Class Tui (state + Run() orchestrator + screen-mode event dispatcher)
|
||||
tui_helpers.{hpp,cpp} Free helpers: ToLower, NaturalLess, LongestCommonPrefix
|
||||
@@ -56,6 +57,9 @@ src/
|
||||
screen_dashboard.cpp BuildDashboardScreen (read-only system overview)
|
||||
screen_analyze.cpp BuildAnalyzeScreen (anomalies / groups / power decisions)
|
||||
screen_palette.cpp BuildPaletteModal (global Ctrl-P fuzzy launcher)
|
||||
screen_filedialog.cpp BuildFileDialog + OpenFileDialog (reusable file picker)
|
||||
screen_error.cpp BuildErrorModal + ShowError (centred error popup)
|
||||
screen_help.cpp BuildHelpScreen (topic-driven feature reference)
|
||||
screen_sigtype_modal.cpp BuildSignalTypeModal (popup attached to explore via Modal())
|
||||
doc/classes.puml -- PlantUML class diagram
|
||||
```
|
||||
@@ -158,6 +162,26 @@ Exposed as the `analyze` shell command which prints groups (sorted by module + l
|
||||
|
||||
Everything is recomputed every frame so manual overrides via the signal-type popup are reflected immediately. Esc returns to the dashboard. The dashboard's previous `[v]erify` letter shortcut was removed — its content is fully covered by this screen. The textual `verify` / `analyze` commands still exist for scripts.
|
||||
|
||||
**Generic file-picker dialog** (`screen_filedialog.cpp`): one reusable modal for every "pick a path" interaction. State lives in `Tui::file_dialog` (a single `FileDialogState`); attached to the tab tree via `Modal(BuildFileDialog(), &file_dialog.open)` in `Run()`. API:
|
||||
|
||||
- `OpenFileDialog(title, persist_key, default_filename, filters, on_confirm)` — opens the modal, restoring the last-used `(dir, filename)` for `persist_key` if previously saved.
|
||||
- `ConfirmFileDialog()` runs on Enter on the OK button: validates against the filter whitelist (rejects unknown extensions with an in-dialog status message), persists `(dir, filename)` under `persist_key`, closes the modal, then calls `on_confirm(full_path)`.
|
||||
|
||||
The optional `filters` vector (`{label, extension}` pairs) renders a horizontal Toggle at the top of the dialog. The first frame after Open seeds the filter index from the filename's current extension; subsequent index changes rewrite the filename extension so the caller's extension-based dispatch picks the right format. Empty filter list ⇒ no Toggle shown, no extension validation.
|
||||
|
||||
Today the only caller is `export` (`{"CSV", ".csv"}, {"ODS", ".ods"}` filters, key `export.connections`), but future callers (`save`, `restore`, `source`, …) plug in by changing four arguments.
|
||||
|
||||
**Generic error modal** (`screen_error.cpp`, `Tui::ShowError(msg)`): centred `borderRounded` popup with red title, the message (wrapped via `paragraph`), and an OK button. Esc / Enter dismiss. Stacked at the top of the Modal chain in `Run()` so it overlays every screen and every other modal. The error is also `Print()`-ed to the console log, so it remains inspectable after dismissal. Used by the export action (unknown extension, open-for-write failure, ODS save failure, unknown kind); other actions can adopt by replacing user-visible `Print("...failed...")` calls with `ShowError(...)`.
|
||||
|
||||
**Per-key path persistence** (`SaveLastUsed(key, dir, filename)` / `LoadLastUsed(key, &dir, &filename)` in `shell.cpp`): each key writes a tiny two-line file (`dir\nfilename\n`) under `UserDataDir() / <key>.last`. `UserDataDir()` is the cross-platform `XDG_DATA_HOME` / `LOCALAPPDATA` etc. helper also used by the command history file. Free functions, not Tui members, so any module (the file dialog today; could be the script-save buffer or the save command tomorrow) can use them with the same minimal API.
|
||||
|
||||
**ODS writer** (`src/imports/ods_writer.{hpp,cpp}`): minimal OpenDocument Spreadsheet writer. Backed by libzip + pugixml (both already pulled in by the ODS importer). Class hierarchy is two structs:
|
||||
|
||||
- `OdsSheet` — sparse row-major grid of string cells (`set(row, col, value)`).
|
||||
- `OdsWriter` — owns the sheets, emits a valid `.ods` archive with `mimetype` (stored uncompressed, magic header), `META-INF/manifest.xml`, and `content.xml`.
|
||||
|
||||
`content.xml` is built with pugixml: one `<table:table>` per sheet, each row a `<table:table-row>` of string `<table:table-cell>` cells. String-only by design (no numbers, dates, formulas, styles, merges) — the format minimum for "rectangular text data" is concise enough to live in ~200 lines without depending on a heavyweight ODS library.
|
||||
|
||||
**Command palette** (`screen_palette.cpp`): a global modal launcher attached to the whole tab tree via `tab | Modal(BuildPaletteModal(), &palette_open)` in `Run()`. Trigger: `Event::CtrlP` (FTXUI Input does not consume Ctrl-P, so the outer `CatchEvent` reliably picks it up first). Behaviour: a single Input bound to `palette_query` plus a result list rebuilt on every frame. Indexes three kinds of entries: commands (from the `commands` map), modules and per-module signals (qualified as `module/signal`). Fuzzy match is subsequence-based, case-insensitive: lower score wins, computed as `first_match_position * 100 + sum_of_gaps`. Kinds are biased by a constant offset (commands +0, modules +1000, signals +2000) so command matches come first when scores tie. Output capped at 20 rows to keep render cheap on big systems. Activation (`Enter`): commands → `Dispatch(name)` (which dispatches like the shell, including opening interactive screens), module → prefill `explore_*` state and jump to `screen_idx = 4`, signal → prefill `net_modules` + seed `net_sig_filter` to the exact signal name and jump to `screen_idx = 5`. `Esc` closes the palette. While the palette is open, the outer `CatchEvent` cedes events to it so Tab/Esc/etc. don't leak into the underlying screen.
|
||||
|
||||
**Dashboard** (`screen_dashboard.cpp`, `dashboard` command, `screen_idx = 4`): read-only system overview. Single Renderer, no Input child. Recomputes everything per frame (cheap on realistic sizes): counters (modules/parts/signals/connections), three health rows (verify pin-role mismatches, bridged-net inconsistencies, NC orphans — green check / yellow warning prefix), an analysis summary line (diff pairs / buses / anomaly count, coloured if non-zero), and a per-module table (parts / signals / `connector_type`-tagged parts). Letter shortcuts handled in the outer `CatchEvent`: `c`=console, `p`=plug (connect), `t`=set-connector-type, `e`=explore, `a`=analyze, `q`=quit. `Esc` is swallowed on the dashboard (home). The dashboard is `interactive = true`, `scriptable = false`; running `dashboard` inside `source` aborts the script.
|
||||
|
||||
Reference in New Issue
Block a user