Skip to content

feat: show file info in headerbar and add right-click copy menu#2

Merged
green2grey merged 4 commits into
mainfrom
feat/info-copy-menu
Jul 6, 2026
Merged

feat: show file info in headerbar and add right-click copy menu#2
green2grey merged 4 commits into
mainfrom
feat/info-copy-menu

Conversation

@green2grey

Copy link
Copy Markdown
Member

Summary

Closes out Phase 2 (info display) and Phase 5 (context-menu copy) of the phased plan.

  • Headerbar info (FR-003): the Full Viewer headerbar now uses adw::WindowTitle — filename as title, 1920×1080 · 2.4 MB as subtitle (glib::format_size), updating immediately on arrow-key navigation. Decode failures show "Could not load image" instead of stale info.
  • Plumbing: ViewerController::connect_file_loaded callback fired from load_file on success and failure; replays the last file's info on late registration so the initial file is covered.
  • Right-click menu (FR-005): canvas.copy / canvas.copy-all widget actions on ZoomableCanvas with a PopoverMenu at the cursor. Copy is enabled when a selection exists; Copy All Text joins every OCR word in reading order once OCR completes. Both windows (Full Viewer and Quick Preview) get the menu via the shared canvas.
  • One code path for copy: clipboard access moved into the widget so Ctrl+C and the menu behave identically.
  • Also bumps PHASED_PLAN to v0.2 (Phase 2/5 ✅, Phase 6 status corrected, FR-002 + ADR-0004 async/sandboxed-loading tasks tracked) and adds a .gitignore rule for local working notes.

Test plan

  • cargo fmt / cargo clippy --all-targets --all-features -- -D warnings clean
  • cargo test --all: 17/17 pass, including 5 new unit tests for format_subtitle (normal, missing size, 0 bytes, >1 GB, load-failure)
  • Manually verified on Wayland: headerbar updates on navigation, right-click menu enablement before/after OCR, Copy and Copy All Text output

Closes Phase 2 (info) and Phase 5 (context menu):

- ViewerController exposes connect_file_loaded; load_file emits FileInfo
  (name, dimensions, size, load_failed) on success and decode failure,
  replaying the last info on late registration so the initial file shows.
- Full Viewer headerbar uses adw::WindowTitle: filename as title,
  "1920x1080 - 2.4 MB" subtitle via glib::format_size, or
  "Could not load image" on failure. format_subtitle unit-tested.
- ZoomableCanvas installs canvas.copy / canvas.copy-all widget actions;
  right-click opens a PopoverMenu at the cursor. Copy All Text joins all
  OCR words in reading order. Enablement tracks selection, OCR arrival,
  and image switches; popover unparented in dispose.
- Clipboard access moved into the widget so Ctrl+C and the menu share
  one code path; both windows get the menu via the shared canvas.

Also: PHASED_PLAN v0.2 (Phase 2/5 done, Phase 6 status corrected, FR-002
and ADR-0004 async/sandboxed-loading tasks tracked) and .gitignore rule
for local working notes.
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0053858a-e2f0-4393-8ae8-5d10a60a4a15

📥 Commits

Reviewing files that changed from the base of the PR and between d46b4b5 and 544231f.

📒 Files selected for processing (4)
  • crates/quickview-ui/src/widgets/image_overlay.rs
  • crates/quickview-ui/src/windows/full_viewer.rs
  • crates/quickview-ui/src/windows/quick_preview.rs
  • crates/quickview-ui/src/windows/shared.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/quickview-ui/src/windows/quick_preview.rs
  • crates/quickview-ui/src/windows/full_viewer.rs
  • crates/quickview-ui/src/windows/shared.rs
  • crates/quickview-ui/src/widgets/image_overlay.rs
📜 Recent review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: build-test

Summary by CodeRabbit

  • New Features

    • Added a right-click context menu to copy selected text or all extracted text, with availability-aware enabling.
    • Improved clipboard support for selected content and full extracted text, including Ctrl+C in both viewers.
    • File name is now shown in the header with a live subtitle for dimensions, file size, or load-failure feedback.
  • Bug Fixes

    • Load failures now immediately update the viewer and clear related overlays to prevent stale content.
  • Documentation

    • Updated the phased plan progress and expanded UX, integration, and hardening notes.
  • Chores

    • Updated local working-notes ignore rules.

Walkthrough

Adds canvas clipboard actions and a context menu, emits FileInfo on file load, updates viewer subtitles and copy shortcuts, and revises planning notes and local ignore rules.

