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, &param_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