Skip to content

Featue: Add portable open- and save-file dialog.#29

Merged
kimkulling merged 5 commits intomainfrom
feature/add_standard_dialogs
Apr 11, 2026
Merged

Featue: Add portable open- and save-file dialog.#29
kimkulling merged 5 commits intomainfrom
feature/add_standard_dialogs

Conversation

@kimkulling
Copy link
Copy Markdown
Owner

@kimkulling kimkulling commented Apr 10, 2026

  • New compiler definition to identify the platform.
  • Add open-file-dialog
  • Add save-file-dialog

Summary by CodeRabbit

  • New Features

    • Added native open/save file dialogs with cross-platform behavior.
  • Bug Fixes

    • Improved window/renderer initialization and cleanup to avoid resource issues.
  • Refactor

    • Strengthened const-correctness and reduced mutable access across core and rendering; update now treats context as read-only.
  • Chores

    • Added platform selection macros and two new return codes; minor scope and local-scope improvements.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bafee5f5-f4c1-4ed0-8737-bf5986c759c3

📥 Commits

Reviewing files that changed from the base of the PR and between 7b04bfd and ba3a7bf.

📒 Files selected for processing (1)
  • src/widgets.cpp

Walkthrough

Const-correctness and local-scope refinements across core and SDL renderer; added cross-platform file dialog APIs (open/save) in Widgets; small SDL init/cleanup change and sample init-statement cleanups.

Changes

