# TProgram  (guide pp. 502–512)

Rust module(s): src/app/program.rs (also src/app/mod.rs, src/app/application.rs, src/capture.rs, src/command.rs)   |   magiblot: include/tvision/app.h / source/tvision/tprogram.cpp

> TProgram is a TGroup descendant and the application keystone. It documents **no
> own fields** (it inherits the group container; the application-wide handles are
> unit-level globals). Its surface is 18 methods + 3 application palettes + the
> AppPalette index. The whole class is reshaped by deviation **D9** (the single
> event loop: `pump_once`/`run` replace the nested modal `execute` loops) and
> **D2** (`TApplication : TProgram` inheritance → `Application` embeds a `Program`
> and delegates). The original's static globals (`Application`, `Desktop`,
> `MenuBar`, `StatusLine`, `AppPalette`) become owned fields / handles on
> `Program`, and the class `Palette` triple becomes `tv::Theme` (deviation D7).

## Globals (TProgram's unit-level statics — App unit)

| Guide entry | Pg | Bucket | Corr | Rust symbol / mapping | Doc | Notes |
|---|---|---|---|---|---|---|
| `Application` (global var) | 502/503 | EQUIVALENT | OK | the owned `Program` value itself (`Program::new`) | N/A | C++ `Application: PProgram` is the `@Self` pointer set in `Init`. There is no global singleton: the app owns one `Program` (or `Application` wrapper) value. Idiomatic mapping (raw pointer/global → owned value). |
| `Desktop` (global var) | 502 | EQUIVALENT | OK | `Program.desktop: Option<ViewId>` + `Program::desktop()` | 3 | Static global pointer → an owned `ViewId` handle (idiomatic `infoPtr → ViewId`). Resolved via `group.find_mut(id)`. Rustdoc now covers stable lifetime, prefer `desktop_insert` for opening windows, heritage note on `tprogram.h`. |
| `MenuBar` (global var) | 502 | EQUIVALENT | OK | `Program.menu_bar: Option<ViewId>` + `Program::menu_bar()` | 3 | As above. Rustdoc now covers when to use the id directly (custom method via downcast) vs. the idiomatic command-enablement path. Heritage note. |
| `StatusLine` (global var) | 502 | EQUIVALENT | OK | `Program.status_line: Option<ViewId>` + `Program::status_line()` | 3 | As above; read by `pump_once` for keyDown/over-line mouseDown pre-routing (getEvent pre-route). Rustdoc now covers pump pre-routing behavior and when to use the id. Heritage note citing `tprogram.cpp:153`. |
| `AppPalette` (global var) | 504/507 | EQUIVALENT | OK | `Program.theme: Theme` (private field) | N/A | C++ `AppPalette` is an integer index (apColor/apBlackWhite/apMonochrome) selecting one of three palette strings. Here the active palette is a whole `tv::Theme` value (private field; no public getter — passed into the paint `DrawCtx`). The mono/B&W variant selection is folded into `Theme` construction (`classic_blue()` is the color default). Idiomatic mapping (class Palette → Theme, D7). |

## Methods

