Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions src/gui/accelerators.c
Original file line number Diff line number Diff line change
Expand Up @@ -4372,6 +4372,27 @@ static gboolean _button_release_delayed(gpointer timed_out)
return G_SOURCE_REMOVE;
}

// returns TRUE if a double or triple press shortcut is registered for this key
static gboolean _shortcut_has_double_triple_press(const dt_input_device_t id,
const guint key,
const guint mods)
{
dt_shortcut_t s = { .key_device = id, .key = key, .mods = mods,
.press = DT_SHORTCUT_DOUBLE,
.views = dt_view_get_current() };

GSequenceIter *multi = _shortcut_search(&s, GINT_TO_POINTER(s.views));
for(int checks = 2; checks--; multi = g_sequence_iter_prev(multi))
{
if(g_sequence_iter_is_end(multi)) continue;

const dt_shortcut_t *m = g_sequence_get(multi);
if(m->key_device == id && m->key == key && m->press >= DT_SHORTCUT_DOUBLE)
return TRUE;
}
return FALSE;
}

void dt_shortcut_key_press(const dt_input_device_t id,
const guint time,
const guint key)
Expand Down Expand Up @@ -4428,16 +4449,37 @@ void dt_shortcut_key_press(const dt_input_device_t id,
_shortcut_description(s), _action_description(s, 2));
}

definition->process(NULL, s->element, DT_ACTION_EFFECT_ON, 1);
// a hold action must not swallow a fast consecutive press of the same
// key: if the key was held and released within the double-click time
// window and a double/triple press shortcut exists for it, fall
// through to the normal handling below which turns this press into a
// double/triple press instead of re-engaging the hold
if(key == _sc.key
&& _sc.key_device == id
&& !dt_gui_long_click(time, _last_time)
&& _shortcut_has_double_triple_press(id, key, _sc.mods))
{
/* fall through to the double/triple press detection below */
}
else
{
definition->process(NULL, s->element, DT_ACTION_EFFECT_ON, 1);

this_key.hold_def = definition;
this_key.hold_element = s->element;

this_key.hold_def = definition;
this_key.hold_element = s->element;
dt_device_key_t *new_key = calloc(1, sizeof(dt_device_key_t));
*new_key = this_key;
_hold_keys = g_slist_prepend(_hold_keys, new_key);

dt_device_key_t *new_key = calloc(1, sizeof(dt_device_key_t));
*new_key = this_key;
_hold_keys = g_slist_prepend(_hold_keys, new_key);
// remember the press so a fast consecutive press of this key
// (after release) can be detected as a double/triple press below
_last_time = time;
_sc.key_device = id;
_sc.key = key;

return;
return;
}
}
}

Expand Down
135 changes: 128 additions & 7 deletions src/gui/color_picker_proxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "libs/lib.h"
#include "control/control.h"
#include "gui/gtk.h"
#include "gui/accelerators.h"
#include "develop/blend.h"

