1 /*
2 This file is part of darktable,
3 Copyright (C) 2009-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "develop/imageop_gui.h"
20 #include "develop/imageop.h"
21 #include "bauhaus/bauhaus.h"
22 #include "dtgtk/button.h"
23 #include "gui/color_picker_proxy.h"
24 #include "gui/accelerators.h"
25
26 #ifdef GDK_WINDOWING_QUARTZ
27 #include "osx/osx.h"
28 #endif
29
30 #include <assert.h>
31 #include <gmodule.h>
32 #include <math.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <strings.h>
36 #if defined(__SSE__)
37 #include <xmmintrin.h>
38 #endif
39 #include <time.h>
40
41 typedef struct dt_module_param_t
42 {
43 dt_iop_module_t *module;
44 void *param;
45 } dt_module_param_t;
46
process_changed_value(dt_iop_module_t * self,GtkWidget * widget,void * data)47 static inline void process_changed_value(dt_iop_module_t *self, GtkWidget *widget, void *data)
48 {
49 if(!self) self = DT_BAUHAUS_WIDGET(widget)->module;
50
51 if(self->gui_changed) self->gui_changed(self, widget, data);
52
53 dt_iop_color_picker_reset(self, TRUE);
54
55 dt_dev_add_history_item(darktable.develop, self, TRUE);
56 }
57
dt_iop_slider_float_callback(GtkWidget * slider,float * field)58 void dt_iop_slider_float_callback(GtkWidget *slider, float *field)
59 {
60 if(darktable.gui->reset) return;
61
62 float previous = *field;
63 *field = dt_bauhaus_slider_get(slider);
64
65 if (*field != previous) process_changed_value(NULL, slider, &previous);
66 }
67
dt_iop_slider_int_callback(GtkWidget * slider,int * field)68 void dt_iop_slider_int_callback(GtkWidget *slider, int *field)
69 {
70 if(darktable.gui->reset) return;
71
72 int previous = *field;
73 *field = dt_bauhaus_slider_get(slider);
74
75 if(*field != previous) process_changed_value(NULL, slider, &previous);
76 }
77
dt_iop_slider_ushort_callback(GtkWidget * slider,unsigned short * field)78 void dt_iop_slider_ushort_callback(GtkWidget *slider, unsigned short *field)
79 {
80 if(darktable.gui->reset) return;
81
82 unsigned short previous = *field;
83 *field = dt_bauhaus_slider_get(slider);
84
85 if(*field != previous) process_changed_value(NULL, slider, &previous);
86 }
87
dt_iop_combobox_enum_callback(GtkWidget * combobox,int * field)88 void dt_iop_combobox_enum_callback(GtkWidget *combobox, int *field)
89 {
90 if(darktable.gui->reset) return;
91
92 int previous = *field;
93
94 *field = GPOINTER_TO_INT(dt_bauhaus_combobox_get_data(combobox));
95
96 if(*field != previous) process_changed_value(NULL, combobox, &previous);
97 }
98
dt_iop_combobox_int_callback(GtkWidget * combobox,int * field)99 void dt_iop_combobox_int_callback(GtkWidget *combobox, int *field)
100 {
101 if(darktable.gui->reset) return;
102
103 int previous = *field;
104
105 *field = dt_bauhaus_combobox_get(combobox);
106
107 if(*field != previous) process_changed_value(NULL, combobox, &previous);
108 }
109
dt_iop_combobox_bool_callback(GtkWidget * combobox,gboolean * field)110 void dt_iop_combobox_bool_callback(GtkWidget *combobox, gboolean *field)
111 {
112 if(darktable.gui->reset) return;
113
114 gboolean previous = *field;
115 *field = dt_bauhaus_combobox_get(combobox);
116
117 if(*field != previous) process_changed_value(NULL, combobox, &previous);
118 }
119
_iop_toggle_callback(GtkWidget * togglebutton,dt_module_param_t * data)120 static void _iop_toggle_callback(GtkWidget *togglebutton, dt_module_param_t *data)
121 {
122 if(darktable.gui->reset) return;
123
124 dt_iop_module_t *self = data->module;
125 gboolean *field = (gboolean*)(data->param);
126
127 gboolean previous = *field;
128 *field = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(togglebutton));
129
130 if(*field != previous) process_changed_value(self, togglebutton, &previous);
131 }
132
dt_bauhaus_slider_from_params(dt_iop_module_t * self,const char * param)133 GtkWidget *dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
134 {
135 dt_iop_params_t *p = (dt_iop_params_t *)self->params;
136 dt_iop_params_t *d = (dt_iop_params_t *)self->default_params;
137
138 size_t param_index = 0;
139 gboolean skip_label = FALSE;
140
141 const size_t param_length = strlen(param) + 1;
142 char *param_name = g_malloc(param_length);
143 char *base_name = g_malloc(param_length);
144 if(sscanf(param, "%[^[][%zu]", base_name, ¶m_index) == 2)
145 {
146 sprintf(param_name, "%s[0]", base_name);
147 skip_label = TRUE;
148 }
149 else
150 {
151 memcpy(param_name, param, param_length);
152 }
153 g_free(base_name);
154
155 const dt_introspection_field_t *f = self->so->get_f(param_name);
156
157 GtkWidget *slider = NULL;
158 gchar *str;
159
160 if(f)
161 {
162 if(f->header.type == DT_INTROSPECTION_TYPE_FLOAT)
163 {
164 const float min = f->Float.Min;
165 const float max = f->Float.Max;
166 const size_t offset = f->header.offset + param_index * sizeof(float);
167 const float defval = *(float*)(d + offset);
168 int digits = 2;
169 float step = 0;
170
171 const float top = fminf(max-min, fmaxf(fabsf(min),fabsf(max)));
172 if (top>=100)
173 {
174 step = 1.f;
175 }
176 else
177 {
178 step = top / 100;
179 const float log10step = log10f(step);
180 const float fdigits = floorf(log10step+.1);
181 step = powf(10.f,fdigits);
182 if (log10step - fdigits > .5)
183 step *= 5;
184 if (fdigits < -2.f)
185 digits = -fdigits;
186 }
187
188 slider = dt_bauhaus_slider_new_with_range_and_feedback(self, min, max, step, defval, digits, 1);
189
190 const char *post = ""; // set " %%", " EV" etc
191
192 if (min < 0 || (post && *post))
193 {
194 str = g_strdup_printf("%%%s.0%df%s", (min < 0 ? "+" : ""), digits, post);
195
196 dt_bauhaus_slider_set_format(slider, str);
197
198 g_free(str);
199 }
200
201 g_signal_connect(G_OBJECT(slider), "value-changed",
202 G_CALLBACK(dt_iop_slider_float_callback),
203 p + offset);
204 }
205 else if(f->header.type == DT_INTROSPECTION_TYPE_INT)
206 {
207 const int min = f->Int.Min;
208 const int max = f->Int.Max;
209 const size_t offset = f->header.offset + param_index * sizeof(int);
210 const int defval = *(int*)(d + offset);
211
212 slider = dt_bauhaus_slider_new_with_range_and_feedback(self, min, max, 1, defval, 0, 1);
213
214 g_signal_connect(G_OBJECT(slider), "value-changed",
215 G_CALLBACK(dt_iop_slider_int_callback),
216 p + offset);
217 }
218 else if(f->header.type == DT_INTROSPECTION_TYPE_USHORT)
219 {
220 const unsigned short min = f->UShort.Min;
221 const unsigned short max = f->UShort.Max;
222 const size_t offset = f->header.offset + param_index * sizeof(unsigned short);
223 const unsigned short defval = *(unsigned short*)(d + offset);
224
225 slider = dt_bauhaus_slider_new_with_range_and_feedback(self, min, max, 1, defval, 0, 1);
226
227 g_signal_connect(G_OBJECT(slider), "value-changed",
228 G_CALLBACK(dt_iop_slider_ushort_callback),
229 p + offset);
230 }
231 else f = NULL;
232 }
233
234 if(f)
235 {
236 if(!skip_label)
237 {
238 if (*f->header.description)
239 {
240 // we do not want to support a context as it break all translations see #5498
241 // dt_bauhaus_widget_set_label(slider, NULL, g_dpgettext2(NULL, "introspection description", f->header.description));
242 dt_bauhaus_widget_set_label(slider, NULL, f->header.description);
243 }
244 else
245 {
246 str = dt_util_str_replace(f->header.field_name, "_", " ");
247
248 dt_bauhaus_widget_set_label(slider, NULL, str);
249
250 g_free(str);
251 }
252 }
253 }
254 else
255 {
256 str = g_strdup_printf("'%s' is not a float/int/unsigned short/slider parameter", param_name);
257
258 slider = dt_bauhaus_slider_new(self);
259 dt_bauhaus_widget_set_label(slider, NULL, str);
260
261 g_free(str);
262 }
263
264 if(!self->widget) self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
265 gtk_box_pack_start(GTK_BOX(self->widget), slider, FALSE, FALSE, 0);
266
267 g_free(param_name);
268
269 return slider;
270 }
271
dt_bauhaus_combobox_from_params(dt_iop_module_t * self,const char * param)272 GtkWidget *dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
273 {
274 dt_iop_params_t *p = (dt_iop_params_t *)self->params;
275 dt_introspection_field_t *f = self->so->get_f(param);
276
277 GtkWidget *combobox = dt_bauhaus_combobox_new(self);
278 gchar *str = NULL;
279
280 if (f && (f->header.type == DT_INTROSPECTION_TYPE_ENUM ||
281 f->header.type == DT_INTROSPECTION_TYPE_INT ||
282 f->header.type == DT_INTROSPECTION_TYPE_UINT ||
283 f->header.type == DT_INTROSPECTION_TYPE_BOOL ))
284 {
285 if (*f->header.description)
286 {
287 // we do not want to support a context as it break all translations see #5498
288 // dt_bauhaus_widget_set_label(combobox, NULL, g_dpgettext2(NULL, "introspection description", f->header.description));
289 dt_bauhaus_widget_set_label(combobox, NULL, f->header.description);
290 }
291 else
292 {
293 str = dt_util_str_replace(f->header.field_name, "_", " ");
294
295 dt_bauhaus_widget_set_label(combobox, NULL, str);
296
297 g_free(str);
298 }
299
300 if(f->header.type == DT_INTROSPECTION_TYPE_BOOL)
301 {
302 dt_bauhaus_combobox_add(combobox, _("no"));
303 dt_bauhaus_combobox_add(combobox, _("yes"));
304
305 g_signal_connect(G_OBJECT(combobox), "value-changed", G_CALLBACK(dt_iop_combobox_bool_callback), p + f->header.offset);
306 }
307 else
308 {
309 if(f->header.type == DT_INTROSPECTION_TYPE_ENUM)
310 {
311 for(dt_introspection_type_enum_tuple_t *iter = f->Enum.values; iter && iter->name; iter++)
312 {
313 // we do not want to support a context as it break all translations see #5498
314 // dt_bauhaus_combobox_add_full(combobox, g_dpgettext2(NULL, "introspection description", iter->description), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, GINT_TO_POINTER(iter->value), NULL, TRUE);
315 if(*iter->description)
316 dt_bauhaus_combobox_add_full(combobox, gettext(iter->description), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, GINT_TO_POINTER(iter->value), NULL, TRUE);
317 }
318
319 g_signal_connect(G_OBJECT(combobox), "value-changed", G_CALLBACK(dt_iop_combobox_enum_callback), p + f->header.offset);
320 }
321 else
322 {
323 g_signal_connect(G_OBJECT(combobox), "value-changed", G_CALLBACK(dt_iop_combobox_int_callback), p + f->header.offset);
324 }
325 }
326 }
327 else
328 {
329 str = g_strdup_printf("'%s' is not an enum/int/bool/combobox parameter", param);
330
331 dt_bauhaus_widget_set_label(combobox, NULL, str);
332
333 g_free(str);
334 }
335
336 if(!self->widget) self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
337 gtk_box_pack_start(GTK_BOX(self->widget), combobox, FALSE, FALSE, 0);
338
339 return combobox;
340 }
341
dt_bauhaus_toggle_from_params(dt_iop_module_t * self,const char * param)342 GtkWidget *dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
343 {
344 dt_iop_params_t *p = (dt_iop_params_t *)self->params;
345 dt_introspection_field_t *f = self->so->get_f(param);
346
347 GtkWidget *button, *label;
348 gchar *str;
349
350 if(f && f->header.type == DT_INTROSPECTION_TYPE_BOOL)
351 {
352 if (*f->header.description)
353 {
354 // we do not want to support a context as it break all translations see #5498
355 // button = gtk_check_button_new_with_label(g_dpgettext2(NULL, "introspection description", f->header.description));
356 label = gtk_label_new(gettext(f->header.description));
357 }
358 else
359 {
360 str = dt_util_str_replace(f->header.field_name, "_", " ");
361
362 label = gtk_label_new(_(str));
363
364 g_free(str);
365 }
366
367 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
368 button = gtk_check_button_new();
369 gtk_container_add(GTK_CONTAINER(button), label);
370 dt_module_param_t *module_param = (dt_module_param_t *)g_malloc(sizeof(dt_module_param_t));
371 module_param->module = self;
372 module_param->param = p + f->header.offset;
373 g_signal_connect_data(G_OBJECT(button), "toggled", G_CALLBACK(_iop_toggle_callback), module_param, (GClosureNotify)g_free, 0);
374 }
375 else
376 {
377 str = g_strdup_printf("'%s' is not a bool/togglebutton parameter", param);
378
379 button = gtk_check_button_new_with_label(str);
380
381 g_free(str);
382 }
383
384 if(!self->widget) self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
385 gtk_box_pack_start(GTK_BOX(self->widget), button, FALSE, FALSE, 0);
386
387 return button;
388 }
389
_send_button_press_event(GtkWidget * w,guint state)390 static void _send_button_press_event(GtkWidget *w, guint state)
391 {
392 if(!(GTK_IS_BUTTON(w))) return;
393
394 GdkEvent *event = gdk_event_new(GDK_BUTTON_PRESS);
395 event->button.state = state;
396 event->button.button = 1;
397 event->button.window = gtk_widget_get_window(w);
398 g_object_ref(event->button.window);
399
400 gtk_widget_event(w, event);
401
402 gdk_event_free(event);
403 }
404
_widget_visible(GtkWidget * w)405 static gboolean _widget_visible(GtkWidget *w)
406 {
407 GtkWidget *parent = gtk_widget_get_parent(w);
408 return gtk_widget_get_visible(w) &&
409 gtk_widget_get_visible(parent) &&
410 gtk_widget_get_visible(gtk_widget_get_parent(parent));
411 }
412
_press_button_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer widget)413 static gboolean _press_button_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
414 GdkModifierType modifier, gpointer widget)
415 {
416 if(_widget_visible(widget))
417 _send_button_press_event(widget, 0);
418 return TRUE;
419 }
420
_ctrl_press_button_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer widget)421 static gboolean _ctrl_press_button_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
422 GdkModifierType modifier, gpointer widget)
423 {
424 if(_widget_visible(widget))
425 _send_button_press_event(widget, GDK_CONTROL_MASK);
426 return TRUE;
427 }
428
dt_iop_togglebutton_new(dt_iop_module_t * self,const char * section,const gchar * label,const gchar * ctrl_label,GCallback callback,gboolean local,guint accel_key,GdkModifierType mods,DTGTKCairoPaintIconFunc paint,GtkWidget * box)429 GtkWidget *dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label,
430 GCallback callback, gboolean local, guint accel_key, GdkModifierType mods,
431 DTGTKCairoPaintIconFunc paint, GtkWidget *box)
432 {
433 GtkWidget *w = dtgtk_togglebutton_new(paint, CPF_STYLE_FLAT, NULL);
434 g_signal_connect(G_OBJECT(w), "button-press-event", callback, self);
435
436 if(!ctrl_label)
437 gtk_widget_set_tooltip_text(w, _(label));
438 else
439 {
440 gchar *tooltip = g_strdup_printf(_("%s\nctrl+click to %s"), _(label), _(ctrl_label));
441 gtk_widget_set_tooltip_text(w, tooltip);
442 g_free(tooltip);
443 }
444
445 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), FALSE);
446 if(GTK_IS_BOX(box)) gtk_box_pack_end(GTK_BOX(box), w, FALSE, FALSE, 0);
447
448 gchar *combined_label = section
449 ? g_strdup_printf("%s`%s", section, label)
450 : g_strdup(label);
451 gchar *combined_ctrl_label = ctrl_label && section
452 ? g_strdup_printf("%s`%s", section, ctrl_label)
453 : g_strdup(ctrl_label);
454
455 if(darktable.control->accel_initialising)
456 {
457 dt_accel_register_iop(self->so, local, combined_label, accel_key, mods);
458 if(ctrl_label) dt_accel_register_iop(self->so, local, combined_ctrl_label, 0, 0);
459 }
460 else
461 {
462 GClosure *closure = g_cclosure_new(G_CALLBACK(_press_button_callback), (gpointer)w, NULL);
463 dt_accel_connect_iop(self, combined_label, closure);
464 if(ctrl_label)
465 {
466 closure = g_cclosure_new(G_CALLBACK(_ctrl_press_button_callback), (gpointer)w, NULL);
467 dt_accel_connect_iop(self, combined_ctrl_label, closure);
468 }
469 }
470
471 g_free(combined_ctrl_label);
472 g_free(combined_label);
473
474 return w;
475 }
476
dt_iop_button_new(dt_iop_module_t * self,const gchar * label,GCallback callback,gboolean local,guint accel_key,GdkModifierType mods,DTGTKCairoPaintIconFunc paint,gint paintflags,GtkWidget * box)477 GtkWidget *dt_iop_button_new(dt_iop_module_t *self, const gchar *label,
478 GCallback callback, gboolean local, guint accel_key, GdkModifierType mods,
479 DTGTKCairoPaintIconFunc paint, gint paintflags, GtkWidget *box)
480 {
481 GtkWidget *button = NULL;
482
483 if(paint)
484 {
485 button = dtgtk_button_new(paint, CPF_STYLE_FLAT | paintflags, NULL);
486 gtk_widget_set_tooltip_text(button, _(label));
487 }
488 else
489 {
490 button = gtk_button_new_with_label(_(label));
491 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(button))), PANGO_ELLIPSIZE_END);
492 }
493
494 g_signal_connect(G_OBJECT(button), "clicked", callback, (gpointer)self);
495
496 if(darktable.control->accel_initialising)
497 {
498 dt_accel_register_iop(self->so, local, label, accel_key, mods);
499 }
500 else
501 {
502 dt_accel_connect_button_iop(self, label, button);
503 }
504
505 if(GTK_IS_BOX(box)) gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
506
507 return button;
508 }
509
510 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
511 // vim: shiftwidth=2 expandtab tabstop=2 cindent
512 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
513