Changes

Clipboard menu, file metadata, and docs updates

Layer / File(s) Summary
Canvas clipboard actions and context menu
crates/quickview-ui/src/widgets/image_overlay.rs
Adds copy actions, clipboard helpers, a right-click context menu, selection-dependent action enablement, and shared view-state reset handling; ImageOverlayWidget exposes delegating wrappers.
FileInfo emission and clipboard API update
crates/quickview-ui/src/windows/shared.rs
Adds FileInfo and file-loaded callback plumbing, emits file metadata on decode success and failure, and removes the display argument from clipboard copying.
Viewer window header and copy handling
crates/quickview-ui/src/windows/full_viewer.rs, crates/quickview-ui/src/windows/quick_preview.rs
Switches to adw::WindowTitle, subscribes to file-loaded updates, formats the subtitle from FileInfo, updates Ctrl+C to the no-argument copy helper, and adds subtitle-formatting tests.
Docs and ignore rules
docs/PHASED_PLAN.md, .gitignore
Updates planning metadata and phase text, and adds TODO.local.md to local ignore rules.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ZoomableCanvas
  participant Popover
  participant Clipboard

  User->>ZoomableCanvas: right-click
  ZoomableCanvas->>ZoomableCanvas: show_context_menu(x, y)
  ZoomableCanvas->>Popover: create or reuse menu
  User->>Popover: choose Copy or Copy All Text
  Popover->>ZoomableCanvas: activate canvas.copy / canvas.copy-all
  ZoomableCanvas->>Clipboard: set_text(if non-empty)
Loading
sequenceDiagram
  participant FullViewer
  participant ViewerController
  participant WindowTitle

  FullViewer->>ViewerController: connect_file_loaded(callback)
  ViewerController->>ViewerController: load_file(...)
  ViewerController-->>FullViewer: FileInfo
  FullViewer->>FullViewer: format_subtitle(&FileInfo)
  FullViewer->>WindowTitle: set_subtitle(text)
Loading

Possibly related PRs

  • Green2Grey2/QuickView#1: Extends the same ZoomableCanvas and viewer wiring with the clipboard/menu and file-metadata flow built here.

Poem

A bunny clicked, a menu sprang,
Copy and all-copy softly sang. 🐇
The header shows the file just right,
And tiny notes are tucked from sight.
Hop-hop, the canvas learned some grace,
With cleaner paths in every place.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main user-facing changes: headerbar file info and a right-click copy menu.
Description check ✅ Passed The description covers the main changes and testing, but it does not follow the repo's checklist template exactly.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 feat/info-copy-menu

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

cargo deny's advisories check fails on anyhow 1.0.102 (unsound
downcast_mut, dtolnay/anyhow#451; fixed in 1.0.103). Unrelated to this
branch's changes — any branch would fail CI until the lockfile moves.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/quickview-ui/src/windows/shared.rs`:
- Around line 104-141: The load failure path in load_file leaves the previous
image visible because only ImageOverlayWidget::set_texture updates
state.texture. In the Err(err) branch of load_file, clear the current canvas
texture in addition to resetting OCR state so the overlay matches the new
FileInfo with load_failed=true and the newly selected filename. Use the existing
load_file and ImageOverlayWidget::set_texture flow to locate the right place for
the reset.
🪄 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: c3931d9b-a76f-44e9-8c6c-91ee1c9767b6

📥 Commits

Reviewing files that changed from the base of the PR and between 5e3c518 and b25f0ce.

📒 Files selected for processing (6)
  • .gitignore
  • crates/quickview-ui/src/widgets/image_overlay.rs
  • crates/quickview-ui/src/windows/full_viewer.rs
  • crates/quickview-ui/src/windows/quick_preview.rs
  • crates/quickview-ui/src/windows/shared.rs
  • docs/PHASED_PLAN.md
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: lint
  • GitHub Check: build-test
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Use rustfmt and clippy for code formatting and linting, as tracked in rust-toolchain.toml
Run clippy with all targets and features enabled with -D warnings flag to treat warnings as errors

Files:

  • crates/quickview-ui/src/windows/quick_preview.rs
  • crates/quickview-ui/src/windows/full_viewer.rs
  • crates/quickview-ui/src/windows/shared.rs
  • crates/quickview-ui/src/widgets/image_overlay.rs
🔇 Additional comments (6)
.gitignore (1)

32-34: LGTM!

