|
| 1 | +# Changelog |
| 2 | + |
| 3 | +All notable changes to **php-gui** are documented here. |
| 4 | +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to semantic versioning. |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## [v1.9] — 2026-04-26 |
| 9 | + |
| 10 | +A foundation release. Closes the four production blockers from the v1.0 plan and ships twelve new essential widgets. Tests grew from 159 → **453 passing assertions** across 30 files. |
| 11 | + |
| 12 | +If you're upgrading from v1.8, the changes are **backwards-compatible at the public API level** — all v1.8 code continues to work, but you also get safer defaults, a faster event loop, and a much larger widget toolbox. |
| 13 | + |
| 14 | +### Highlights |
| 15 | + |
| 16 | +- **Twelve new widgets** — `Text`, `Scrollbar`, `Listbox`, `Radiobutton` (with `RadioGroup`), `Scale`, `Spinbox`, `Progressbar`, `Notebook`, `Treeview`, `PanedWindow`, `LabelFrame`, `Separator`. The library now covers the full table-stakes set for desktop apps. |
| 17 | +- **Animated GIFs** — the `Image` widget now plays multi-frame GIFs with proper disposal and transparency compositing. |
| 18 | +- **JPEG + BMP via GD** — `Image` transcodes formats Tk core can't read into temp PNGs. |
| 19 | +- **Arbitrary widget nesting** — Frames inside Frames inside Windows now work the way you'd expect. |
| 20 | +- **Tcl-injection eliminated** — every widget now safely quotes user-supplied strings. |
| 21 | +- **Event loop is in-process** — no more `/tmp/phpgui_callback.txt`, no 100 ms input lag, no cross-process collisions. |
| 22 | + |
| 23 | +--- |
| 24 | + |
| 25 | +### Added — New widgets |
| 26 | + |
| 27 | +#### `Text` |
| 28 | +Multi-line editor with safe value routing through Tcl variables, `setText` / `getText` / `append` / `insertAt` / `clear`, length and line counts, `setState('disabled')` for read-only log views (still accepts `append()`). Strict index validation on `insertAt()` blocks Tcl injection through index expressions. |
| 29 | + |
| 30 | +#### `Scrollbar` |
| 31 | +Wraps `ttk::scrollbar` with manual `bindTo($target)` two-way wiring, plus a one-call `Scrollbar::attachTo($target, $orient)` factory that creates, binds, and packs in one shot. Both orientations. |
| 32 | + |
| 33 | +#### `Listbox` |
| 34 | +Selectable list with all four Tk select modes (`browse`, `single`, `multiple`, `extended`). Full add/remove/replace/clear, all selection accessors, `<<ListboxSelect>>` virtual-event binding via `onSelect()`. Items round-trip Tcl-special characters literally. |
| 35 | + |
| 36 | +#### `Radiobutton` + `RadioGroup` |
| 37 | +First-class `RadioGroup` object backing a single Tcl variable — solves the silent foot-gun where two radios accidentally share `-variable` names. Per-radio `command` callback receives the value; group-level `onChange` fires for both user clicks and programmatic `setValue()`. |
| 38 | + |
| 39 | +#### `Scale` |
| 40 | +Slider input with `from`/`to`/`orient`, `getValue`/`setValue`, and `onChange` that fires uniformly for user drag and programmatic updates. |
| 41 | + |
| 42 | +#### `Spinbox` |
| 43 | +Bounded numeric input or fixed enumeration via `values`. `onChange` is wired to `<Return>` and `<FocusOut>` in addition to Tk's `-command`, so direct typing fires the handler. |
| 44 | + |
| 45 | +#### `Progressbar` |
| 46 | +Both determinate (`setValue` / `step`) and indeterminate (`start(intervalMs)` / `stop`) modes; `setMode()` switches at runtime; all inputs validated. |
| 47 | + |
| 48 | +#### `Notebook` |
| 49 | +Tabbed container wrapping `ttk::notebook`. `addTab` / `selectTab` / `selectPage` / `setTabState` (`normal` / `disabled` / `hidden`); `onTabChange` bound to `<<NotebookTabChanged>>`. Enforces page-is-child invariant at `addTab()` time so the silent "empty pane" Tk failure mode can't happen. |
| 50 | + |
| 51 | +#### `Treeview` |
| 52 | +Hierarchical list / multi-column table — the single biggest missing widget for desktop apps. Two operating modes: |
| 53 | +- Flat tables with named columns and headings |
| 54 | +- Hierarchies (folder trees, outlines) |
| 55 | + |
| 56 | +Positional and keyed value APIs, full selection management (single + multi), `onSelect` and `onDoubleClick` (with click-coordinate-to-row resolution), column / heading configuration, `delete` cascades to descendants. All values go through safe-quoting; row IDs are deterministic and Tcl-safe. |
| 57 | + |
| 58 | +#### `PanedWindow` |
| 59 | +Resizable split container; `addPane` with `weight`-based space distribution, `setSashPosition` / `getSashPosition`, both orientations. |
| 60 | + |
| 61 | +#### `LabelFrame` |
| 62 | +Titled bordered container — a `Frame` whose top edge is broken by a small caption. Optional title (empty produces a plain bordered frame). |
| 63 | + |
| 64 | +#### `Separator` |
| 65 | +Thin horizontal or vertical divider line. |
| 66 | + |
| 67 | +--- |
| 68 | + |
| 69 | +### Added — Image widget improvements |
| 70 | + |
| 71 | +- **Animated GIFs** — full multi-frame playback with disposal handling, on-load frame compositing onto a logical-screen-sized canvas, and per-frame snapshot pre-decoding so frame swaps never flash transparent gaps. New `getFrameCount()` / `isAnimated()`. |
| 72 | +- **JPEG + BMP support** — transparent transcoding through PHP's GD extension to a temp PNG before Tk loads it. Original path is preserved by `getPath()`; the temp PNG is unlinked automatically on `setPath()` and `destroy()`. |
| 73 | +- **Safe Tcl quoting** for all option values; paths route through Tcl variables so spaces, brackets, `$`, and quotes never break command parsing. |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +### Added — Window event hooks |
| 78 | + |
| 79 | +- `Window::onClose(callable)` — runs your handler before the application exits. Returning `false` vetoes the close. Useful for "save unsaved changes?" prompts. |
| 80 | +- `Window::onResize(callable)` — fires `(int $width, int $height)` on actual resizes (descendant `<Configure>` events and same-size duplicates are filtered out). |
| 81 | + |
| 82 | +--- |
| 83 | + |
| 84 | +### Added — Public API additions |
| 85 | + |
| 86 | +- `AbstractWidget::tclQuote(string)` — central safe-quoting helper. |
| 87 | +- `AbstractWidget::buildOptionString(array $skip)` — replaces every per-widget option-formatter; routes every value through `tclQuote()`. |
| 88 | +- `AbstractWidget::getParentId()` / `getTclPath()` — used internally by factories like `Scrollbar::attachTo`, also useful for advanced custom widgets. |
| 89 | +- `ProcessTCL::drainPendingCallbacks()` — drains the new in-process callback queue (returns count for tests). |
| 90 | +- `ProcessTCL::shouldQuit()` — reads the new `::phpgui_quit` flag. |
| 91 | +- `ProcessTCL::unregisterCallback($id)` — releases a previously-registered callback. |
| 92 | + |
| 93 | +--- |
| 94 | + |
| 95 | +### Changed |
| 96 | + |
| 97 | +- **Event loop replaced** — `/tmp/phpgui_callback.txt` polling is gone. Tcl-side handlers now `lappend ::phpgui_pending $id`; the PHP event loop drains that list every tick. Loop cadence dropped from 100 ms to 10 ms (~100 Hz responsiveness). Lost-callback bug fixed (queue holds an arbitrary number of IDs); cross-process collision fixed (no shared filesystem state). |
| 98 | +- **Quit signal** — `/tmp/phpgui_quit.txt` removed; `::exit_app` now sets `::phpgui_quit 1`. |
| 99 | +- **`Application::quit()`** — no longer calls `exit(0)`. Flips the run flag and tears down WebViews, then `Application::run()` returns to the caller. You can finalise (logging, cleanup) before the script ends. |
| 100 | +- **Widget destruction** — `AbstractWidget::destroy()` now also calls `unregisterCallback($this->id)`. `Menu::destroy()` cascades through its per-command callback IDs and submenus. `Window::destroy()` frees the `onClose` / `onResize` IDs. |
| 101 | +- **Widget nesting** — `AbstractWidget` now tracks the full Tk path (`getTclPath()`) instead of assuming one level under root. Frames inside Frames inside Windows finally work as advertised. |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +### Fixed |
| 106 | + |
| 107 | +- **Tcl injection across all widgets.** Before v1.9, a label whose text contained `Hello"; destroy .; "` would execute the embedded `destroy` command. Now every widget runs user-supplied strings through `tclQuote()` (or, where possible, sets a Tcl variable directly via FFI). Documented in `docs/security.md`. Regression suite in `tests/widgets_test/TclInjectionTest.php`. |
| 108 | +- **Lost callbacks.** Two events fired between event-loop ticks no longer collapse to one. |
| 109 | +- **Callback memory leaks.** Closures registered by destroyed widgets are now released. |
| 110 | +- **`docs/Frame.md` was aspirational.** The Tcl-path refactor makes its nesting examples actually work. |
| 111 | +- **Animated GIFs flickered transparent dots.** Pre-decoded frame snapshots remove the per-tick reload flash. |
| 112 | +- **GIF `setPath` after animation produced "couldn't recognize data" errors.** Reset `-format` to `{}` when swapping to non-GIFs. |
| 113 | +- **Spinbox `value` shorthand was clobbering `-values`.** Tk partial-matched `-value` to `-values` and overwrote the enumeration list. |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +### Documentation |
| 118 | + |
| 119 | +New pages in `docs/`: |
| 120 | + |
| 121 | +- `security.md` — Tcl-quoting policy and threat model. |
| 122 | +- `event-loop.md` — IPC redesign, test patterns, callback lifecycle. |
| 123 | +- `Image.md`, `Text.md`, `Scrollbar.md`, `Listbox.md`, `Radiobutton.md`, `Scale.md`, `Spinbox.md`, `Progressbar.md`, `Notebook.md`, `Treeview.md`, `PanedWindow.md`, `LabelFrame.md`, `Separator.md`. |
| 124 | + |
| 125 | +`docs/Window.md` updated with `onClose` / `onResize` examples. `docs/_sidebar.md` updated to list every new page. |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +### Tests |
| 130 | + |
| 131 | +- 159 → **453 passing assertions** across 30 files. |
| 132 | +- New suites: `EventLoopTest`, `ImageTest`, `LabelFrameTest`, `ListboxTest`, `NestingTest`, `NotebookTest`, `PanedWindowTest`, `ProgressbarTest`, `RadiobuttonTest`, `ScaleTest`, `ScrollbarTest`, `SeparatorTest`, `SpinboxTest`, `TclInjectionTest`, `TextTest`, `TreeviewTest`, `WindowEventsTest`. |
| 133 | + |
| 134 | +Run any suite with `php tests/widgets_test/<Name>Test.php` — exits non-zero on failure. |
| 135 | + |
| 136 | +--- |
| 137 | + |
| 138 | +### Upgrade notes |
| 139 | + |
| 140 | +No breaking changes. If you were relying on the temp-file behaviour (`/tmp/phpgui_callback.txt`, `/tmp/phpgui_quit.txt`) from outside the library, those files no longer exist. If you called `Application::quit()` and counted on `exit(0)`, add an explicit `exit;` at the end of your script. |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +## [v1.8] — earlier |
| 145 | + |
| 146 | +See `git log v1.7..v1.8`. |
| 147 | + |
| 148 | +## Earlier releases |
| 149 | + |
| 150 | +Earlier release tags (`v1.0` through `v1.7`) are in the git history. |
0 commit comments