Cohort / File(s) Summary
Renderer / core constness & platform config
src/backends/sdl2_renderer.h, src/backends/sdl2_renderer.cpp, src/tinyui.cpp, src/tinyui.h
Changed Renderer::update to accept const Context&; cast backend context as const; made local context bindings const auto&; initScreen now calls sdlCtx->destroy() when a window exists; added platform selection macros and two new public return codes (OpCancelled, InvalidContext).
Widgets & file-dialog APIs
src/widgets.h, src/widgets.cpp
Added Widgets::getOpenFileDialog and Widgets::getSaveFileDialog (return ret_code, fill std::string &filename); Windows uses WinAPI (OPENFILENAME), non-Windows uses zenity via popen; returns OpCancelled on cancel.
Samples: scope/init-statement refinements
samples/demo/main.cpp, samples/hello_world/main.cpp
Moved Style/context creation into if-init statements, tightened constness of context bindings, removed an unused NextPanelId constant.
Local refactors & type-deduction
src/widgets.cpp, src/backends/sdl2_renderer.cpp, samples/*
Replaced explicit local pointer/type declarations with auto* / const auto& / const Widget*; adjusted payload casts to const in progress/tree handling; minor whitespace and formatting tweaks.

Sequence Diagram(s)

sequenceDiagram
    participant W as Widgets
    participant Core as TinyUi/Core
    participant OS as OperatingSystem
    W->>Core: getOpenFileDialog(title, extensions, filename)
    Core->>OS: invoke platform dialog (WinAPI or zenity)
    OS-->>Core: dialog result (path or cancel)
    Core-->>W: return ret_code and filename (if selected)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title contains a typo ('Featue' instead of 'Feature') and accurately describes the main change: adding portable file dialogs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/add_standard_dialogs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/tinyui.h`:
- Around line 241-248: The merge logic uses top.x + r.width and bottom.y +
r.height which derives from the current rect instead of r.top; change the
computed bounds to use r.top.x and r.top.y when calculating x2_ and y2_ (i.e.,
compare against r's top-based edges), update bottom/top accordingly, and then
recompute width and height as bottom.x - top.x and bottom.y - top.y (replace the
assignments width = r.width / height = r.height). Update the code paths that use
x2_ and y2_ (variables x2_ and y2_) and the width/height assignments to follow
this new calculation.
- Around line 76-79: Remove the Win32 includes from the public header tinyui.h
and add them only in the implementation file that needs them: delete the
conditional `#include` block for windows.h/Commdlg.h from tinyui.h, and instead
add a conditional `#include` for Windows.h and Commdlg.h (use the exact casing) at
the top of src/widgets.cpp so the Win32 dialog implementations
(getOpenFileDialog and getSaveFileDialog) can access the Win32 APIs without
polluting all consumers; keep the includes guarded by `#ifdef` TINYUI_WINDOWS to
preserve cross-platform builds.

In `@src/widgets.cpp`:
- Line 842: The build error comes from using an undefined type alias `c8`;
replace every occurrence of `c8` with the standard `char` (e.g., change the
declarations like `c8 buffer[BufferSize] = { '\0' };` and the other `c8`
declaration later) so the code compiles on non-Windows systems; update both
declarations referencing `c8` in this file (the buffer variable and the second
`c8` usage) to `char`.
- Around line 843-849: The Zenity branches misuse popen modes, omit pclose,
ignore title/extensions, fail to strip trailing newlines, and lack error
handling: change the save-dialog popen to read mode ("r") instead of "w", add
the flags "--save --confirm-overwrite" to the save command, include optional
"--title" and "--file-filter" (or "--file-filter='EXT'") when title/extensions
are provided for both open and save commands, check f for nullptr and verify
fgets/feof results before using buffer, strip a trailing '\n' from buffer before
assigning to filename, call pclose(f) on all control paths, and propagate/return
false on errors. Apply these fixes around the zenity popen/fgets/pclose usage
where filename, buffer, BufferSize, and f are referenced.
- Around line 813-840: The Windows and Linux dialog implementations have three
issues: define the missing alias c8 (e.g., using c8 = char;) so non-Windows
builds compile; in Widgets::getOpenFileDialog and getSaveFileDialog fix the
OPENFILENAME lpstrFilter usage by converting the incoming extensions string into
the double-null-terminated Windows filter format and call the explicit ANSI or
wide API consistently (use GetOpenFileNameA/GetSaveFileNameA with char buffers
or the W variants with wide strings) to match your buffer types; and in the
Linux path for getSaveFileDialog change the popen mode to "r" when reading
zenity output (or use the same read mode used in getOpenFileDialog) so fgets()
can read the result.

In `@src/widgets.h`:
- Around line 341-353: The two dialog APIs in Widgets (getOpenFileDialog and
getSaveFileDialog) must return a ret_code (int32_t) instead of bool so callers
can distinguish selected/cancelled/failed; change the declarations of
getOpenFileDialog(const char*, const char*, std::string&) and
getSaveFileDialog(const char*, const char*, std::string&) to return int32_t (or
the project's ret_code typedef), update their documentation comments to describe
the ret_code semantics (e.g., RET_OK/RET_CANCEL/RET_FAIL), update all callers to
handle the new ret_code values, and ensure the implementations map existing bool
outcomes to the appropriate ret_code constants and include any required header
for ret_code/int32_t.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 907b5fa3-9d47-4680-b5c5-6f95520678f7

📥 Commits

Reviewing files that changed from the base of the PR and between f0fdce0 and 3b8a200.

📒 Files selected for processing (8)
  • samples/demo/main.cpp
  • samples/hello_world/main.cpp
  • src/backends/sdl2_renderer.cpp
  • src/backends/sdl2_renderer.h
  • src/tinyui.cpp
  • src/tinyui.h
  • src/widgets.cpp
  • src/widgets.h

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/widgets.cpp (1)

584-588: ⚠️ Potential issue | 🔴 Critical

fillRate is read from uninitialized payload bytes.

Line 584 treats payload->payload as a FilledState, but Widgets::progressBar only copies EventPayload.type into mContent on Lines 510-514. The first render can therefore read garbage and draw an arbitrary width.

🔧 Suggested fix spanning the writer and reader paths
-    FilledState state;
-    state.filledState = fillRate;
     child->mContent = new uint8_t[sizeof(EventPayload)];
     child->mCallback = callback;
-    EventPayload payload;
+    EventPayload payload{};
     payload.type = EventDataType::FillState;
-    memcpy(child->mContent, &payload, sizeof(EventPayload));
+    auto *storedState = reinterpret_cast<FilledState *>(payload.payload);
+    storedState->filledState = fillRate;
+    memcpy(child->mContent, &payload, sizeof(payload));
-                const auto *state = reinterpret_cast<FilledState *>(payload->payload);
+                const auto *state = reinterpret_cast<const FilledState *>(payload->payload);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets.cpp` around lines 584 - 588, The code reads a FilledState from
payload->payload in Widgets::progressBar (using const auto *state =
reinterpret_cast<FilledState *>(payload->payload) and then reading
state->filledState) even though the writer only copied EventPayload.type into
mContent; fix by ensuring the writer and reader agree on the payload layout:
either update the writer that populates mContent to memcpy/serialize the entire
FilledState (not just EventPayload.type) when emitting this event, or change the
reader in Widgets::progressBar to validate payload size and safely deserialize
into a local FilledState (e.g., check payload->size >= sizeof(FilledState) and
memcpy into a local variable) before using filledState; reference FilledState,
Widgets::progressBar, mContent, and EventPayload.type when making the change.
♻️ Duplicate comments (1)
src/widgets.cpp (1)

813-849: ⚠️ Potential issue | 🔴 Critical

The new dialog helpers still have cross-platform blockers.

Line 828/870 passes extensions straight to lpstrFilter, but Win32 expects double-null-terminated filter pairs, and the generic GetOpenFileName / GetSaveFileName macros do not match char buffers in UNICODE builds. On the non-Windows side, Line 842 uses undefined c8, Line 888 uses undefined cchar, and Line 884 opens a write-only pipe before reading it with fgets(). Both Zenity branches also skip fgets()/pclose() result handling, keep the trailing newline, and ignore title / extensions, so the implementation does not match the public API yet.

#!/bin/bash
set -e
sed -n '811,895p' src/widgets.cpp | cat -n
rg -n '\b(c8|cchar)\b' src/ --type cpp --type h
rg -n 'lpstrFilter|GetOpenFileName|GetSaveFileName|popen|fgets|pclose' src/widgets.cpp
What format does OPENFILENAMEA::lpstrFilter require for GetOpenFileNameA/GetSaveFileNameA, and do the generic GetOpenFileName/GetSaveFileName macros work with char buffers when UNICODE is enabled?

Also applies to: 854-892

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets.cpp` around lines 813 - 849, Widgets::getOpenFileDialog currently
passes extensions directly to ofn.lpstrFilter and uses the ANSI macros that
break in UNICODE builds, references undefined types c8/cchar, and mishandles
popen/fgets/pclose and returned newline/title/extension handling on non-Windows;
fix by (1) on Windows explicitly use OPENFILENAMEA and GetOpenFileNameA when
working with char buffers (or convert to wchar_t and use
OPENFILENAMEW/GetOpenFileNameW), (2) build lpstrFilter as a
double-null-terminated pair string ("Description\0*.ext\0\0") and assign it to
ofn.lpstrFilter, (3) replace undefined c8/cchar with plain char (or std::string
buffer), (4) when using popen call with "r", check fgets return for null, strip
trailing newline before assigning filename, call pclose and check its result,
and (5) include title and extensions when constructing the zenity command (e.g.
--title and --file-filter) while escaping inputs; locate fixes around
Widgets::getOpenFileDialog, ofn.lpstrFilter,
GetOpenFileName/GetOpenFileNameA/GetOpenFileNameW, popen/fgets/pclose, and the
local buffer variables.
🧹 Nitpick comments (1)
src/widgets.cpp (1)

813-815: Consider ret_code here instead of bool.

These new public APIs currently collapse cancel, OS/API failure, and validation errors into the same false result. Returning ret_code would align them with the rest of the surface and let callers distinguish a canceled dialog from an actual failure. As per coding guidelines, "Use ret_code (int32_t) for return values" and "Return error codes for recoverable errors instead of throwing exceptions".

Also applies to: 854-856

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets.cpp` around lines 813 - 815, Change the public APIs from
returning bool to returning ret_code (int32_t) so callers can distinguish
success, user-cancel, and failures; specifically update the signature of
Widgets::getOpenFileDialog and the analogous getSaveFileDialog to return
ret_code, update all internal return points to return RET_OK on success,
RET_CANCEL (or defined cancel code) when the user cancels, and an appropriate
error code (e.g., RET_FAIL) for OS/API/validation failures, and adjust any
callers to handle these ret_code values accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/widgets.cpp`:
- Around line 584-588: The code reads a FilledState from payload->payload in
Widgets::progressBar (using const auto *state = reinterpret_cast<FilledState
*>(payload->payload) and then reading state->filledState) even though the writer
only copied EventPayload.type into mContent; fix by ensuring the writer and
reader agree on the payload layout: either update the writer that populates
mContent to memcpy/serialize the entire FilledState (not just EventPayload.type)
when emitting this event, or change the reader in Widgets::progressBar to
validate payload size and safely deserialize into a local FilledState (e.g.,
check payload->size >= sizeof(FilledState) and memcpy into a local variable)
before using filledState; reference FilledState, Widgets::progressBar, mContent,
and EventPayload.type when making the change.

---

Duplicate comments:
In `@src/widgets.cpp`:
- Around line 813-849: Widgets::getOpenFileDialog currently passes extensions
directly to ofn.lpstrFilter and uses the ANSI macros that break in UNICODE
builds, references undefined types c8/cchar, and mishandles popen/fgets/pclose
and returned newline/title/extension handling on non-Windows; fix by (1) on
Windows explicitly use OPENFILENAMEA and GetOpenFileNameA when working with char
buffers (or convert to wchar_t and use OPENFILENAMEW/GetOpenFileNameW), (2)
build lpstrFilter as a double-null-terminated pair string
("Description\0*.ext\0\0") and assign it to ofn.lpstrFilter, (3) replace
undefined c8/cchar with plain char (or std::string buffer), (4) when using popen
call with "r", check fgets return for null, strip trailing newline before
assigning filename, call pclose and check its result, and (5) include title and
extensions when constructing the zenity command (e.g. --title and --file-filter)
while escaping inputs; locate fixes around Widgets::getOpenFileDialog,
ofn.lpstrFilter, GetOpenFileName/GetOpenFileNameA/GetOpenFileNameW,
popen/fgets/pclose, and the local buffer variables.

---

Nitpick comments:
In `@src/widgets.cpp`:
- Around line 813-815: Change the public APIs from returning bool to returning
ret_code (int32_t) so callers can distinguish success, user-cancel, and
failures; specifically update the signature of Widgets::getOpenFileDialog and
the analogous getSaveFileDialog to return ret_code, update all internal return
points to return RET_OK on success, RET_CANCEL (or defined cancel code) when the
user cancels, and an appropriate error code (e.g., RET_FAIL) for
OS/API/validation failures, and adjust any callers to handle these ret_code
values accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5ddabfe0-0848-4481-b8f8-46034f87a252

📥 Commits

Reviewing files that changed from the base of the PR and between 3b8a200 and 4ecc261.

📒 Files selected for processing (1)
  • src/widgets.cpp

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
26.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@kimkulling kimkulling merged commit 96b40f1 into main Apr 11, 2026
2 of 3 checks passed
@kimkulling kimkulling deleted the feature/add_standard_dialogs branch April 11, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant