Skip to content

Additional button to remove the star rating of an image - #21199

Closed
deekayhd wants to merge 753 commits into
darktable-org:masterfrom
deekayhd:switch_unrated
Closed

Additional button to remove the star rating of an image#21199
deekayhd wants to merge 753 commits into
darktable-org:masterfrom
deekayhd:switch_unrated

Conversation

@deekayhd

Copy link
Copy Markdown
Contributor

This is a proposal for issue #21127 .
Currently, the only way to remove the star rating of an image is either by using the shortcut 0 or by clicking the first star in a toggle fashion. When more than one star has been set for an image, you have to click the first star twice. This is not very intuitive.
With this PR two new dedicated buttons are introduced to remove the star rating of an image in the same fashion as setting the rating.
At the bottom of the window
image
and on the thumbnail overlay
image

closes #21127

@deekayhd deekayhd changed the title Addiional button to unrate an image Additional button to remove the star rating of an image May 31, 2026
@TurboGit

TurboGit commented Jul 4, 2026

Copy link
Copy Markdown
Member

But then should we keep the double-click on 1 star to remove the rating?

Note that this was done to avoid too much encumbered thumbs especially when using small ones.

@deekayhd

deekayhd commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

But then should we keep the double-click on 1 star to remove the rating?

I would keep the double-click, because some may be used to it and get confused.

Note that this was done to avoid too much encumbered thumbs especially when using small ones.

I do not think that this is a problem. When the thumb gets too small, the stars are not visible, anyway.
If you have serious doubts, we could just add the new button at the bottom of the lighttable and leave the thumbnails as they are (primarily as a display of the rating).

@deekayhd

deekayhd commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@TurboGit Do you consider this PR for 6.8? If I understand correctly, it needs some adjustments due to the changes in PR #21659. Of course, I will do the adjustments, if necessary.

@Arecsu

Arecsu commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@deekayhd hey hello! Just a heads up, many things are changing with all this gtk4-prep. If needed to adapt your code to it, don't highly trust the assessment at #21659 that quotes this PR. It serves as a guidance, but be sure to check how other similar interactions are being done at master and possibly some gtk4-prep open PRs.

My last #21745 PR (still not in master at the moment of writing this) simplified a couple of things, so it should become easier to understand as we go. Luckily the code you might need to change seems to be a couple of lines at worst, so there shouldn't be huge surprises!

There is also this guide which can become handy: https://docs.gtk.org/gtk4/migrating-3to4.html

If using an LLM / AI coding tool, here is some helpful tips that I use in my workflow:

  • make it use https://docs.gtk.org/gtk4/migrating-3to4.html by fetching it and read it
  • make it use gh-cli. If you don't have it in your system (github CLI), do it, incredibly handy!
  • with gh-cli it should automate the search and exploration of issues and PRs
  • make it read and understand any possible gtk4-prep open PRs at darktable-org/darktable repo, even your own PRs if needed
  • use your own (or helped with) senses to know if it would be a good idea to merge those PRs locally prior to do your own work
  • and also say to it that any commit titled gtk4-prep in the git history of the master branch should be helpful, along with other patterns in the current code, to know how to adapt your code to work with this preparation

I believe these guidelines are universal and would help plenty of other developers as well when it comes to merging new PRs in this phase. Of course, if you don't use any LLMs, well, you can always inspect the code yourself. It's the same process, but possibly a bit slower, although some people are pretty good at this!

CC to @TurboGit because this message could be useful for other people as well

@deekayhd

Copy link
Copy Markdown
Contributor Author

@TurboGit Now that 5.6.1 is out I would like to ask you again, if you consider this PR for the next release. I understand that soem adjustments are necessary due to the GTK4 migration, but I do not want to spend the effort if this change is not worth to be included in 5.8.
If you have concerns about the used space on the thumbnail, we could also only add the new button at the bottom of the lighttable (first screenshot above). The double-click to remove the rating should be kept.

@TurboGit

Copy link
Copy Markdown
Member

@deekayhd : Indeed I'm not 100% convinced by this extra control and not many people have commented.

Indeed I would add this into the bottom bar only for now. That would avoid another icon on the thumbs which are already crowded. A good compromise as I see it.

Arecsu and others added 21 commits August 30, 2026 14:28
- dt_shortcut_key_press() now flushes a still-armed delayed release
  before the hold takes over _sc: the pending pass requires
  c->key == s->key, so left armed it would resolve against the hold
  key and re-run the hold action, silently disengaging the hold while
  the key is still held.  A same-key re-engage inside the window is
  that pending pass's own double-press check and is only cancelled.