| Guide entry | Pg | Bucket | Corr | Rust symbol / mapping | Doc | Notes |
|---|---|---|---|---|---|---|
| `Init` (constructor) | 503 | EQUIVALENT | OK | `Program::new(backend, clock, theme, create_desktop, create_status_line, create_menu_bar)` | 3 | C++ `Init` sets `Application:=@Self`, calls `InitScreen`, `TGroup.Init` over the full-screen rect, sets state `sfVisible+sfSelected+sfFocused+sfModal+sfExposed`, Options:=0, Buffer:=ScreenBuffer, then `InitDesktop`/`InitStatusLine`/`InitMenuBar` and inserts the three views. Rust: builds the `Group` over `(0,0,w,h)`, sets `active/selected/focused/modal` directly (sfExposed dropped — whole-tree redraw, no occlusion), runs the three injected factory closures (the InitDesktop/InitStatusLine/InitMenuBar virtual hooks → D2 factory-mixin) in **desktop, status-line, menu-bar** order, seeds the initial command-graying, and runs the startup currency settle. Documented heritage. Faithful. |
| `Done` (destructor) | 503 | EQUIVALENT | OK | `Drop for Program` (implicit) | N/A | C++ `Done` disposes Desktop/MenuBar/StatusLine, sets `Application:=nil`, calls inherited `Done`. Rust relies on ownership: dropping the `Program` drops its `Group` (and the three children) — no explicit destructor needed, no global to null. Idiomatic (RAII). |
| `CanMoveFocus` (method) | 503 | PORTED | OK | inline in `program_handle_event`: `desktop.valid(Command::RELEASED_FOCUS, ctx)` | N/A | C++ `CanMoveFocus` returns True iff `deskTop->valid(cmReleasedFocus)`. Rust realizes it inline in the Alt-N window-selection block (`can = desktop … valid(Command::RELEASED_FOCUS, ctx)`) before `select_window_num`, with the faithful three-way clear matrix. Not a standalone public method — folded into its sole caller. Comment explicitly notes "canMoveFocus(): deskTop->valid(cmReleasedFocus) — desktop-specific, NOT the root group's valid()". |
| `ExecuteDialog` (method) | 503 | EQUIVALENT | OK | `Program::exec_view` / `exec_view_with_completion` (+ `message_box`, `input_box`, `open_file_dialog`, `color_dialog`) | 3 | C++ `ExecuteDialog(P, Data)` = `ValidView(P)` → `deskTop->ExecView(pD)` → on close `GetData`/`SetData` transfer + dispose. Rust splits this: `exec_view` is the modal driver (single-loop + `ModalFrame` capture, D9); the typed value currency (D10) replaces `setData`/`getData` (`input_box` seeds/reads the lone field, `Dialog` gather/scatter for multi-field). **Documented deviation:** Rust **root-inserts** the modal (faithful to C++ `application->execView`, msgbox.cpp) rather than `deskTop->execView` (the `executeDialog` variant); the comment flags the off-center caveat when the desktop is inset. Rustdoc now adds Rust-first "when to call" guidance and links to typed wrappers. |
| `GetEvent` (method) | 504 | EQUIVALENT | OK | event-pick block in `pump_once` (steps 3/3b) | N/A | C++ `GetEvent`: check pending `PutEvent` buffer → `GetMouseEvent` → `GetKeyEvent` → `Idle` if none, then pre-route evKeyDown/evMouseDown to the StatusLine. Rust `pump_once`: drain `out_events` (the PutEvent successor + posted commands/timer broadcasts) **first**, else `backend.poll_event(timeout)`; then the evMouseAuto synthesizer; then the **status-line pre-routing** (keyDown always; mouseDown only when the line is the topmost view under the cursor) before dispatch. The idle path (no event) is the `None` arm. Faithful to `tprogram.cpp` getEvent ordering (the pre-route comment cites tprogram.cpp:153). Not a standalone public method — it is the loop body (D9). |
| `GetPalette` (method) | 504 | EQUIVALENT | OK | `Program.theme: Theme` + `Role`-keyed lookups (D7) | N/A | C++ `GetPalette` returns the `AppPalette`-indexed palette (apColor/apBlackWhite/apMonochrome). Rust has no palette-index getter: the active `Theme` is carried into every `DrawCtx` and views resolve colors by `Role` (theme.rs). Idiomatic mapping (class Palette → Theme). The three application palettes map onto Theme roles (see Palettes table). |
| `HandleEvent` (method) | 504 | PORTED | OK | `program_handle_event` (free fn) | N/A | C++ `HandleEvent`: inherited TGroup handling, then Alt+1..Alt+9 → evBroadcast `cmSelectWindowNum`/InfoInt 1..9 (TWindow selects itself), then evCommand `cmQuit` → `EndModal(cmQuit)`. Rust `program_handle_event`: Alt-1..9 window-select runs **before** `group.handle_event` (faithful order — the C++ Alt-N pre-check sits ahead of inherited dispatch), then group dispatch, then the `cmQuit → end_state=Some(QUIT)` catch, plus the TApplication-level `cmTile`/`cmCascade`/`cmDosShell` arms and an app-command deposit slot (`app_commands`, the TApplication::handleEvent hook). **Documented deviation:** Alt-N uses a direct desktop walk (`select_window_num`), not an `evBroadcast{source}` — the comment explains the int payload is not a polymorphic infoPtr subject. **Documented deviation (D9):** program-level interception is NOT suppressed during a modal (single loop), so `cmQuit` ends a modal and quits even from inside a dialog (flagged on `exec_view`). |
| `Idle` (method) | 505 | EQUIVALENT | OK | the `None` (no-event) arm of `pump_once` | N/A | C++ `Idle` calls `StatusLine->Update` (re-find items against the top view's help context), then broadcasts `cmCommandSetChanged` once if the command set changed since the last Idle. Rust `None` arm: emits the queued `cmCommandSetChanged` broadcast when `command_set_changed` (then clears it), collects expired timers (as typed `Event::Timer(id)`), and updates the status line's help context from the top modal view (or `group.get_help_ctx()` when no modal — faithful TopView() walk to the sfModal root, cited tview.cpp:879 / tstatusl.cpp:209). Faithful; cmCommandSetChanged is fired on idle, not per-change, matching C++. |
| `InitDesktop` (method) | 505 | EQUIVALENT | OK | `create_desktop: impl FnOnce(Rect) -> Option<Box<dyn View>>` ctor closure | N/A | C++ virtual factory storing into the `Desktop` global. Rust: the injected `create_desktop` closure (the factory-mixin replacing the virtual override hook, D2). The closure owns its own rect-shrinking (the `r.a.y++; r.b.y--` inset), faithfully noted in the `new` doc. The result is `group.insert`ed and its id stored in `Program.desktop`. |
| `InitMenuBar` (method) | 504 | EQUIVALENT | OK | `create_menu_bar: impl FnOnce(Rect) -> Option<Box<dyn View>>` ctor closure | N/A | C++ virtual factory storing into `MenuBar` (almost always overridden by the app). Rust: the injected `create_menu_bar` closure (may return `None` to omit the bar — the empty-default case). Inserted third; id stored in `Program.menu_bar`. |
| `InitScreen` (method) | 505 | EQUIVALENT | OK | folded into `Program::new` + the resize check at the top of `pump_once` | N/A | C++ `InitScreen` adjusts screen-mode-dependent vars (shadow size, markers, AppPalette) on every Init / SetScreenMode. Rust has no DOS screen-mode concept: the backend reports its size (`backend.size()`), `new` builds over `(0,0,w,h)`, and `pump_once` step 1 detects a live terminal resize and relayouts (`renderer.resize` + `group.change_bounds`) — the realization of setScreenMode/cmScreenChanged. Idiomatic (DOS screen-mode machinery → terminal size + resize). |
| `InitStatusLine` (method) | 506 | EQUIVALENT | OK | `create_status_line: impl FnOnce(Rect) -> Option<Box<dyn View>>` ctor closure | N/A | C++ virtual factory storing into `StatusLine`. Rust: the injected `create_status_line` closure (may return `None`). Inserted second; id stored in `Program.status_line`. |
| `InsertWindow` (method) | 506 | EQUIVALENT | OK | `Program::desktop_insert` (+ `Application` has none of its own; `exec_view` for modals) | 3 | C++ `InsertWindow(P)`: `ValidView(P)` → `CanMoveFocus` → if OK `Desktop->Insert(P)` and return P, else dispose and return nil. Rust `desktop_insert` inserts into the desktop and focuses the child (`Desktop::insert_and_focus`), returning the new `ViewId` or `None`. **Partial:** the `CanMoveFocus` (validation-protects-the-active-window) and `ValidView` (LowMemory/`valid(cmValid)`) guards of C++ `InsertWindow` are **not** applied here (no LowMemory in Rust; the active-window validation gate is enforced at focus-move time via the Alt-N `valid(RELEASED_FOCUS)` path and on modal close, not at desktop insert). See Notes / gap-report. Rustdoc now adds "standard way to open a window at runtime" call-site guidance + `run_app` context. |
| `OutOfMemory` (method) | 506 | NOT-PORTED | — | — | — | C++ `OutOfMemory` alerts the user when `LowMemory` is True (DOS 640K heap pressure). No analog: Rust has no `LowMemory` global / DOS heap manager; allocation failure aborts. DOS-era memory machinery → no analog. |
| `PutEvent` (method) | 506 | EQUIVALENT | OK | `Context::put_event` → `Program.out_events` (front of `pump_once`'s pick) | 3 | Verified: `Context::put_event` doc (score 3) explains re-queue any `Event` (not just `Command`), landing in `out_events` before backend poll, typical use (re-post outside click after menu, forward deferred key), and relationship to `post`/`broadcast`. C++ `PutEvent(E)` stores one event in a buffer; Rust's `VecDeque` `out_events` is a deliberate superset (multi-slot). Cross-ref: documented in the TView sweep. |
| `Run` (method) | 507 | PORTED | OK | `Program::run` (+ `run_app` for the app-command hook) | 3 | C++ `Run` = inherited `TGroup::Execute`. Rust `run`: `loop { end_state=None; while none { pump_and_drive() }; if valid_end(es) return es }` — the outer `while !valid(endState)` of `TGroup::execute` (D9: one pump replaces the nested modal loop). `run_app(on_command)` adds the application-command drain (`app_commands`) so a menu-driven custom command reaches user code with `&mut Program`. Documented heritage + headless-busy-loop caveat. |
| `SetScreenMode` (method) | 507 | EQUIVALENT | OK | resize detection at the top of `pump_once` (step 1) | N/A | C++ `SetScreenMode(Mode)`: hide mouse → `SetVideoMode` → `InitScreen` → reassign Buffer → `ChangeBounds(new rect)` → show mouse, for smCO80/smBW80/smMono ± smFont8x8. Rust has no DOS video modes (no 43/50-line EGA/VGA toggle): the only "mode change" is a live terminal resize, handled in `pump_once` step 1 (`renderer.resize` + `group.change_bounds(0,0,w,h)`). The B&W/mono palette selection is a `Theme` choice at construction, not a runtime mode switch. Idiomatic (DOS smXXXX modes → terminal resize). |
| `ValidView` (method) | 507 | NOT-PORTED | — | — | — | C++ `ValidView(P)`: nil-check, LowMemory→`OutOfMemory`+dispose, `P->Valid(cmValid)` veto+dispose, else return P. No analog: Rust constructs views by value (no nil pointer), has no `LowMemory`, and the `valid(cmValid)` instantiation veto is not modeled — modal close runs `valid(end_cmd)` instead (exec_view's modal-close gate / `valid_end`). The DOS-pointer-validation role has no Rust counterpart; the surviving piece (close-time `valid`) lives in `exec_view`/`valid_end`. |

## Palettes (pp. 507–511)

| Guide entry | Pg | Bucket | Corr | Rust symbol / mapping | Doc | Notes |
|---|---|---|---|---|---|---|
| `CAppColor` palette (entries 1–127) | 507–511 | EQUIVALENT | OK | `Theme::classic_blue()` + `Role` map (theme.rs) | N/A | The v2.0 color application palette (background, menu/status 2–7, blue/cyan/gray windows 8–31, gray/blue/gray dialog objects 32–127). Rust expresses the whole color mapping as `tv::Theme` keyed by `Role` (D7) with definite RGB pinned via `Color::bios_rgb` (the classic_blue default). The byte-attribute table → typed `Style`/`Role` lookups. Idiomatic mapping (class Palette → Theme). Per-slot byte values are not individually re-tabulated; the role chain reproduces them. |
| `CAppBlackWhite` palette | 507–511 | EQUIVALENT | OK | a B&W `Theme` variant (theme.rs) | N/A | The black-and-white attribute palette (AppPalette index apBlackWhite). Folded into Theme construction rather than an indexed string. Idiomatic mapping (class Palette → Theme). |
| `CAppMonochrome` palette | 507–511 | EQUIVALENT | OK | a monochrome `Theme` variant (theme.rs) | N/A | The monochrome attribute palette (apMonochrome). As above. |
| `apColor` / `apBlackWhite` / `apMonochrome` (AppPalette indices) | 504/507 | EQUIVALENT | OK | the chosen `Theme` value (no integer index) | N/A | C++ `AppPalette` selects one of the three by integer; Rust selects by constructing/holding the corresponding `Theme`. No public index getter on `Program`. Idiomatic mapping. |
| `CColor` / `CBlackWhite` / `CMonochrome` (v1.0 compat palettes) | 507/508 | NOT-PORTED | — | — | — | The guide keeps the v1.0 64-entry palettes "for compatibility with existing programs that have extended the default palettes." No legacy-compat layer in a fresh port — only the v2.0 (CApp*) mapping is modeled. Deliberate (no v1.0 extension surface to preserve). |

## Summary

- PORTED: 3   EQUIVALENT: 22   NOT-PORTED: 3   MISSING: 0   UNSURE: 0
- SUSPECT: 0   |   doc<3 (public): 0   |   → concept: 0
- Reconciliation: `PutEvent` → `Context::put_event` verified score-3 in `src/view/context.rs`; re-scored to 3.
- Notable findings: No SUSPECT divergences — every reshape (single event loop D9, embed-and-delegate D2, factory-closure init hooks, AppPalette→Theme D7, PutEvent single-slot→queue, raw-pointer globals→ViewId handles) is a documented, commented deviation. The one genuine **coverage gap**: `InsertWindow`'s `CanMoveFocus`/`ValidView` guards are not applied at `desktop_insert` (the active-window validation-protection on window insertion); C++ `InsertWindow` runs `ValidView` then `CanMoveFocus` and disposes the window if the active window can't release focus. In Rust the focus-release gate exists only on Alt-N selection and modal close, not on desktop insert — worth a gap-report entry. `OutOfMemory`/`ValidView`/v1.0-compat-palettes are intentional DOS-era / legacy NOT-PORTED items with written reasons.