/*
Expand Down Expand Up @@ -155,7 +156,8 @@ static void _init_picker(dt_iop_color_picker_t *picker,
}

static gboolean _color_picker_callback_button_press(GtkWidget *button,
GdkEventButton *e,
const gboolean ctrl,
const gboolean right,
dt_iop_color_picker_t *self)
{
// module is NULL if primary colorpicker
Expand All @@ -174,9 +176,7 @@ static gboolean _color_picker_callback_button_press(GtkWidget *button,
if(module && module->off)
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), TRUE);

const GdkModifierType state = e != NULL ? e->state : dt_key_modifier_state();
const gboolean to_area_mode =
dt_modifier_is(state, GDK_CONTROL_MASK) || (e != NULL && e->button == GDK_BUTTON_SECONDARY);
const gboolean to_area_mode = ctrl || right;
dt_iop_color_picker_flags_t flags = self->flags;

// setup if a new picker or switching between point/area mode
Expand Down Expand Up @@ -259,7 +259,10 @@ static gboolean _color_picker_callback_button_press(GtkWidget *button,
static void _color_picker_callback(GtkWidget *button,
dt_iop_color_picker_t *self)
{
_color_picker_callback_button_press(button, NULL, self);
_color_picker_callback_button_press(button,
dt_modifier_is(dt_key_modifier_state(), GDK_CONTROL_MASK),
FALSE,
self);
}

static void _color_picker_clicked(GtkGestureSingle *gesture,
Expand All @@ -269,9 +272,102 @@ static void _color_picker_clicked(GtkGestureSingle *gesture,
dt_iop_color_picker_t *self)
{
GtkWidget *button = dt_gui_get_widget(gesture);
_color_picker_callback_button_press(button, NULL, self);
// pass the clicked button through to the callback: a secondary click
// switches the picker to area mode (as before the gtk4-prep migration)
_color_picker_callback_button_press(button,
dt_modifier_is(dt_key_modifier_state(), GDK_CONTROL_MASK),
gtk_gesture_single_get_current_button(gesture)
== GDK_BUTTON_SECONDARY,
self);
}

/*
* Claim the event sequence in CAPTURE phase so the toggle button's own
* internal GtkGestureMultiPress (GTK_PHASE_BUBBLE, which emits "clicked"
* and toggles the button on release) never processes the event; the
* picker callback fully controls the button state instead. Same pattern
* as dt_iop_togglebutton_new.
*
* GTK4 migration: the pattern is the same, just rename
* GtkGestureMultiPress to GtkGestureClick. */
static void _gesture_begin_claim(GtkGesture *gesture,
GdkEventSequence *sequence,
gpointer user_data)
{
gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
}

/*
* Shared activation entry for standalone picker toggle buttons (created
* with a NULL container, e.g. AgX "auto tune levels"). Real clicks go
* through the CAPTURE-phase gesture above, shortcuts through the action
* definition below (dt_action_def_color_picker); both end up here,
* mirroring how bauhaus pickers route clicks and shortcuts through
* dt_bauhaus_widget_press_quad().
*/
static float _color_picker_widget_toggle(GtkWidget *target,
const dt_action_effect_t effect,
const float move_size)
{
if(!DT_PERFORM_ACTION(move_size) || !target)
return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(target));

dt_iop_color_picker_t *self = g_object_get_data(G_OBJECT(target), DT_COLOR_PICKER_INSTANCE_KEY);
if(!self) return 0.f;

if(!DT_ACTION_TOGGLE_NEEDED(effect, move_size, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(target))))
return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(target));

_color_picker_callback_button_press(target,
effect == DT_ACTION_EFFECT_TOGGLE_CTRL
|| effect == DT_ACTION_EFFECT_ON_CTRL,
effect == DT_ACTION_EFFECT_TOGGLE_RIGHT
|| effect == DT_ACTION_EFFECT_ON_RIGHT,
self);

const gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(target));
if(!gtk_widget_is_visible(target))
dt_action_widget_toast(NULL, target, active ? _("on") : _("off"));

return active;
}

/*
* Action definition for standalone picker toggle buttons.
*
* The generic toggle definition (dt_action_def_toggle in accelerators.c)
* activates widgets by synthesizing GObject "button-press-event" signals.
* Those only reach the widget class handler (which dispatches
* BUBBLE-phase controllers) and carry no device, so they can never
* trigger the CAPTURE-phase gesture that picker buttons need in order to
* suppress the button's own internal gesture -- which is why picker
* shortcuts stopped working after the gtk4-prep migration. Like bauhaus
* widgets, picker buttons therefore use their own definition whose
* process calls the same shared entry as real clicks.
*/
static const dt_action_element_def_t _color_picker_elements[]
= { { NULL, dt_action_effect_toggle } };