docs/PHASED_PLAN.md (1)

120-120: Phase 6 status still says “partially done.”

This conflicts with the updated completion state described in the change notes. If the section is meant to be finalized, update the heading to keep the document internally consistent.

crates/quickview-ui/src/widgets/image_overlay.rs (1)

80-83: LGTM!

Also applies to: 107-123, 133-139, 303-326, 343-364, 463-473, 537-537, 618-618, 794-831

crates/quickview-ui/src/windows/shared.rs (1)

17-33: LGTM!

Also applies to: 46-49, 65-67, 86-103, 175-177

crates/quickview-ui/src/windows/full_viewer.rs (1)

6-9: LGTM!

Also applies to: 19-32, 43-49, 84-145

crates/quickview-ui/src/windows/quick_preview.rs (1)

43-43: LGTM!

Comment thread crates/quickview-ui/src/windows/shared.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 32c0deae0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

model.append(Some("Copy All Text"), Some("canvas.copy-all"));

let popover = gtk::PopoverMenu::from_model(Some(&model));
popover.set_parent(self);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Allocate the popover from the custom widget

Because ZoomableCanvas is a custom gtk::Widget with no layout manager or size_allocate implementation, parenting a GtkPopoverMenu to it here is not enough for GTK to allocate and position that popover; custom popover parents need to present the popover during size allocation. In the new right-click path this means the context menu can be unallocated, misplaced, or fail to show. Add a size_allocate vfunc that presents the stored popover, or host it under a widget that already supports popover children.

Useful? React with 👍 / 👎.

Addresses PR #2 review findings:

- clear_texture() resets the canvas (texture, dims, OCR, selection,
  view) when a decode fails, so the display matches the load_failed
  headerbar info instead of showing the previous image (CodeRabbit).
- size_allocate now presents the context-menu popover: custom widgets
  without a layout manager must call gtk_popover_present() during
  allocation or the popover can be unallocated/mispositioned after
  window resizes (Codex).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d46b4b570a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +131 to 132
