| Age | Commit message (Collapse) | Author |
|
Wire the navigator mark API into the window actions, grid badges, and
keyboard/pointer controls:
- Navigator: add a path-based range anchor (p_last_mark) set on `v`
toggle-on and cleared on unmark-of-anchor / clear_marks / remove /
mark_removed / rescan-prune; expose navigator_get_last_mark().
navigator_mark_removed now clears the removed file's mark (decision Q
/ task du0) and the anchor if it was it, without touching unrelated
marks, and emits "changed" whenever the removed set or marks actually
changed so grid badges and the header never go stale.
- Window: add win.mark-range (`V`: range from the last-mark anchor to the
current/grid-selected image), register it, wire Shift+V, and document
it in the shortcuts overlay. Trash/delete clear marks via mark_removed.
- Grid: middle-click on a cell selects it and dispatches win.mark
(pointer-accessible marks).
- Tests: unit test_marks_anchor_and_remove and
test_marks_anchor_pruned_on_rescan; integration test_marks.c covering
toggle, all+clear, range, persistence across view switch,
clear-on-trash, and the middle-click gesture wiring; updated
test_shortcut.c action table + count.
clang-format clean; full suite + ASan leak check green.
|
|
Instead of a transient 'a' popup, add a persistent enhance side panel
(GtkRevealer overlaid on the left of the main content) toggled by 'a'.
It lists every preset as a button showing its hotkey (1..8) plus a
'0 Original' row, and stays open while you compare presets.
Each optimization now has a dedicated global hotkey: 1-8 apply preset
1-8 directly (win.enhance-1 .. win.enhance-8), whether the panel is
visible or not. 'a' toggles the panel; 's' still saves the enhanced
copy; the panel's '0 Original' row (and navigating away) reverts.
Adds win.enhance-1..8 actions (one _action_enhance_n handler parses the
index from the action name) and the 1-8 keybindings; the ? overlay
Enhance group documents the panel + 1-8 + s. test_shortcut full-table
updated (33 rows, 29 actions).
|
|
The enhancer module existed (GEGL presets: Auto-fix, Brightness, Contrast,
Saturation, Warm, Cool, Sharpen, Denoise) but was never connected to the
app and GEGL was never initialized. Wire it up:
- app.c: gegl_init() once in GApplication::startup (GEGL-gated).
- enhancer.c: add enhancer_load(GFile) -> GeglBuffer (gegl:load) and
enhancer_buffer_to_texture(GeglBuffer) -> GdkTexture (RGBA8 ->
GdkMemoryTexture) for the live preview bridge.
- enhancer_export: pick the saver from the output extension (jpg/png/webp)
instead of always gegl:jpg-save (ju0 - was writing JPEG bytes into .png);
verify the save actually produced a non-empty newer file instead of
trusting g_file_test(EXISTS) on a pre-existing path (ku0 - false success).
- window.c: win.enhance (a) cycles the active preset and previews it on the
current image (switches to large view, loads via GEGL, applies, shows the
texture); win.enhance-save (s) exports <stem>-enhanced.<ext> with the
active preset, never overwriting the original. Preview resets on
navigation; the title shows the active preset.
- shortcuts.c: a -> win.enhance, s -> win.enhance-save; ? overlay Enhance
group.
- tests: test_enhancer gains load_and_to_texture, export_format (PNG/JPEG
signature checks), export_real_success (ku0 - parent-missing and
pre-existing-directory both return FALSE). LSAN suppressions cover
GEGL's jpg-load plugin leak. test_shortcut full-table updated (25 rows).
Synchronous apply (may briefly block on large images) and a navigate-away
dirty prompt are deliberately out of scope for v1.
|
|
The navigator already had a mark API and the grid rendered mark badges,
but nothing could set marks — so multi-file ops (D, Ctrl+c, m) only ever
acted on the single current image. Wire up marking in the UI:
- v toggles a mark on the highlighted grid cell (or the current image in
large view). navigator_toggle_mark does not emit "changed", so the
cell's badge is updated in place via ggaze_grid_update_mark_badge
(no reflow / re-decode).
- Ctrl+a marks all; Esc now clears marks first (contextual), then keeps
its existing back/quit behavior.
- The window title appends "N marked" so multi-selection is visible.
- A GtkCssProvider styles .ggaze-marked (accent border + tint) — the
class was set but had no styling, so badges were invisible.
Also add a ? -> win.shortcuts action that presents a GtkShortcutsWindow
(grouped: navigation, view, selection, files, zoom) built from an inline
GtkBuilder UI string, transient to the window.
tests/test_shortcut.c full-table test updated for the three new bindings
(v, Ctrl+a, ?) -> 23 rows, 19 actions.
|
|
The GtkShortcutController on the window used MANAGED scope, so the
viewer's own GtkEventControllerKey consumed key events before the
window-level win.* shortcuts were consulted — t (toggle-view) and Esc
(back) never fired. Switch to GLOBAL scope, which registers the
shortcuts with the toplevel's global shortcut manager consulted before
child key controllers. Comment notes the future-text-entry caveat.
New integration test tests/test_shortcut.c (3 subtests):
- controller_scope: asserts the window's shortcut controller is GLOBAL
(and is ours, not GtkApplicationWindow's mnemonic controller).
- keypath_toggle_and_back: verifies the t→win.toggle-view and
Escape→win.back bindings (bidirectional keyval/action check) and
dispatches them via gtk_shortcut_action_activate (the controller's own
dispatch primitive), asserting the stack flips large↔grid. GTK 4.22
exposes no public GdkKeyEvent synthesis API, so this is the most
faithful exercise of the keyboard shortcut path available.
- full_table_registered: asserts all 16 win.* actions and all 20
shortcut rows are registered, guarding against table regressions.
Notes a pre-existing, out-of-scope keybinding conflict: GTK4 normalizes
GDK_KEY_G/0 to GDK_KEY_g/0 in GtkKeyvalTrigger, so win.last (G) collides
with win.first (g); left unfixed per task's 'preserve existing behavior'.
|
|
M4 (task ot0): fullscreen + slideshow + info overlay.
- src/info.{c,h}: plain-C EXIF gather via libexif (camera, lens, focal,
aperture, shutter, ISO, datetime, orientation) + file info (dims, format,
size). info_format() produces the overlay text. Unit-tested.
- src/window.c: fullscreen toggle (f), slideshow (S, 3s auto-advance), info
overlay (i, GtkLabel in GtkOverlay, auto-hide 5s), Esc contextual back
(fullscreen→unfullscreen→large→grid→quit). Stack wrapped in GtkOverlay.
ggaze_window_get_stack() accessor for tests.
- src/shortcuts.c: f, S, i, Escape bindings.
- meson.build: libexif dep added.
- tests: unit test_info (EXIF extraction + orientation tag). 14/14 green,
ASan clean.
|
|
M7 (task jt0): thumbnail cache + grid view + trash.
- src/trash.{c,h}: ./Trash bin (lazy create, collision suffix -1/-2, restore_last
for u undo, permanent delete for D). Plain-C, unit-tested.
- src/thumbnail.{c,h}: freedesktop TMS cache (normal/large/x-large buckets,
md5(URI) key, Thumb::URI/MTime/Size, mtime verify). Async GTask worker with
GCancellable so the grid can cancel pending requests on dispose.
- src/gridview.{c,h}: GgazeGrid (GtkWidget wrapping GtkFlowBox): one cell per
navigator file, lazy thumbnail decode on realize, +/- resize 64-512, dim
removed cells, mark badges, Enter/double-click → 'activate' signal, cursor
sync. ggaze_grid_detach() breaks the nav ref before dispose.
- src/navigator.{c,h}: removed set (mark_removed/is_removed/unmark_removed);
_relist preserves removed items (stay dimmed even if absent from disk);
get_remaining = count - removed_count.
- src/window.c: d (trash+mark_removed+advance), D (permanent delete, confirm
dialog if >1 marked), u (restore_last+rescan), t (toggle grid/large),
+/- (viewer zoom in large / grid resize). Header counter uses remaining.
- src/shortcuts.c: d/D/u/t/+/= bindings added.
- tests: unit test_trash + test_thumbnail, integration test_grid_cull
(grid count + trash/undo + ./sample-images 613-cell grid). 13/13 green,
ASan clean.
Fixed: grid dispose UAF (detach before nav freed), g_file_get_basename leak,
test p_plain lifetime, thumbnail async-cancellable for clean dispose, test
grid-toggle avoiding async-thumbnail drain issues.
|
|
M3 (task it0): responsive + prefetch.
- src/loader/loader.{c,h}: loader_load_async/_finish wrap the sync
loader_load in a GTask worker; the task's source object is the GFile so the
finish callback can check it against navigator.current (last-write-wins).
- src/texturecache.{c,h}: bounded LRU (cap 4) of GFile->GdkTexture, hash+GQueue
O(1) get/put, entry owns the key (no double-unref), evicts LRU on overflow.
- src/window.c: async visible load (cancel-then-recreate single GCancellable);
cache hit -> show+prefetch, miss -> async load; last-write-wins guard
(g_file_equal(current, loaded)) before showing; prefetch next/prev into the
cache via a separate prefetch cancellable (cancelled each round). Finish
callbacks hold a ref on the window (released in the callback) so they never
deref a freed window; dispose cancels both cancellables.
- tests: unit test_texturecache (LRU evict/order/replace/miss), integration
test_responsive_nav (10 rapid next -> last-write-wins). M1/M2 tests updated
to pump the main loop (loads are now async) + drain_main before exit.
- meson: texturecache in libggae; GIO_USE_VFS=local in fixtures_env.
Fixed: texturecache double-unref (key ownership), async-callback UAF (window
ref in callback data + drain in tests), a test g_build_filename leak. Sub-agent
review issues addressed (dispose + cache-hit cancel in-flight loads).
|
|
M2 (task ht0): walk the directory.
- src/navigator.{c,h}: GObject (no GtkWidget) holding the folder listing —
image-MIME filter + extension fallback, hide-RAW-sidecars (decision #33),
sort name/time/size, cursor + prev/next/wrap, path-based marks (survive
re-sort, pruned on rescan/monitor, cleared on remove), nearest-fallback,
GFileMonitor debounced 250ms (decision #28), 'changed' signal. 92% gcov.
- src/shortcuts.{c,h}: GtkShortcutController (MANAGED) binding h/l/Left/Right/
g/G/o/q to win.* actions; one table all milestones add to.
- src/window.{c,h}: open file->parent+current / folder->first; h/l/g/G actions
-> nav -> load -> viewer; single GCancellable last-write-wins (g_file_equal
guard for M3 async); header title 'filename · n/total'; GtkDropTarget
(GDK_TYPE_FILE_LIST, many->first, decision Z); GtkFileDialog open.
- data/gschema: add hide-raw-sidecars key.
- tests: unit test_navigator (12 subtests), integration test_walk_folder
(temp fixtures walk + ./sample-images skip-if-absent). 8/8 green; ASan clean
(GIO_USE_VFS=local in fixtures_env).
Fixed during this milestone: a UAF (borrowed c_name used after unref), a
cancellable double-ref leak, time::modified UINT64 vs INT64, and several
test-harness leaks. Sub-agent review issues addressed (mark pruning on
rescan/monitor, remove-before-cursor test, recursive temp-dir cleanup,
callback _cb naming, connect-before-mutate).
|