- dt_shortcut_key_release() cancels a still-armed delayed release
  before scheduling the hold cleanup, mirroring the ordinary key
  release, so g_timeout_add() cannot orphan a live source handle.
- _delay_for_double_triple()'s stuck-key early return drops the
  _hold_release_pending flag and the stale hold key, or the next
  ordinary key release would silently skip its own dispatch.
- _key_release_delayed() restores the identity of a still-engaged
  hold key when _pressed_keys is empty but _hold_keys is not,
  keeping move/scroll routing alive for the held key.
- the CAPTURE-phase gesture claims selectively (modifier presses,
  single-click mode, MONTH rule, folder/filmroll right-click) instead
  of unconditionally, so a plain click reaches the treeview's internal
  gesture again: single-click expander toggles, focus grab and cursor
  placement work as before the migration.
- a plain primary double-click on the expander arrow claims only the
  second press and skips the module's own toggle: the internal gesture
  toggles on the first release, giving exactly one toggle with no
  expand/collapse flicker, while longer click bursts keep responding.
- shift+ctrl+click on a folder/filmroll row activates again, making
  the folders<->filmrolls view switch reachable.
…close

GTK3 synthesizes a fake enter crossing (GDK_CROSSING_GTK_UNGRAB) on every
widget a grab was shadowing when the grab ends -- menus, popovers, modal
dialogs -- so the button's internal enter handler marks the pointer as
inside and the button keeps its hover state even after the pointer has
left, only clearing on the next genuine crossing.

Connect a grab-notify handler on dtgtk buttons that drops the stale
hover/pressed flags when the grab ends and the pointer is not over the
button.

