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_iop_module_t *)(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 dt_action_t *action = dt_action_locate(&self->so->actions, (gchar **)(const gchar *[]){ *f->header.description ? f->header.description : f->header.field_name, NULL}, FALSE);
320 if(action && f->Enum.values)
321 g_hash_table_insert(darktable.control->combo_introspection, action, f->Enum.values);
322
323 g_signal_connect(G_OBJECT(combobox), "value-changed", G_CALLBACK(dt_iop_combobox_enum_callback), p + f->header.offset);
324 }
325 else
326 {
327 g_signal_connect(G_OBJECT(combobox), "value-changed", G_CALLBACK(dt_iop_combobox_int_callback), p + f->header.offset);
328 }
329 }
330 }
331 else
332 {
333 str = g_strdup_printf("'%s' is not an enum/int/bool/combobox parameter", param);
334
335 dt_bauhaus_widget_set_label(combobox, NULL, str);
336
337 g_free(str);
338 }
339
340 if(!self->widget) self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
341 gtk_box_pack_start(GTK_BOX(self->widget), combobox, FALSE, FALSE, 0);
342
343 return combobox;
344 }
345
dt_bauhaus_toggle_from_params(dt_iop_module_t * self,const char * param)346 GtkWidget *dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
347 {
348 dt_iop_params_t *p = (dt_iop_params_t *)self->params;
349 dt_introspection_field_t *f = self->so->get_f(param);
350
351 GtkWidget *button, *label;
352 gchar *str;
353
354 if(f && f->header.type == DT_INTROSPECTION_TYPE_BOOL)
355 {
356 // we do not want to support a context as it break all translations see #5498
357 // button = gtk_check_button_new_with_label(g_dpgettext2(NULL, "introspection description", f->header.description));
358 label = gtk_label_new(gettext(f->header.description));
359 str = *f->header.description
360 ? g_strdup(f->header.description)
361 : dt_util_str_replace(f->header.field_name, "_", " ");
362
363 label = gtk_label_new(_(str));
364 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
365 button = gtk_check_button_new();
366 gtk_container_add(GTK_CONTAINER(button), label);
367 dt_module_param_t *module_param = (dt_module_param_t *)g_malloc(sizeof(dt_module_param_t));
368 module_param->module = self;
369 module_param->param = p + f->header.offset;
370 g_signal_connect_data(G_OBJECT(button), "toggled", G_CALLBACK(_iop_toggle_callback), module_param, (GClosureNotify)g_free, 0);
371
372 dt_action_define_iop(self, NULL, str, button, &dt_action_def_toggle);
373 }
374 else
375 {
376 str = g_strdup_printf("'%s' is not a bool/togglebutton parameter", param);
377
378 button = gtk_check_button_new_with_label(str);
379 }
380
381 g_free(str);
382 if(!self->widget) self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
383 gtk_box_pack_start(GTK_BOX(self->widget), button, FALSE, FALSE, 0);
384
385 return button;
386 }
387
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)388 GtkWidget *dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label,
389 GCallback callback, gboolean local, guint accel_key, GdkModifierType mods,
390 DTGTKCairoPaintIconFunc paint, GtkWidget *box)
391 {
392 GtkWidget *w = dtgtk_togglebutton_new(paint, CPF_STYLE_FLAT, NULL);
393 g_signal_connect(G_OBJECT(w), "button-press-event", callback, self);
394
395 if(!ctrl_label)
396 gtk_widget_set_tooltip_text(w, _(label));
397 else
398 {
399 gchar *tooltip = g_strdup_printf(_("%s\nctrl+click to %s"), _(label), _(ctrl_label));
400 gtk_widget_set_tooltip_text(w, tooltip);
401 g_free(tooltip);
402 }
403
404 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), FALSE);
405 if(GTK_IS_BOX(box)) gtk_box_pack_end(GTK_BOX(box), w, FALSE, FALSE, 0);
406
407 dt_action_define_iop(self, section, label, w, &dt_action_def_toggle);
408
409 return w;
410 }
411
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)412 GtkWidget *dt_iop_button_new(dt_iop_module_t *self, const gchar *label,
413 GCallback callback, gboolean local, guint accel_key, GdkModifierType mods,
414 DTGTKCairoPaintIconFunc paint, gint paintflags, GtkWidget *box)
415 {
416 GtkWidget *button = NULL;
417
418 if(paint)
419 {
420 button = dtgtk_button_new(paint, CPF_STYLE_FLAT | paintflags, NULL);
421 gtk_widget_set_tooltip_text(button, _(label));
422 }
423 else
424 {
425 button = gtk_button_new_with_label(_(label));
426 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(button))), PANGO_ELLIPSIZE_END);
427 }
428
429 g_signal_connect(G_OBJECT(button), "clicked", callback, (gpointer)self);
430
431 if(darktable.control->accel_initialising)
432 {
433 dt_accel_register_iop(self->so, local, label, accel_key, mods);
434 }
435 else
436 {
437 dt_accel_connect_button_iop(self, label, button);
438 }
439
440 if(GTK_IS_BOX(box)) gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
441
442 return button;
443 }
444
dt_mask_scroll_increases(int up)445 gboolean dt_mask_scroll_increases(int up)
446 {
447 const gboolean mask_down = dt_conf_get_bool("masks_scroll_down_increases");
448 return up ? !mask_down : mask_down;
449 }
450
451 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
452 // vim: shiftwidth=2 expandtab tabstop=2 cindent
453 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
454