static const dt_shortcut_fallback_t _color_picker_fallbacks[]
= { { .mods = GDK_CONTROL_MASK , .effect = DT_ACTION_EFFECT_TOGGLE_CTRL },
{ .button = DT_SHORTCUT_RIGHT , .effect = DT_ACTION_EFFECT_TOGGLE_RIGHT },
{ .press = DT_SHORTCUT_LONG , .effect = DT_ACTION_EFFECT_TOGGLE_RIGHT },
{ } };

static float _color_picker_process(gpointer target,
const dt_action_element_t element,
const dt_action_effect_t effect,
const float move_size)
{
return _color_picker_widget_toggle(target, effect, move_size);
}

const dt_action_def_t dt_action_def_color_picker
= { N_("toggle"),
_color_picker_process,
_color_picker_elements,
_color_picker_fallbacks };

void dt_iop_color_picker_set_cst(dt_iop_module_t *module,
const dt_iop_colorspace_type_t picker_cst)
{
Expand Down Expand Up @@ -390,6 +486,10 @@ static void _color_picker_destroy(dt_iop_color_picker_t *picker)
// before freeing the struct to prevent use-after-free in dt_iop_color_picker_reset.
if(darktable.lib && darktable.lib->proxy.colorpicker.picker_proxy == picker)
darktable.lib->proxy.colorpicker.picker_proxy = NULL;
if(picker->colorpick)
{
g_object_set_data(G_OBJECT(picker->colorpick), DT_COLOR_PICKER_INSTANCE_KEY, NULL);
}
g_free(picker);
}

Expand All @@ -411,7 +511,28 @@ static GtkWidget *_color_picker_new(dt_iop_module_t *module,
color_picker->picker_cst = cst;
color_picker->fixed_cst = TRUE;
}
dt_gui_connect_click(button, _color_picker_clicked, NULL, color_picker);
// The button is a GtkToggleButton, which owns its own
// GtkGestureMultiPress (bubble phase) that emits "clicked" and toggles
// the button on release. Use a CAPTURE-phase gesture that claims the
// event sequence (same pattern as dt_iop_togglebutton_new) so that
// internal gesture never runs and the picker callback fully controls
// the button state on real clicks.
//
// Shortcuts (dt_action_def_color_picker, see above) call
// _color_picker_widget_toggle() directly instead of synthesizing
// GObject "button-press-event" signals, which cannot reach this
// CAPTURE-phase gesture (the widget class handler only dispatches
// BUBBLE-phase controllers and synthetic events carry no device).
GtkGesture *gesture = gtk_gesture_multi_press_new(button);
gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(gesture),
GTK_PHASE_CAPTURE);
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 0);
dt_gui_add_controller(button, gesture);
g_signal_connect_data(gesture, "pressed",
G_CALLBACK(_color_picker_clicked),
color_picker, (GClosureNotify)_color_picker_destroy, 0);
g_signal_connect(gesture, "begin", G_CALLBACK(_gesture_begin_claim), NULL);
g_object_set_data(G_OBJECT(button), DT_COLOR_PICKER_INSTANCE_KEY, color_picker);
if(w) gtk_box_pack_start(GTK_BOX(w), button, FALSE, FALSE, 0);

return button;
Expand Down
6 changes: 6 additions & 0 deletions src/gui/color_picker_proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ typedef struct dt_iop_color_picker_t

gboolean dt_iop_color_picker_is_visible(const dt_develop_t *dev);

/* g_object data key on the picker widget: the owning dt_iop_color_picker_t */
#define DT_COLOR_PICKER_INSTANCE_KEY "dt-color-picker-instance"

/* action definition for standalone picker toggle buttons (see color_picker_proxy.c) */
extern const struct dt_action_def_t dt_action_def_color_picker;