The lib module reset button additionally ran its action from a custom
gesture's press, so the confirm dialog's grab split the button's press
from its release (the press was processed after the dialog closed, the
release was consumed by the dialog's grab), leaving the button stuck in
its pressed state; wiring the action to the button's own clicked signal
keeps the press/release cycle intact, and also avoids the cancel
forwarding in dt_gui_connect_click() firing the reset twice.
The shortcut machinery grabs the pointer on every key press
(gdk_seat_grab()); the synthetic crossings that the grab/ungrab dance
generates reach the thumbtable and culling leave handlers, which cleared
mouse_inside unconditionally -- the darktable-org#21734 hovered-image fix
(2f24f86) only guarded the mouse_over_id clear.  With mouse_inside
FALSE, _cache_update_hover() skips the "hovered image is inside the
selection" check (column 1) and acts on the hovered image only, so
rating, rejecting, moving and color labels hit just the hovered thumbnail
instead of the whole selection for every shortcut press until the pointer
moves.

Clear mouse_inside only on real leaves (same guard as mouse_over_id: not
an inferior/grab crossing, and not while the pointer is grabbed), and
restore it in the enter handler so the state self-heals.
The per device tracking of cl_mem allocation errors did not work as intended for long so it got removed.
This is due to "lazy cl_mem allocation" as almost all drivers/hardware do.

In short - the actual cl_mem is not allocated immediately but on first use by a kernel/cl function
so all alloc tests like `cl_mem == NULL` almost never returned NULL as an error condition.

This commit adds clEnqueueMigrateMemObjects() whenever cl_mem is allocated, this enforces
the cl_mem being prepared on the device so we have a fair chance errors are reported.

As there might be a perf penalty doing so we currently use a cli switch `--opencl-migrate` to enforce.

dt_opencl_memory_statistics() and dt_opencl_get_device_info() are internal so not exposed and made static.
Some improved logs
In some cases it's important to know the darktable version.
Without this, for example, downloading AI models will not work.
Enable scene refered editing of details in images.
The core principle of the module is to modifiy the
log difference between a luminance estimate and its
low pass version filtered using the exposure invariant
guided filter (eigf, same as in tone equalizer).
Its in this way easy to target different detail sizes.

Work based on initial idea by
Co-authored-by: bjoern <wilecoyote2015@gmail.com>
And further efforts by
Co-authored-by: Christian Bouhon <christian.bouhon@outlook.be>
Co-authored-by: Dan Zwell <devel@zwell.net>
shouldn't the color stuff (especially for graphs and colorlabels) move to gtk.h/.c and use dt_gui_gtk_set_source_rgb(a)? After those are renamed to dt_gui_set_color/_alpha.
…ll_should_pan()

Mouse wheels no longer pan: GTK4 can deliver wheel scrolls as smooth
events, so the old 'smooth without ctrl' check made the wheel pan in
the culling layouts at 100% zoom instead of navigating to the next or
previous image (issue darktable-org#21765, regression from f5d6acb).  A wheel
must always scroll/navigate; only a real touchpad swipe pans.

Add a shared helper, dt_gui_scroll_should_pan(), that a scroll event
must satisfy to be treated as a two-finger pan: the touchpad-gestures
preference, a smooth non-stop scroll, no ctrl modifier, and a touchpad
source device (or the device that just produced a pinch/swipe gesture,
as some touchpads report the follow-up scroll stream from a different
device).  On macOS/Quartz the built-in trackpad reports as a mouse, so
any non-ctrl smooth scroll pans there.

Use the helper in the main view _scrolled(), the culling layouts and
the darkroom second window, replacing the three duplicated smooth/ctrl
checks.  The second window had the same regression as the main view.
Zoom on up/left, out on down/right, everywhere: the darkroom, the
culling layouts and the lighttable zoomable/filemanager modes.

The direction is resolved from the GDK scroll direction rather than the
sign of the raw delta.  macOS delivers shift+wheel (which becomes
left/right scroll) with inverted horizontal delta signs for some
Bluetooth mice, but GDK still reports the correct LEFT/RIGHT direction,
so up/left==zoom-in, down/right==zoom-out works there too.  Previously
the culling and zoomable paths computed 'right==up==zoom-in' from the
delta sign, so ctrl+shift+wheel zoomed the wrong way (up zoomed out,
down zoomed in).

Smooth scrolls carry no direction, so their dominant delta is used with
the same sign convention (negative==up/left==zoom-in).  A shared
_scroll_zoom_delta() helper replaces the duplicated logic in the culling
layouts; the zoomable/filemanager path keeps its integer-step handling
but uses the corrected delta sign.
- bauhaus: gtk_get_current_event() may return NULL for synthetic
  motions; guard the button-press opentime read (was a NULL deref) and
  fall back to dt_key_modifier_state() in _widget_motion where the event
  is only used for its modifier state.  Free the event when present.
- culling: add dt_culling_destroy() to cancel a pending deferred-zoom
  finalize timer, disconnect the control-signal connections that use the
  table as user_data, and destroy+unref the widget (tearing down the
  event-controller callbacks).  Use it from the lighttable view cleanup
  so neither culling nor preview tables leak their timers and signals.
…ared helper

Unify the zoom direction across the culling layouts, the lighttable
filmstrip/zoomable modes, the darkroom main view and the second window,
and settle the horizontal axis consistently (issue darktable-org#20843).

With the shift modifier held, the OS turns a vertical wheel step into a
left/right scroll on every platform (macOS, Windows, X11), and the event
still carries the shift state.  Such a scroll is really a rotated wheel
step, so LEFT keeps meaning "wheel up": it zooms in, preserving the
wheel's up==in/down==out muscle memory.  Without shift (wheel tilt or a
two-finger swipe), RIGHT is the increase direction and zooms in, LEFT
zooms out -- the convention documented in darktable-org#20843.

Discrete events are resolved from the normalized GDK direction rather
than the raw delta sign, whose polarity is not canonical across
platforms; smooth (fractional) scrolls use their dominant delta with the
same convention.  A single dt_gui_scroll_zoom_delta() helper in
src/gui/gtk.c replaces the duplicated per-view logic (culling, darkroom
second window, main-view fallback) and the filmstrip/zoomable sign
derivation, which previously disagreed with the darkroom for horizontal
scrolls (and still disagreed with the wheel when shift was held).
…ion widget

The left-panel navigation widget zoomed with the same pre-helper pattern
(-delta_x dominant axis, no shift handling) that the culling, darkroom
and thumbtable paths had: without shift its behavior already matches
dt_gui_scroll_zoom_delta() (right/tilt zooms in), so this is a no-op for
those inputs, but shift+wheel (an OS-rotated wheel step delivered as a
left/right scroll) now zooms in on up like everywhere else instead of
zooming out.
victoryforce and others added 24 commits August 30, 2026 14:28
test_hdr_alignment_internal was converted to C++ in 330661f but still
includes win/main_wrapper.h. The wrapper relied on C pointer conversions and
the test used a main signature that conflicts with the wrapper declaration in
C++, preventing Windows builds with BUILD_TESTING enabled.

Use g_new0() for a typed argv allocation, explicitly treat Windows wchar_t
input as GLib UTF-16, and give the C++ test the standard argc/argv signature.

This lets the full Windows test build compile again.
Not that such images are expected, but let's handle such situations properly.
Before this change, when reading such a file, we would have garbage values ​​as
dimensions and would end up trying to allocate memory of potentially very large
size. So we risked triggering an OOM killer in such a, albeit unlikely, case.
The TIFF specification notes that the field is mandatory and applications
must not guess or default the value if the tag is missing; the file is
considered invalid or non-compliant if it is omitted.
Each tile's seam strip spans sensor_O rows and columns past the tile
edge, but the flush loops bounded only the horizontal strip's columns.
A tile row ending within sensor_O of the image bottom then wrote past
the end of cfa_out. Whether that happens depends on where the last tile
row falls, so it crashed on a 44MP 8152x5432 sensor and not on a 24MP
one, identically on every platform.

Clamp both flushes to the working area, matching the bound the
horizontal strip's columns already used.

Fixes darktable-org#22023
restore_raw_linear.c is a near-copy of the bayer path and shares its
seam-bounds bug: the accumulation bounds are not clamped to the image,
the vertical flush does not clamp its columns, and the horizontal flush
does not clamp its rows. The last two write past the end of rgb_out;
the middle one also wraps columns into the next row, overwriting output
that was already correct.

Same geometry as darktable-org#22023, but nothing reported it, so this is fixed by
inspection rather than from a reproducer.

Related: darktable-org#22023
The guard admitted any tile larger than 2 * O, but two adjacent tiles'
seam ramps only sum to 1 from 4 * O upward, so sizes in between seamed
the output and left both flushes' start bounds outside the image.

Raise it to 4 * O. Every start bound then sits at least 2 * O inside the
working area, so the four flush sites are safe by construction rather
than by clamping each one. Only a model declaring a tile size in 65..127
could reach this; the shipped raw denoise model uses 512.
…22042)

* opencl: make the bilinear resampling kernels reusable

The buffer bilinear resampler used to down- and upscale the working
buffers of a guided filter lived inside colorequal.cl, so any other
module needing it had to copy the three kernels and their channel-count
dispatch. Its CPU counterpart, interpolate_bilinear() in
common/fast_guided_filter.h, is already shared by several modules.

In preparation for implementing an OpenCL code path for tone equalizer,
those kernels are moved into their own program and exposed as
dt_interpolate_bilinear_cl(), following the dt_gaussian_*_cl() pattern:
a small global holding the kernels, initialised once in dt_opencl_init().
colorequal now calls that instead of carrying its own kernel handles.

basic.cl keeps its own interpolate_bilinear(): it works on image2d_t
rather than on buffers, so it is a different memory model rather than a
copy of this one.
…raster

dt_develop_blend_process_cl() skipped the device-to-host readback of the
final mask when this module's own mask came from a raster mask, on the
reasoning that such a mask is built on the host and uploaded, so the host
buffer already holds it.

That only holds while nothing touches it on the device afterwards. The
mask post-processing immediately above -- feathering, blur, tone curve --
reads and writes dev_mask. With any of those active the host buffer is
still the unrefined mask, and that is what gets published: a module that
consumes a raster mask, refines it, and is itself a raster source for a
later module hands every downstream consumer a different mask than it
blended with, and a different one than the CPU path publishes for the
same edit.

Found by replaying harvested mask edits on both pipes and comparing.
Bucketed by mask mode and by active post-processing, over 42,078 edits:
raster with any post-op diverges in 61 of 61 cases; raster with no
post-op in 0 of 590; drawn masks with the same feathering, blur and tone
curve in 0 of 6,747 -- which rules out the post-processing
implementations themselves and leaves the publication step. On a corpus
of every distinct raster configuration found, this takes the divergent
edits from 42 of 76 to 0, and the worst CPU-vs-OpenCL gap from 0.940 to
0.000153.

CPU renders are unchanged; OpenCL renders change to match them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@deekayhd
deekayhd requested a review from LebedevRI as a code owner August 30, 2026 12:30
@deekayhd

Copy link
Copy Markdown
Contributor Author

@TurboGit @LebedevRI I think I made a stupid mistake when I rebased this branch on master, and I do not understand why there are so many changed files, as I effectively only changed one.
I will close this and redo my changes in a new branch and PR.
Sorry if this may have caused confusion.

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.

UX: No way to mark a photo as not rated on the lightable