self.overlay.clear_texture();
self.overlay.set_ocr_busy(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invalidate OCR jobs when decode fails

When a decode failure happens after a previous image's OCR job is still running, this branch clears the canvas and returns without advancing ocr_job_id. The pending spawn_local receiver still sees job_id == job_id_cell.get() and can call set_ocr_result for the previous file, re-enabling stale OCR text/Copy All Text while the header reports the new file failed to load. Bump or otherwise invalidate the OCR job id before returning from this failure path.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
crates/quickview-ui/src/widgets/image_overlay.rs (1)

574-643: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

canvas.copy enablement lags during an active selection drag.

on_drag_begin (Line 571) and on_drag_end (Line 652) call update_copy_actions(), but on_drag_update — where selected_indices is actually mutated while selecting (Lines 604-637) — never does. The canvas.copy action (and therefore its Ctrl+C keybinding, per shared.rs) will not reflect the live selection until the drag finishes.

🛠️ Proposed fix
         drop(state);
         if needs_redraw {
             self.queue_draw();
+            self.update_copy_actions();
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/quickview-ui/src/widgets/image_overlay.rs` around lines 574 - 643, The
live selection state in on_drag_update is not propagated to the copy action, so
canvas.copy stays stale until drag end. Update on_drag_update in
image_overlay.rs to call update_copy_actions whenever selected_indices changes
during the selecting branch, alongside the existing queue_draw path, so the
Ctrl+C binding reflects the current drag selection immediately.
🧹 Nitpick comments (2)
crates/quickview-ui/src/widgets/image_overlay.rs (2)

497-506: 🎯 Functional Correctness | 🔵 Trivial | ⚖️ Poor tradeoff

No keyboard path to open the context menu.

The menu only opens via right-click (GestureClick on BUTTON_SECONDARY). Consider also wiring the conventional "Menu" key / Shift+F10 shortcut so keyboard-only users can reach Copy/Copy All Text.

Also applies to: 828-850

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/quickview-ui/src/widgets/image_overlay.rs` around lines 497 - 506, The
context menu in the image overlay is only reachable through the right-click
GestureClick path, so add a keyboard-triggered entry point as well. Update the
context menu handling in image_overlay.rs around the existing right_click setup
and the related menu code near show_context_menu so the conventional Menu key
and Shift+F10 both invoke the same menu logic, giving keyboard-only users access
to the Copy and Copy All Text actions.

299-338: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate reset logic between set_texture and clear_texture.

Both methods reset the same eight CanvasState fields (image_width, image_height, ocr, ocr_index, zoom_factor, center_img, selecting, panning, pinch_active, selected_indices), diverging only in the texture/dimension source. Consider extracting a shared reset_view_state(&mut CanvasState) helper to avoid the two copies drifting apart on future changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/quickview-ui/src/widgets/image_overlay.rs` around lines 299 - 338,
Both image_overlay::set_texture and image_overlay::clear_texture duplicate the
same CanvasState reset steps, so extract that shared state-reset logic into a
helper such as reset_view_state on the widget/state to keep behavior consistent.
Keep set_texture responsible only for applying the new gtk::gdk::Texture and its
dimensions, and let clear_texture reuse the same helper while still clearing the
texture-specific fields separately so the two paths cannot drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/quickview-ui/src/widgets/image_overlay.rs`:
- Around line 574-643: The live selection state in on_drag_update is not
propagated to the copy action, so canvas.copy stays stale until drag end. Update
on_drag_update in image_overlay.rs to call update_copy_actions whenever
selected_indices changes during the selecting branch, alongside the existing
queue_draw path, so the Ctrl+C binding reflects the current drag selection
immediately.

---

Nitpick comments:
In `@crates/quickview-ui/src/widgets/image_overlay.rs`:
- Around line 497-506: The context menu in the image overlay is only reachable
through the right-click GestureClick path, so add a keyboard-triggered entry
point as well. Update the context menu handling in image_overlay.rs around the
existing right_click setup and the related menu code near show_context_menu so
the conventional Menu key and Shift+F10 both invoke the same menu logic, giving
keyboard-only users access to the Copy and Copy All Text actions.
- Around line 299-338: Both image_overlay::set_texture and
image_overlay::clear_texture duplicate the same CanvasState reset steps, so
extract that shared state-reset logic into a helper such as reset_view_state on
the widget/state to keep behavior consistent. Keep set_texture responsible only
for applying the new gtk::gdk::Texture and its dimensions, and let clear_texture
reuse the same helper while still clearing the texture-specific fields
separately so the two paths cannot drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 429cab6c-cd6b-48c7-b0ad-ed86e87f75a9

📥 Commits

Reviewing files that changed from the base of the PR and between b25f0ce and d46b4b5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • crates/quickview-ui/src/widgets/image_overlay.rs
  • crates/quickview-ui/src/windows/shared.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/quickview-ui/src/windows/shared.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: build-test
  • GitHub Check: lint
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Use rustfmt and clippy for code formatting and linting, as tracked in rust-toolchain.toml
Run clippy with all targets and features enabled with -D warnings flag to treat warnings as errors

Files:

  • crates/quickview-ui/src/widgets/image_overlay.rs
🔇 Additional comments (3)
crates/quickview-ui/src/widgets/image_overlay.rs (3)

111-127: LGTM on popover lifecycle wiring.

present() in size_allocate and unparent() in dispose() are the documented idiomatic patterns for custom widgets that own a popover child.

Also applies to: 140-158


63-66: Copy-action enablement plumbing looks solid.

update_copy_actions() is invoked from set_texture, clear_texture, set_ocr_result, clear_selection, on_drag_begin, and on_drag_end, correctly reflecting selection/OCR state changes across load/reset flows, matching the upstream clear_texture/set_ocr_result contracts in shared.rs.

Also applies to: 84-87, 318-337, 340-352, 354-361, 571-572, 652-653, 852-864


828-850: 🎯 Functional Correctness

set_halign is meaningful here. GtkPopover uses halign in its placement logic, so set_halign(gtk::Align::Start) is not dead configuration.

			> Likely an incorrect or invalid review comment.

- Bump ocr_job_id in the decode-failure path so an in-flight OCR job
  from the previous image can't repopulate the cleared canvas (Codex).
- Refresh copy-action enablement during drag updates, not just at drag
  boundaries (CodeRabbit).
- Add keyboard access to the context menu: Menu key and Shift+F10 open
  it at the pointer (or widget center), per NFR-005 (CodeRabbit).
- Extract CanvasState::reset_view_state so set_texture/clear_texture
  share one reset path instead of duplicating it (CodeRabbit).
@green2grey green2grey merged commit dd33bc9 into main Jul 6, 2026
3 checks passed
@green2grey green2grey deleted the feat/info-copy-menu branch July 6, 2026 07:21
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