//* reset current color picker if not keep-active or not keep */
void dt_iop_color_picker_reset(dt_iop_module_t *module,
const gboolean keep);
Expand Down
2 changes: 1 addition & 1 deletion src/iop/agx.c
Original file line number Diff line number Diff line change
Expand Up @@ -2071,7 +2071,7 @@ static void _add_exposure_box(dt_iop_module_t *self, dt_iop_agx_gui_data_t *g, d
GtkWidget *auto_tune_label = dt_ui_label_new(_("auto tune levels"));
g->range_exposure_picker = dt_color_picker_new(self, DT_COLOR_PICKER_AREA | DT_COLOR_PICKER_DENOISE, NULL);
gtk_widget_set_tooltip_text(g->range_exposure_picker, _("set black and white relative exposure using the selected area"));
dt_action_define_iop(real_self, N_("exposure range"), N_("auto tune levels"), g->range_exposure_picker, &dt_action_def_toggle);
dt_action_define_iop(real_self, N_("exposure range"), N_("auto tune levels"), g->range_exposure_picker, &dt_action_def_color_picker);
dt_gui_box_add(auto_tune_box, dt_gui_expand(auto_tune_label), g->range_exposure_picker);
dt_gui_box_add(g->range_exposure_picker_group, auto_tune_box);

Expand Down
4 changes: 2 additions & 2 deletions src/iop/borders.c
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,7 @@ void gui_init(dt_iop_module_t *self)
gtk_widget_set_tooltip_text(GTK_WIDGET(g->border_picker),
_("pick border color from image"));
dt_action_define_iop(self, N_("pickers"), N_("border color"),
g->border_picker, &dt_action_def_toggle);
g->border_picker, &dt_action_def_color_picker);
dt_gui_box_add(self->widget, dt_gui_hbox(dt_gui_expand(label), g->colorpick, g->border_picker));

label = dtgtk_reset_label_new(_("frame line color"), self, &p->frame_color, 3 * sizeof(float));
Expand All @@ -1031,7 +1031,7 @@ void gui_init(dt_iop_module_t *self)
gtk_widget_set_tooltip_text(GTK_WIDGET(g->frame_picker),
_("pick frame line color from image"));
dt_action_define_iop(self, N_("pickers"), N_("frame line color"),
g->frame_picker, &dt_action_def_toggle);
g->frame_picker, &dt_action_def_color_picker);
dt_gui_box_add(self->widget, dt_gui_hbox(dt_gui_expand(label), g->frame_colorpick, g->frame_picker));
}

Expand Down
2 changes: 1 addition & 1 deletion src/iop/channelmixerrgb.c
Original file line number Diff line number Diff line change
Expand Up @@ -4482,7 +4482,7 @@ void gui_init(dt_iop_module_t *self)
G_CALLBACK(_illuminant_color_draw), self);

g->color_picker = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, NULL);
dt_action_define_iop(self, NULL, N_("picker"), g->color_picker, &dt_action_def_toggle);
dt_action_define_iop(self, NULL, N_("picker"), g->color_picker, &dt_action_def_color_picker);
gtk_widget_set_tooltip_text(g->color_picker,
_("set white balance to detected from area"));

Expand Down
6 changes: 3 additions & 3 deletions src/iop/negadoctor.c
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,7 @@ void gui_init(dt_iop_module_t *self)

g->Dmin_sampler = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, NULL);
gtk_widget_set_tooltip_text(g->Dmin_sampler , _("pick color of film material from image"));
dt_action_define_iop(self, N_("pickers"), N_("film material"), g->Dmin_sampler, &dt_action_def_toggle);
dt_action_define_iop(self, N_("pickers"), N_("film material"), g->Dmin_sampler, &dt_action_def_color_picker);

dt_gui_box_add(page1, dt_ui_section_label_new(C_("section", "color of the film base")),
dt_gui_hbox(dt_gui_expand(g->Dmin_picker), g->Dmin_sampler));
Expand Down Expand Up @@ -909,7 +909,7 @@ void gui_init(dt_iop_module_t *self)

g->WB_low_sampler = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, NULL);
gtk_widget_set_tooltip_text(g->WB_low_sampler, _("pick shadows color from image"));
dt_action_define_iop(self, N_("pickers"), N_("shadows"), g->WB_low_sampler, &dt_action_def_toggle);
dt_action_define_iop(self, N_("pickers"), N_("shadows"), g->WB_low_sampler, &dt_action_def_color_picker);

dt_gui_box_add(page2, dt_ui_section_label_new(C_("section", "shadows color cast")),
dt_gui_hbox(dt_gui_expand(g->WB_low_picker), g->WB_low_sampler));
Expand Down Expand Up @@ -943,7 +943,7 @@ void gui_init(dt_iop_module_t *self)

g->WB_high_sampler = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, NULL);
gtk_widget_set_tooltip_text(g->WB_high_sampler , _("pick illuminant color from image"));
dt_action_define_iop(self, N_("pickers"), N_("illuminant"), g->WB_high_sampler, &dt_action_def_toggle);
dt_action_define_iop(self, N_("pickers"), N_("illuminant"), g->WB_high_sampler, &dt_action_def_color_picker);

dt_gui_box_add(page2, dt_ui_section_label_new(C_("section", "highlights white balance")),
dt_gui_hbox(dt_gui_expand(g->WB_high_picker), g->WB_high_sampler));
Expand Down
2 changes: 1 addition & 1 deletion src/iop/rgblevels.c
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ void gui_init(dt_iop_module_t *self)
#define PICKER_SETUP(color, name, tooltip) \
g->color##pick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL); \
dt_action_define_iop(self, N_("pickers"), name, g->color##pick, \
&dt_action_def_toggle); \
&dt_action_def_color_picker); \
gtk_widget_set_tooltip_text(g->color##pick, tooltip); \
gtk_widget_set_name(GTK_WIDGET(g->color##pick), "picker-"#color); \
g_signal_connect(dt_gui_expand(g->color##pick), "toggled", \
Expand Down
2 changes: 1 addition & 1 deletion src/iop/tonecurve.c
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,7 @@ void gui_init(dt_iop_module_t *self)
gtk_widget_set_tooltip_text
(g->colorpicker,
_("pick GUI color from image\nctrl+click or right-click to select an area"));
dt_action_define_iop(self, NULL, N_("pick color"), g->colorpicker, &dt_action_def_toggle);
dt_action_define_iop(self, NULL, N_("pick color"), g->colorpicker, &dt_action_def_color_picker);

dt_gui_box_add(self->widget, dt_gui_hbox(dt_gui_expand(g->channel_tabs),
dt_gui_align_right(g->colorpicker)));
Expand Down
2 changes: 1 addition & 1 deletion src/iop/watermark.c
Original file line number Diff line number Diff line change
Expand Up @@ -1461,7 +1461,7 @@ void gui_init(dt_iop_module_t *self)
gtk_widget_set_tooltip_text(GTK_WIDGET(g->color_picker_button),
_("pick color from image"));
dt_action_define_iop(self, NULL, N_("pick color"),
g->color_picker_button, &dt_action_def_toggle);
g->color_picker_button, &dt_action_def_color_picker);

gtk_grid_attach(grid, label, 0, line++, 1, 1);
gtk_grid_attach_next_to(grid, g->colorpick, label, GTK_POS_RIGHT, 1, 1);
Expand Down
2 changes: 1 addition & 1 deletion src/libs/colorpicker.c
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ void gui_init(dt_lib_module_t *self)
g_signal_connect(G_OBJECT(data->picker_button), "toggled",
G_CALLBACK(_picker_button_toggled), data);
dt_action_define(DT_ACTION(self), NULL, N_("pick color"),
data->picker_button, &dt_action_def_toggle);
data->picker_button, &dt_action_def_color_picker);

// The small sample, label and add button
GtkWidget *sample_row_events = gtk_event_box_new();
Expand Down
Loading