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.h"
20 #include "bauhaus/bauhaus.h"
21 #include "common/debug.h"
22 #include "common/exif.h"
23 #include "common/collection.h"
24 #include "common/dtpthread.h"
25 #include "common/imagebuf.h"
26 #include "common/imageio_rawspeed.h"
27 #include "common/interpolation.h"
28 #include "common/iop_group.h"
29 #include "common/module.h"
30 #include "common/history.h"
31 #include "common/opencl.h"
32 #include "common/usermanual_url.h"
33 #include "control/control.h"
34 #include "develop/blend.h"
35 #include "develop/develop.h"
36 #include "develop/format.h"
37 #include "develop/masks.h"
38 #include "develop/tiling.h"
39 #include "dtgtk/button.h"
40 #include "dtgtk/expander.h"
41 #include "dtgtk/gradientslider.h"
42 #include "dtgtk/icon.h"
43 #include "gui/accelerators.h"
44 #include "gui/gtk.h"
45 #include "gui/presets.h"
46 #include "gui/color_picker_proxy.h"
47 #include "libs/modulegroups.h"
48 #ifdef GDK_WINDOWING_QUARTZ
49 #include "osx/osx.h"
50 #endif
51 
52 #include <assert.h>
53 #include <gmodule.h>
54 #include <math.h>
55 #include <complex.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <strings.h>
59 #if defined(__SSE__)
60 #include <xmmintrin.h>
61 #endif
62 #include <time.h>
63 
64 typedef struct dt_iop_gui_simple_callback_t
65 {
66   dt_iop_module_t *self;
67   int index;
68 } dt_iop_gui_simple_callback_t;
69 
70 static void _iop_panel_label(GtkWidget *lab, dt_iop_module_t *module);
71 
dt_iop_load_default_params(dt_iop_module_t * module)72 void dt_iop_load_default_params(dt_iop_module_t *module)
73 {
74   memcpy(module->params, module->default_params, module->params_size);
75   dt_develop_blend_colorspace_t cst = dt_develop_blend_default_module_blend_colorspace(module);
76   dt_develop_blend_init_blend_parameters(module->default_blendop_params, cst);
77   dt_iop_commit_blend_params(module, module->default_blendop_params);
78   dt_iop_gui_blending_reload_defaults(module);
79 }
80 
dt_iop_modify_roi_in(struct dt_iop_module_t * self,struct dt_dev_pixelpipe_iop_t * piece,const dt_iop_roi_t * roi_out,dt_iop_roi_t * roi_in)81 static void dt_iop_modify_roi_in(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece,
82                                  const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
83 {
84   *roi_in = *roi_out;
85 }
86 
dt_iop_modify_roi_out(struct dt_iop_module_t * self,struct dt_dev_pixelpipe_iop_t * piece,dt_iop_roi_t * roi_out,const dt_iop_roi_t * roi_in)87 static void dt_iop_modify_roi_out(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece,
88                                   dt_iop_roi_t *roi_out, const dt_iop_roi_t *roi_in)
89 {
90   *roi_out = *roi_in;
91 }
92 
93 /* default group for modules which do not implement the default_group() function */
default_default_group(void)94 static int default_default_group(void)
95 {
96   return IOP_GROUP_BASIC;
97 }
98 
99 /* default flags for modules which does not implement the flags() function */
default_flags(void)100 static int default_flags(void)
101 {
102   return 0;
103 }
104 
105 /* default operation tags for modules which does not implement the flags() function */
default_operation_tags(void)106 static int default_operation_tags(void)
107 {
108   return 0;
109 }
110 
111 /* default operation tags filter for modules which does not implement the flags() function */
default_operation_tags_filter(void)112 static int default_operation_tags_filter(void)
113 {
114   return 0;
115 }
116 
default_description(struct dt_iop_module_t * self)117 static const char *default_description(struct dt_iop_module_t *self)
118 {
119   return g_strdup("");
120 }
121 
default_aliases(void)122 static const char *default_aliases(void)
123 {
124   return "";
125 }
126 
default_deprecated_msg(void)127 static const char *default_deprecated_msg(void)
128 {
129   return NULL;
130 }
131 
default_commit_params(struct dt_iop_module_t * self,dt_iop_params_t * params,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)132 static void default_commit_params(struct dt_iop_module_t *self, dt_iop_params_t *params,
133                                   dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
134 {
135   memcpy(piece->data, params, self->params_size);
136 }
137 
default_init_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)138 static void default_init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe,
139                               dt_dev_pixelpipe_iop_t *piece)
140 {
141   piece->data = calloc(1,self->params_size);
142 }
143 
default_cleanup_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)144 static void default_cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe,
145                                  dt_dev_pixelpipe_iop_t *piece)
146 {
147   free(piece->data);
148 }
149 
default_gui_cleanup(dt_iop_module_t * self)150 static void default_gui_cleanup(dt_iop_module_t *self)
151 {
152   IOP_GUI_FREE;
153 }
154 
default_cleanup(dt_iop_module_t * module)155 static void default_cleanup(dt_iop_module_t *module)
156 {
157   g_free(module->params);
158   module->params = NULL;
159   free(module->default_params);
160   module->default_params = NULL;
161 }
162 
163 
default_distort_transform(dt_iop_module_t * self,dt_dev_pixelpipe_iop_t * piece,float * points,size_t points_count)164 static int default_distort_transform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *points,
165                                      size_t points_count)
166 {
167   return 1;
168 }
default_distort_backtransform(dt_iop_module_t * self,dt_dev_pixelpipe_iop_t * piece,float * points,size_t points_count)169 static int default_distort_backtransform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *points,
170                                          size_t points_count)
171 {
172   return 1;
173 }
174 
default_process(struct dt_iop_module_t * self,struct dt_dev_pixelpipe_iop_t * piece,const void * const i,void * const o,const struct dt_iop_roi_t * const roi_in,const struct dt_iop_roi_t * const roi_out)175 static void default_process(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece,
176                             const void *const i, void *const o, const struct dt_iop_roi_t *const roi_in,
177                             const struct dt_iop_roi_t *const roi_out)
178 {
179   if(roi_in->width <= 1 || roi_in->height <= 1 || roi_out->width <= 1 || roi_out->height <= 1) return;
180 
181   if(darktable.codepath.OPENMP_SIMD && self->process_plain)
182     self->process_plain(self, piece, i, o, roi_in, roi_out);
183 #if defined(__SSE__)
184   else if(darktable.codepath.SSE2 && self->process_sse2)
185     self->process_sse2(self, piece, i, o, roi_in, roi_out);
186 #endif
187   else if(self->process_plain)
188     self->process_plain(self, piece, i, o, roi_in, roi_out);
189   else
190     dt_unreachable_codepath_with_desc(self->op);
191 }
192 
default_get_introspection_linear(void)193 static dt_introspection_field_t *default_get_introspection_linear(void)
194 {
195   return NULL;
196 }
default_get_introspection(void)197 static dt_introspection_t *default_get_introspection(void)
198 {
199   return NULL;
200 }
default_get_p(const void * param,const char * name)201 static void *default_get_p(const void *param, const char *name)
202 {
203   return NULL;
204 }
default_get_f(const char * name)205 static dt_introspection_field_t *default_get_f(const char *name)
206 {
207   return NULL;
208 }
209 
dt_iop_default_init(dt_iop_module_t * module)210 void dt_iop_default_init(dt_iop_module_t *module)
211 {
212   size_t param_size = module->so->get_introspection()->size;
213   module->params_size = param_size;
214   module->params = (dt_iop_params_t *)calloc(1, param_size);
215   module->default_params = (dt_iop_params_t *)calloc(1, param_size);
216 
217   module->default_enabled = 0;
218   module->has_trouble = FALSE;
219   module->gui_data = NULL;
220 
221   dt_introspection_field_t *i = module->so->get_introspection_linear();
222   while(i->header.type != DT_INTROSPECTION_TYPE_NONE)
223   {
224     switch(i->header.type)
225     {
226     case DT_INTROSPECTION_TYPE_FLOAT:
227       *(float*)(module->default_params + i->header.offset) = i->Float.Default;
228       break;
229     case DT_INTROSPECTION_TYPE_INT:
230       *(int*)(module->default_params + i->header.offset) = i->Int.Default;
231       break;
232     case DT_INTROSPECTION_TYPE_UINT:
233       *(unsigned int*)(module->default_params + i->header.offset) = i->UInt.Default;
234       break;
235     case DT_INTROSPECTION_TYPE_USHORT:
236       *(unsigned short*)(module->default_params + i->header.offset) = i->UShort.Default;
237       break;
238     case DT_INTROSPECTION_TYPE_ENUM:
239       *(int*)(module->default_params + i->header.offset) = i->Enum.Default;
240       break;
241     case DT_INTROSPECTION_TYPE_BOOL:
242       *(gboolean*)(module->default_params + i->header.offset) = i->Bool.Default;
243       break;
244     case DT_INTROSPECTION_TYPE_CHAR:
245       *(char*)(module->default_params + i->header.offset) = i->Char.Default;
246       break;
247     case DT_INTROSPECTION_TYPE_OPAQUE:
248       memset(module->default_params + i->header.offset, 0, i->header.size);
249       break;
250     case DT_INTROSPECTION_TYPE_ARRAY:
251       {
252         if(i->Array.type == DT_INTROSPECTION_TYPE_CHAR) break;
253 
254         size_t element_size = i->Array.field->header.size;
255         if(element_size % sizeof(int))
256         {
257           int8_t *p = module->default_params + i->header.offset;
258           for (size_t c = element_size; c < i->header.size; c++, p++)
259             p[element_size] = *p;
260         }
261         else
262         {
263           element_size /= sizeof(int);
264           size_t num_ints = i->header.size / sizeof(int);
265 
266           int *p = module->default_params + i->header.offset;
267           for (size_t c = element_size; c < num_ints; c++, p++)
268             p[element_size] = *p;
269         }
270       }
271       break;
272     case DT_INTROSPECTION_TYPE_STRUCT:
273       // ignore STRUCT; nothing to do
274       break;
275     default:
276       fprintf(stderr, "unsupported introspection type \"%s\" encountered in dt_iop_default_init (field %s)\n", i->header.type_name, i->header.field_name);
277       break;
278     }
279 
280     i++;
281   }
282 }
283 
dt_iop_load_module_so(void * m,const char * libname,const char * module_name)284 int dt_iop_load_module_so(void *m, const char *libname, const char *module_name)
285 {
286   dt_iop_module_so_t *module = (dt_iop_module_so_t *)m;
287   g_strlcpy(module->op, module_name, sizeof(module->op));
288 
289 #define INCLUDE_API_FROM_MODULE_LOAD "iop_load_module"
290 #include "iop/iop_api.h"
291 
292   if(!module->init) module->init = dt_iop_default_init;
293   if(!module->modify_roi_in) module->modify_roi_in = dt_iop_modify_roi_in;
294   if(!module->modify_roi_out) module->modify_roi_out = dt_iop_modify_roi_out;
295 
296   #ifdef HAVE_OPENCL
297   if(!module->process_tiling_cl) module->process_tiling_cl = darktable.opencl->inited ? default_process_tiling_cl : NULL;
298   if(!darktable.opencl->inited) module->process_cl = NULL;
299   #endif // HAVE_OPENCL
300 
301   module->process_plain = module->process;
302   module->process = default_process;
303 
304   module->data = NULL;
305 
306   // the introspection api
307   module->have_introspection = FALSE;
308   if(module->introspection_init)
309   {
310     if(!module->introspection_init(module, DT_INTROSPECTION_VERSION))
311     {
312       // set the introspection related fields in module
313       module->have_introspection = TRUE;
314 
315       if(module->get_p == default_get_p ||
316          module->get_f == default_get_f ||
317          module->get_introspection_linear == default_get_introspection_linear ||
318          module->get_introspection == default_get_introspection)
319         goto api_h_error;
320     }
321     else
322       fprintf(stderr, "[iop_load_module] failed to initialize introspection for operation `%s'\n", module_name);
323   }
324 
325   if(module->init_global) module->init_global(module);
326   return 0;
327 }
328 
dt_iop_load_module_by_so(dt_iop_module_t * module,dt_iop_module_so_t * so,dt_develop_t * dev)329 int dt_iop_load_module_by_so(dt_iop_module_t *module, dt_iop_module_so_t *so, dt_develop_t *dev)
330 {
331   module->dev = dev;
332   module->widget = NULL;
333   module->header = NULL;
334   module->off = NULL;
335   module->hide_enable_button = 0;
336   module->has_trouble = FALSE;
337   module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
338   module->request_histogram = DT_REQUEST_ONLY_IN_GUI;
339   module->histogram_stats.bins_count = 0;
340   module->histogram_stats.pixels = 0;
341   module->multi_priority = 0;
342   module->iop_order = 0;
343   for(int k = 0; k < 3; k++)
344   {
345     module->picked_color[k] = module->picked_output_color[k] = 0.0f;
346     module->picked_color_min[k] = module->picked_output_color_min[k] = 666.0f;
347     module->picked_color_max[k] = module->picked_output_color_max[k] = -666.0f;
348   }
349   module->picker = NULL;
350   module->histogram_cst = iop_cs_NONE;
351   module->color_picker_box[0] = module->color_picker_box[1] = .25f;
352   module->color_picker_box[2] = module->color_picker_box[3] = .75f;
353   module->color_picker_point[0] = module->color_picker_point[1] = 0.5f;
354   module->histogram = NULL;
355   module->histogram_max[0] = module->histogram_max[1] = module->histogram_max[2] = module->histogram_max[3]
356       = 0;
357   module->histogram_middle_grey = FALSE;
358   module->request_mask_display = DT_DEV_PIXELPIPE_DISPLAY_NONE;
359   module->suppress_mask = 0;
360   module->enabled = module->default_enabled = 0; // all modules disabled by default.
361   g_strlcpy(module->op, so->op, 20);
362   module->raster_mask.source.users = g_hash_table_new(NULL, NULL);
363   module->raster_mask.source.masks = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
364   module->raster_mask.sink.source = NULL;
365   module->raster_mask.sink.id = 0;
366 
367   // only reference cached results of dlopen:
368   module->module = so->module;
369   module->so = so;
370 
371 #define INCLUDE_API_FROM_MODULE_LOAD_BY_SO
372 #include "iop/iop_api.h"
373 
374   module->version = so->version;
375   module->process_plain = so->process_plain;
376   module->have_introspection = so->have_introspection;
377 
378   module->accel_closures = NULL;
379   module->accel_closures_local = NULL;
380   module->local_closures_connected = FALSE;
381   module->reset_button = NULL;
382   module->presets_button = NULL;
383   module->fusion_slider = NULL;
384 
385   if(module->dev && module->dev->gui_attached)
386   {
387     /* set button state */
388     char option[1024];
389     snprintf(option, sizeof(option), "plugins/darkroom/%s/visible", module->op);
390     dt_iop_module_state_t state = dt_iop_state_HIDDEN;
391     if(dt_conf_get_bool(option))
392     {
393       state = dt_iop_state_ACTIVE;
394       snprintf(option, sizeof(option), "plugins/darkroom/%s/favorite", module->op);
395       if(dt_conf_get_bool(option)) state = dt_iop_state_FAVORITE;
396     }
397     dt_iop_gui_set_state(module, state);
398   }
399 
400   module->global_data = so->data;
401 
402   // now init the instance:
403   module->init(module);
404 
405   /* initialize blendop params and default values */
406   module->blend_params = calloc(1, sizeof(dt_develop_blend_params_t));
407   module->default_blendop_params = calloc(1, sizeof(dt_develop_blend_params_t));
408   dt_develop_blend_colorspace_t cst = dt_develop_blend_default_module_blend_colorspace(module);
409   dt_develop_blend_init_blend_parameters(module->default_blendop_params, cst);
410   dt_iop_commit_blend_params(module, module->default_blendop_params);
411 
412   if(module->params_size == 0)
413   {
414     fprintf(stderr, "[iop_load_module] `%s' needs to have a params size > 0!\n", so->op);
415     return 1; // empty params hurt us in many places, just add a dummy value
416   }
417   module->enabled = module->default_enabled; // apply (possibly new) default.
418   return 0;
419 }
420 
dt_iop_init_pipe(struct dt_iop_module_t * module,struct dt_dev_pixelpipe_t * pipe,struct dt_dev_pixelpipe_iop_t * piece)421 void dt_iop_init_pipe(struct dt_iop_module_t *module, struct dt_dev_pixelpipe_t *pipe,
422                       struct dt_dev_pixelpipe_iop_t *piece)
423 {
424   module->init_pipe(module, pipe, piece);
425   piece->blendop_data = calloc(1, sizeof(dt_develop_blend_params_t));
426 }
427 
_header_motion_notify_show_callback(GtkWidget * eventbox,GdkEventCrossing * event,GtkWidget * header)428 static gboolean _header_motion_notify_show_callback(GtkWidget *eventbox, GdkEventCrossing *event, GtkWidget *header)
429 {
430   return dt_iop_show_hide_header_buttons(header, event, TRUE, FALSE);
431 }
432 
_header_motion_notify_hide_callback(GtkWidget * eventbox,GdkEventCrossing * event,GtkWidget * header)433 static gboolean _header_motion_notify_hide_callback(GtkWidget *eventbox, GdkEventCrossing *event, GtkWidget *header)
434 {
435   return dt_iop_show_hide_header_buttons(header, event, FALSE, FALSE);
436 }
437 
_header_menu_deactivate_callback(GtkMenuShell * menushell,GtkWidget * header)438 static gboolean _header_menu_deactivate_callback(GtkMenuShell *menushell, GtkWidget *header)
439 {
440   return dt_iop_show_hide_header_buttons(header, NULL, FALSE, FALSE);
441 }
442 
dt_iop_gui_delete_callback(GtkButton * button,dt_iop_module_t * module)443 static void dt_iop_gui_delete_callback(GtkButton *button, dt_iop_module_t *module)
444 {
445   dt_develop_t *dev = module->dev;
446 
447   // we search another module with the same base
448   // we want the next module if any or the previous one
449   GList *modules = module->dev->iop;
450   dt_iop_module_t *next = NULL;
451   int find = 0;
452   while(modules)
453   {
454     dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
455     if(mod == module)
456     {
457       find = 1;
458       if(next) break;
459     }
460     else if(mod->instance == module->instance)
461     {
462       next = mod;
463       if(find) break;
464     }
465     modules = g_list_next(modules);
466   }
467   if(!next) return; // what happened ???
468 
469   if(dev->gui_attached)
470     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_DEVELOP_HISTORY_WILL_CHANGE,
471                             dt_history_duplicate(darktable.develop->history), darktable.develop->history_end,
472                             dt_ioppr_iop_order_copy_deep(darktable.develop->iop_order_list));
473 
474   // we must pay attention if priority is 0
475   const gboolean is_zero = (module->multi_priority == 0);
476 
477   // we set the focus to the other instance
478   dt_iop_gui_set_expanded(next, TRUE, FALSE);
479   dt_iop_request_focus(next);
480 
481   ++darktable.gui->reset;
482 
483   // we remove the plugin effectively
484   if(!dt_iop_is_hidden(module))
485   {
486     // we just hide the module to avoid lots of gtk critical warnings
487     gtk_widget_hide(module->expander);
488 
489     // we move the module far away, to avoid problems when reordering instance after that
490     // FIXME: ?????
491     gtk_box_reorder_child(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER),
492                           module->expander, -1);
493 
494     dt_iop_gui_cleanup_module(module);
495     gtk_widget_destroy(module->widget);
496   }
497 
498   // we remove all references in the history stack and dev->iop
499   // this will inform that a module has been removed from history
500   // we do it here so we have the multi_priorities to reconstruct
501   // de deleted module if the user undo it
502   dt_dev_module_remove(dev, module);
503 
504   // if module was priority 0, then we set next to priority 0
505   if(is_zero)
506   {
507     // we want the first one in history
508     dt_iop_module_t *first = NULL;
509     GList *history = dev->history;
510     while(history)
511     {
512       dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
513       if(hist->module->instance == module->instance && hist->module != module)
514       {
515         first = hist->module;
516         break;
517       }
518       history = g_list_next(history);
519     }
520     if(first == NULL) first = next;
521 
522     // we set priority of first to 0
523     dt_iop_update_multi_priority(first, 0);
524 
525     // we change this in the history stack too
526     for(history = dev->history; history; history = g_list_next(history))
527     {
528       dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
529       if(hist->module == first) hist->multi_priority = 0;
530     }
531   }
532 
533   // we save the current state of history (with the new multi_priorities)
534   if(dev->gui_attached)
535   {
536     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_DEVELOP_HISTORY_CHANGE);
537   }
538 
539   // rebuild the accelerators (to point to an extant module)
540   dt_iop_connect_accels_multi(module->so);
541 
542   dt_accel_cleanup_closures_iop(module);
543 
544   // don't delete the module, a pipe may still need it
545   dev->alliop = g_list_append(dev->alliop, module);
546 
547   // we update show params for multi-instances for each other instances
548   dt_dev_modules_update_multishow(dev);
549 
550   // we refresh the pipe
551   dev->pipe->changed |= DT_DEV_PIPE_REMOVE;
552   dev->preview_pipe->changed |= DT_DEV_PIPE_REMOVE;
553   dev->preview2_pipe->changed |= DT_DEV_PIPE_REMOVE;
554   dev->pipe->cache_obsolete = 1;
555   dev->preview_pipe->cache_obsolete = 1;
556   dev->preview2_pipe->cache_obsolete = 1;
557 
558   // invalidate buffers and force redraw of darkroom
559   dt_dev_invalidate_all(dev);
560 
561   /* redraw */
562   dt_control_queue_redraw_center();
563 
564   --darktable.gui->reset;
565 }
566 
dt_iop_gui_get_previous_visible_module(dt_iop_module_t * module)567 dt_iop_module_t *dt_iop_gui_get_previous_visible_module(dt_iop_module_t *module)
568 {
569   dt_iop_module_t *prev = NULL;
570   for(GList *modules = module->dev->iop; modules; modules = g_list_next(modules))
571   {
572     dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
573     if(mod == module)
574     {
575       break;
576     }
577     else
578     {
579       // only for visible modules
580       GtkWidget *expander = mod->expander;
581       if(expander && gtk_widget_is_visible(expander))
582       {
583         prev = mod;
584       }
585     }
586   }
587   return prev;
588 }
589 
dt_iop_gui_get_next_visible_module(dt_iop_module_t * module)590 dt_iop_module_t *dt_iop_gui_get_next_visible_module(dt_iop_module_t *module)
591 {
592   dt_iop_module_t *next = NULL;
593   for(const GList *modules = g_list_last(module->dev->iop); modules; modules = g_list_previous(modules))
594   {
595     dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
596     if(mod == module)
597     {
598       break;
599     }
600     else
601     {
602       // only for visible modules
603       GtkWidget *expander = mod->expander;
604       if(expander && gtk_widget_is_visible(expander))
605       {
606         next = mod;
607       }
608     }
609   }
610   return next;
611 }
612 
dt_iop_gui_movedown_callback(GtkButton * button,dt_iop_module_t * module)613 static void dt_iop_gui_movedown_callback(GtkButton *button, dt_iop_module_t *module)
614 {
615   dt_ioppr_check_iop_order(module->dev, 0, "dt_iop_gui_movedown_callback begin");
616 
617   // we need to place this module right before the previous
618   dt_iop_module_t *prev = dt_iop_gui_get_previous_visible_module(module);
619   // dt_ioppr_check_iop_order(module->dev, "dt_iop_gui_movedown_callback 1");
620   if(!prev) return;
621 
622   const int moved = dt_ioppr_move_iop_before(module->dev, module, prev);
623   // dt_ioppr_check_iop_order(module->dev, "dt_iop_gui_movedown_callback 2");
624   if(!moved) return;
625 
626   // we move the headers
627   GValue gv = { 0, { { 0 } } };
628   g_value_init(&gv, G_TYPE_INT);
629   gtk_container_child_get_property(
630       GTK_CONTAINER(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER)), prev->expander,
631       "position", &gv);
632   gtk_box_reorder_child(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER),
633                         module->expander, g_value_get_int(&gv));
634 
635   // we update the headers
636   dt_dev_modules_update_multishow(prev->dev);
637 
638   dt_dev_add_history_item(prev->dev, module, TRUE);
639 
640   dt_ioppr_check_iop_order(module->dev, 0, "dt_iop_gui_movedown_callback end");
641 
642   // we rebuild the pipe
643   module->dev->pipe->changed |= DT_DEV_PIPE_REMOVE;
644   module->dev->preview_pipe->changed |= DT_DEV_PIPE_REMOVE;
645   module->dev->preview2_pipe->changed |= DT_DEV_PIPE_REMOVE;
646   module->dev->pipe->cache_obsolete = 1;
647   module->dev->preview_pipe->cache_obsolete = 1;
648   module->dev->preview2_pipe->cache_obsolete = 1;
649 
650   // rebuild the accelerators
651   dt_iop_connect_accels_multi(module->so);
652   DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_DEVELOP_MODULE_MOVED);
653 
654   // invalidate buffers and force redraw of darkroom
655   dt_dev_invalidate_all(module->dev);
656 }
657 
dt_iop_gui_moveup_callback(GtkButton * button,dt_iop_module_t * module)658 static void dt_iop_gui_moveup_callback(GtkButton *button, dt_iop_module_t *module)
659 {
660   dt_ioppr_check_iop_order(module->dev, 0, "dt_iop_gui_moveup_callback begin");
661 
662   // we need to place this module right after the next one
663   dt_iop_module_t *next = dt_iop_gui_get_next_visible_module(module);
664   if(!next) return;
665 
666   const int moved = dt_ioppr_move_iop_after(module->dev, module, next);
667   if(!moved) return;
668 
669   // we move the headers
670   GValue gv = { 0, { { 0 } } };
671   g_value_init(&gv, G_TYPE_INT);
672   gtk_container_child_get_property(
673       GTK_CONTAINER(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER)), next->expander,
674       "position", &gv);
675 
676   gtk_box_reorder_child(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER),
677                         module->expander, g_value_get_int(&gv));
678 
679   // we update the headers
680   dt_dev_modules_update_multishow(next->dev);
681 
682   dt_dev_add_history_item(next->dev, module, TRUE);
683 
684   dt_ioppr_check_iop_order(module->dev, 0, "dt_iop_gui_moveup_callback end");
685 
686   // we rebuild the pipe
687   next->dev->pipe->changed |= DT_DEV_PIPE_REMOVE;
688   next->dev->preview_pipe->changed |= DT_DEV_PIPE_REMOVE;
689   next->dev->preview2_pipe->changed |= DT_DEV_PIPE_REMOVE;
690   next->dev->pipe->cache_obsolete = 1;
691   next->dev->preview_pipe->cache_obsolete = 1;
692   next->dev->preview2_pipe->cache_obsolete = 1;
693 
694   // rebuild the accelerators
695   dt_iop_connect_accels_multi(module->so);
696   DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_DEVELOP_MODULE_MOVED);
697 
698   // invalidate buffers and force redraw of darkroom
699   dt_dev_invalidate_all(next->dev);
700 }
701 
dt_iop_gui_duplicate(dt_iop_module_t * base,gboolean copy_params)702 dt_iop_module_t *dt_iop_gui_duplicate(dt_iop_module_t *base, gboolean copy_params)
703 {
704   // make sure the duplicated module appears in the history
705   dt_dev_add_history_item(base->dev, base, FALSE);
706 
707   // first we create the new module
708   ++darktable.gui->reset;
709   dt_iop_module_t *module = dt_dev_module_duplicate(base->dev, base);
710   --darktable.gui->reset;
711   if(!module) return NULL;
712 
713   // what is the position of the module in the pipe ?
714   GList *modules = module->dev->iop;
715   int pos_module = 0;
716   int pos_base = 0;
717   int pos = 0;
718   while(modules)
719   {
720     dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
721     if(mod == module)
722       pos_module = pos;
723     else if(mod == base)
724       pos_base = pos;
725     modules = g_list_next(modules);
726     pos++;
727   }
728 
729   // we set the gui part of it
730   /* initialize gui if iop have one defined */
731   if(!dt_iop_is_hidden(module))
732   {
733     // make sure gui_init and reload defaults is called safely
734     dt_iop_gui_init(module);
735 
736     /* add module to right panel */
737     dt_iop_gui_set_expander(module);
738     GValue gv = { 0, { { 0 } } };
739     g_value_init(&gv, G_TYPE_INT);
740     gtk_container_child_get_property(
741         GTK_CONTAINER(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER)),
742         base->expander, "position", &gv);
743     gtk_box_reorder_child(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER),
744                           module->expander, g_value_get_int(&gv) + pos_base - pos_module + 1);
745     dt_iop_gui_set_expanded(module, TRUE, FALSE);
746 
747     if(dt_conf_get_bool("darkroom/ui/scroll_to_module"))
748         darktable.gui->scroll_to[1] = module->expander;
749 
750     dt_iop_reload_defaults(module); // some modules like profiled denoise update the gui in reload_defaults
751 
752     if(copy_params)
753     {
754       memcpy(module->params, base->params, module->params_size);
755       if(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
756       {
757         dt_iop_commit_blend_params(module, base->blend_params);
758         if(base->blend_params->mask_id > 0)
759         {
760           module->blend_params->mask_id = 0;
761           dt_masks_iop_use_same_as(module, base);
762         }
763       }
764     }
765 
766     // we save the new instance creation
767     dt_dev_add_history_item(module->dev, module, TRUE);
768 
769     dt_iop_gui_update_blending(module);
770   }
771 
772   if(dt_conf_get_bool("darkroom/ui/single_module"))
773   {
774     dt_iop_gui_set_expanded(base, FALSE, TRUE);
775     dt_iop_gui_set_expanded(module, TRUE, TRUE);
776   }
777 
778   // we update show params for multi-instances for each other instances
779   dt_dev_modules_update_multishow(module->dev);
780 
781   // and we refresh the pipe
782   dt_iop_request_focus(module);
783 
784   if(module->dev->gui_attached)
785   {
786     module->dev->pipe->changed |= DT_DEV_PIPE_REMOVE;
787     module->dev->preview_pipe->changed |= DT_DEV_PIPE_REMOVE;
788     module->dev->preview2_pipe->changed |= DT_DEV_PIPE_REMOVE;
789     module->dev->pipe->cache_obsolete = 1;
790     module->dev->preview_pipe->cache_obsolete = 1;
791     module->dev->preview2_pipe->cache_obsolete = 1;
792 
793     // invalidate buffers and force redraw of darkroom
794     dt_dev_invalidate_all(module->dev);
795   }
796 
797   /* update ui to new parameters */
798   dt_iop_gui_update(module);
799 
800   dt_dev_modulegroups_update_visibility(darktable.develop);
801 
802   return module;
803 }
804 
dt_iop_gui_copy_callback(GtkButton * button,gpointer user_data)805 static void dt_iop_gui_copy_callback(GtkButton *button, gpointer user_data)
806 {
807   dt_iop_gui_duplicate(user_data, FALSE);
808 
809   /* setup key accelerators */
810   dt_iop_connect_accels_multi(((dt_iop_module_t *)user_data)->so);
811 }
812 
dt_iop_gui_duplicate_callback(GtkButton * button,gpointer user_data)813 static void dt_iop_gui_duplicate_callback(GtkButton *button, gpointer user_data)
814 {
815   dt_iop_gui_duplicate(user_data, TRUE);
816 
817   /* setup key accelerators */
818   dt_iop_connect_accels_multi(((dt_iop_module_t *)user_data)->so);
819 }
820 
_rename_module_key_press(GtkWidget * entry,GdkEventKey * event,dt_iop_module_t * module)821 static gboolean _rename_module_key_press(GtkWidget *entry, GdkEventKey *event, dt_iop_module_t *module)
822 {
823   int ended = 0;
824 
825   if(event->type == GDK_FOCUS_CHANGE || event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter)
826   {
827     if(gtk_entry_get_text_length(GTK_ENTRY(entry)) > 0)
828     {
829       // name is not empty, set new multi_name
830 
831        const gchar *name = gtk_entry_get_text(GTK_ENTRY(entry));
832 
833       // restore saved 1st character of instance name (without it the same name wouls still produce unnecessary copy + add history item)
834       module->multi_name[0] = module->multi_name[sizeof(module->multi_name) - 1];
835       module->multi_name[sizeof(module->multi_name) - 1] = 0;
836 
837       if(g_strcmp0(module->multi_name, name) != 0)
838       {
839         g_strlcpy(module->multi_name, name, sizeof(module->multi_name));
840         dt_dev_add_history_item(module->dev, module, TRUE);
841       }
842     }
843     else
844     {
845       // clear out multi-name (set 1st char to 0)
846       module->multi_name[0] = 0;
847       dt_dev_add_history_item(module->dev, module, TRUE);
848     }
849 
850     ended = 1;
851   }
852   else if(event->keyval == GDK_KEY_Escape)
853   {
854     // restore saved 1st character of instance name
855     module->multi_name[0] = module->multi_name[sizeof(module->multi_name) - 1];
856     module->multi_name[sizeof(module->multi_name) - 1] = 0;
857 
858     ended = 1;
859   }
860 
861   if(ended)
862   {
863     g_signal_handlers_disconnect_by_func(entry, G_CALLBACK(_rename_module_key_press), module);
864     gtk_widget_destroy(entry);
865     dt_iop_show_hide_header_buttons(module->header, NULL, TRUE, FALSE); // after removing entry
866     dt_iop_gui_update_header(module);
867     dt_masks_group_update_name(module);
868     return TRUE;
869   }
870 
871   return FALSE; /* event not handled */
872 }
873 
_rename_module_resize(GtkWidget * entry,GdkEventKey * event,dt_iop_module_t * module)874 static gboolean _rename_module_resize(GtkWidget *entry, GdkEventKey *event, dt_iop_module_t *module)
875 {
876   int width = 0;
877   GtkBorder padding;
878 
879   pango_layout_get_pixel_size(gtk_entry_get_layout(GTK_ENTRY(entry)), &width, NULL);
880   gtk_style_context_get_padding(gtk_widget_get_style_context (entry),
881                                 gtk_widget_get_state_flags (entry),
882                                 &padding);
883   gtk_widget_set_size_request(entry, width + padding.left + padding.right + 1, -1);
884 
885   return TRUE;
886 }
887 
_iop_gui_rename_module(dt_iop_module_t * module)888 static void _iop_gui_rename_module(dt_iop_module_t *module)
889 {
890   GtkWidget *focused = gtk_container_get_focus_child(GTK_CONTAINER(module->header));
891   if(focused && GTK_IS_ENTRY(focused)) return;
892 
893   GtkWidget *entry = gtk_entry_new();
894 
895   gtk_widget_set_name(entry, "iop-panel-label");
896   gtk_entry_set_width_chars(GTK_ENTRY(entry), 0);
897   gtk_entry_set_max_length(GTK_ENTRY(entry), sizeof(module->multi_name) - 1);
898   gtk_entry_set_text(GTK_ENTRY(entry), module->multi_name);
899 
900   // remove instance name but save 1st character in case of escape
901   module->multi_name[sizeof(module->multi_name) - 1] = module->multi_name[0];
902   module->multi_name[0] = 0;
903   dt_iop_gui_update_header(module);
904 
905   dt_gui_key_accel_block_on_focus_connect(entry); // needs to be before focus-out-event
906   g_signal_connect(entry, "key-press-event", G_CALLBACK(_rename_module_key_press), module);
907   g_signal_connect(entry, "focus-out-event", G_CALLBACK(_rename_module_key_press), module);
908   g_signal_connect(entry, "style-updated", G_CALLBACK(_rename_module_resize), module);
909   g_signal_connect(entry, "changed", G_CALLBACK(_rename_module_resize), module);
910 
911   dt_iop_show_hide_header_buttons(module->header, NULL, FALSE, TRUE); // before adding entry
912   gtk_box_pack_start(GTK_BOX(module->header), entry, TRUE, TRUE, 0);
913   gtk_widget_show(entry);
914   gtk_widget_grab_focus(entry);
915 }
916 
dt_iop_gui_rename_callback(GtkButton * button,dt_iop_module_t * module)917 static void dt_iop_gui_rename_callback(GtkButton *button, dt_iop_module_t *module)
918 {
919   _iop_gui_rename_module(module);
920 }
921 
dt_iop_gui_multiinstance_callback(GtkButton * button,GdkEventButton * event,gpointer user_data)922 static void dt_iop_gui_multiinstance_callback(GtkButton *button, GdkEventButton *event, gpointer user_data)
923 {
924   dt_iop_module_t *module = (dt_iop_module_t *)user_data;
925 
926   if(event->button == 3)
927   {
928     if(!(module->flags() & IOP_FLAGS_ONE_INSTANCE)) dt_iop_gui_copy_callback(button, user_data);
929     return;
930   }
931   else if(event->button == 2)
932   {
933     return;
934   }
935 
936   GtkMenuShell *menu = GTK_MENU_SHELL(gtk_menu_new());
937   GtkWidget *item;
938 
939   item = gtk_menu_item_new_with_label(_("new instance"));
940   // gtk_widget_set_tooltip_text(item, _("add a new instance of this module to the pipe"));
941   g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(dt_iop_gui_copy_callback), module);
942   gtk_widget_set_sensitive(item, module->multi_show_new);
943   gtk_menu_shell_append(menu, item);
944 
945   item = gtk_menu_item_new_with_label(_("duplicate instance"));
946   // gtk_widget_set_tooltip_text(item, _("add a copy of this instance to the pipe"));
947   g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(dt_iop_gui_duplicate_callback), module);
948   gtk_widget_set_sensitive(item, module->multi_show_new);
949   gtk_menu_shell_append(menu, item);
950 
951   item = gtk_menu_item_new_with_label(_("move up"));
952   // gtk_widget_set_tooltip_text(item, _("move this instance up"));
953   g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(dt_iop_gui_moveup_callback), module);
954   gtk_widget_set_sensitive(item, module->multi_show_up);
955   gtk_menu_shell_append(menu, item);
956 
957   item = gtk_menu_item_new_with_label(_("move down"));
958   // gtk_widget_set_tooltip_text(item, _("move this instance down"));
959   g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(dt_iop_gui_movedown_callback), module);
960   gtk_widget_set_sensitive(item, module->multi_show_down);
961   gtk_menu_shell_append(menu, item);
962 
963   item = gtk_menu_item_new_with_label(_("delete"));
964   // gtk_widget_set_tooltip_text(item, _("delete this instance"));
965   g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(dt_iop_gui_delete_callback), module);
966   gtk_widget_set_sensitive(item, module->multi_show_close);
967   gtk_menu_shell_append(menu, item);
968 
969   gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
970   item = gtk_menu_item_new_with_label(_("rename"));
971   g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(dt_iop_gui_rename_callback), module);
972   gtk_menu_shell_append(menu, item);
973 
974   gtk_widget_show_all(GTK_WIDGET(menu));
975 
976   g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(_header_menu_deactivate_callback), module->header);
977 
978   // popup
979 #if GTK_CHECK_VERSION(3, 22, 0)
980   gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(button), GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST, NULL);
981 #else
982   gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
983 #endif
984 
985   // make sure the button is deactivated now that the menu is opened
986   dtgtk_button_set_active(DTGTK_BUTTON(button), FALSE);
987 }
988 
dt_iop_gui_off_button_press(GtkWidget * w,GdkEventButton * e,gpointer user_data)989 static gboolean dt_iop_gui_off_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
990 {
991   dt_iop_module_t *module = (dt_iop_module_t *)user_data;
992   if(!darktable.gui->reset && dt_modifier_is(e->state, GDK_CONTROL_MASK))
993   {
994     dt_iop_request_focus(darktable.develop->gui_module == module ? NULL : module);
995     return TRUE;
996   }
997   return FALSE;
998 }
999 
dt_iop_gui_off_callback(GtkToggleButton * togglebutton,gpointer user_data)1000 static void dt_iop_gui_off_callback(GtkToggleButton *togglebutton, gpointer user_data)
1001 {
1002   dt_iop_module_t *module = (dt_iop_module_t *)user_data;
1003 
1004   const gboolean basics = (dt_dev_modulegroups_get_activated(module->dev) == DT_MODULEGROUP_BASICS);
1005 
1006   if(!darktable.gui->reset)
1007   {
1008     if(gtk_toggle_button_get_active(togglebutton))
1009     {
1010       module->enabled = 1;
1011 
1012       if(dt_conf_get_bool("darkroom/ui/scroll_to_module"))
1013         darktable.gui->scroll_to[1] = module->expander;
1014 
1015       if(!basics && dt_conf_get_bool("darkroom/ui/activate_expand") && !module->expanded)
1016         dt_iop_gui_set_expanded(module, TRUE, dt_conf_get_bool("darkroom/ui/single_module"));
1017 
1018       dt_dev_add_history_item(module->dev, module, FALSE);
1019     }
1020     else
1021     {
1022       module->enabled = 0;
1023 
1024       //  if current module is set as the CAT instance, remove that setting
1025       if(module->dev->proxy.chroma_adaptation == module)
1026         module->dev->proxy.chroma_adaptation = NULL;
1027 
1028       dt_dev_add_history_item(module->dev, module, FALSE);
1029 
1030       if(!basics && dt_conf_get_bool("darkroom/ui/activate_expand") && module->expanded)
1031         dt_iop_gui_set_expanded(module, FALSE, FALSE);
1032     }
1033 
1034     const gboolean raster = module->blend_params->mask_mode & DEVELOP_MASK_RASTER;
1035     // set mask indicator sensitive according to module activation and raster mask
1036     if(module->mask_indicator)
1037       gtk_widget_set_sensitive(module->mask_indicator, !raster && module->enabled);
1038   }
1039 
1040   char tooltip[512];
1041   gchar *module_label = dt_history_item_get_name(module);
1042   snprintf(tooltip, sizeof(tooltip), module->enabled ? _("%s is switched on") : _("%s is switched off"),
1043            module_label);
1044   g_free(module_label);
1045   gtk_widget_set_tooltip_text(GTK_WIDGET(togglebutton), tooltip);
1046   gtk_widget_queue_draw(GTK_WIDGET(togglebutton));
1047 
1048   // rebuild the accelerators
1049   dt_iop_connect_accels_multi(module->so);
1050 
1051   if(module->enabled && !gtk_widget_is_visible(module->header))
1052     dt_dev_modulegroups_update_visibility(darktable.develop);
1053 }
1054 
dt_iop_so_is_hidden(dt_iop_module_so_t * module)1055 gboolean dt_iop_so_is_hidden(dt_iop_module_so_t *module)
1056 {
1057   gboolean is_hidden = TRUE;
1058   if(!(module->flags() & IOP_FLAGS_HIDDEN))
1059   {
1060     if(!module->gui_init)
1061       g_debug("Module '%s' is not hidden and lacks implementation of gui_init()...", module->op);
1062     else if(!module->gui_cleanup)
1063       g_debug("Module '%s' is not hidden and lacks implementation of gui_cleanup()...", module->op);
1064     else
1065       is_hidden = FALSE;
1066   }
1067   return is_hidden;
1068 }
1069 
dt_iop_is_hidden(dt_iop_module_t * module)1070 gboolean dt_iop_is_hidden(dt_iop_module_t *module)
1071 {
1072   return dt_iop_so_is_hidden(module->so);
1073 }
1074 
dt_iop_shown_in_group(dt_iop_module_t * module,uint32_t group)1075 gboolean dt_iop_shown_in_group(dt_iop_module_t *module, uint32_t group)
1076 {
1077   if(group == DT_MODULEGROUP_NONE) return TRUE;
1078 
1079   return dt_dev_modulegroups_test(module->dev, group, module);
1080 }
1081 
_iop_panel_label(GtkWidget * lab,dt_iop_module_t * module)1082 static void _iop_panel_label(GtkWidget *lab, dt_iop_module_t *module)
1083 {
1084   gtk_widget_set_name(lab, "iop-panel-label");
1085   char *module_name = dt_history_item_get_name_html(module);
1086 
1087   if((module->has_trouble && module->enabled))
1088   {
1089     char *saved_old_name = module_name;
1090     module_name = dt_iop_warning_message(module_name);
1091     g_free(saved_old_name);
1092   }
1093 
1094   gtk_label_set_markup(GTK_LABEL(lab), module_name);
1095   g_free(module_name);
1096 
1097   gtk_label_set_ellipsize(GTK_LABEL(lab), !module->multi_name[0] ? PANGO_ELLIPSIZE_END: PANGO_ELLIPSIZE_MIDDLE);
1098   g_object_set(G_OBJECT(lab), "xalign", 0.0, (gchar *)0);
1099   if((module->flags() & IOP_FLAGS_DEPRECATED) && module->deprecated_msg())
1100     gtk_widget_set_tooltip_text(lab, module->deprecated_msg());
1101   else
1102   {
1103     gchar *tooltip = (char *)module->description(module);
1104     gtk_widget_set_tooltip_text(lab, tooltip);
1105     g_free(tooltip);
1106   }
1107 }
1108 
_iop_gui_update_header(dt_iop_module_t * module)1109 static void _iop_gui_update_header(dt_iop_module_t *module)
1110 {
1111   if (!module->header)                  /* some modules such as overexposed don't actually have a header */
1112     return;
1113   /* get the enable button and button */
1114   GtkWidget *lab = dt_gui_container_nth_child(GTK_CONTAINER(module->header), IOP_MODULE_LABEL);
1115 
1116   // set panel name to display correct multi-instance
1117   _iop_panel_label(lab, module);
1118   dt_iop_gui_set_enable_button(module);
1119 }
1120 
dt_iop_gui_set_enable_button_icon(GtkWidget * w,dt_iop_module_t * module)1121 void dt_iop_gui_set_enable_button_icon(GtkWidget *w, dt_iop_module_t *module)
1122 {
1123   // set on/off icon
1124   if(module->default_enabled && module->hide_enable_button)
1125   {
1126     gtk_widget_set_name(w, "module-always-enabled-button");
1127     dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(w),
1128                                  dtgtk_cairo_paint_switch_on, CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, module);
1129   }
1130   else if(!module->default_enabled && module->hide_enable_button)
1131   {
1132     gtk_widget_set_name(w, "module-always-disabled-button");
1133     dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(w),
1134                                  dtgtk_cairo_paint_switch_off, CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, module);
1135   }
1136   else
1137   {
1138     gtk_widget_set_name(w, "module-enable-button");
1139     dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(w),
1140                                  dtgtk_cairo_paint_switch, CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, module);
1141   }
1142 }
1143 
dt_iop_gui_set_enable_button(dt_iop_module_t * module)1144 void dt_iop_gui_set_enable_button(dt_iop_module_t *module)
1145 {
1146   if(module->off)
1147   {
1148     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), module->enabled);
1149     if(module->hide_enable_button)
1150       gtk_widget_set_sensitive(GTK_WIDGET(module->off), FALSE);
1151     else
1152       gtk_widget_set_sensitive(GTK_WIDGET(module->off), TRUE);
1153 
1154     dt_iop_gui_set_enable_button_icon(GTK_WIDGET(module->off), module);
1155   }
1156 }
1157 
dt_iop_gui_update_header(dt_iop_module_t * module)1158 void dt_iop_gui_update_header(dt_iop_module_t *module)
1159 {
1160   _iop_gui_update_header(module);
1161 }
1162 
dt_iop_set_module_trouble_message(dt_iop_module_t * const module,const char * const trouble_msg,const char * const trouble_tooltip,const char * const stderr_message)1163 void dt_iop_set_module_trouble_message(dt_iop_module_t *const module,
1164                                        const char* const trouble_msg,
1165                                        const char* const trouble_tooltip,
1166                                        const char *const stderr_message)
1167 {
1168   //  first stderr message if any
1169   if(stderr_message)
1170   {
1171     const char *name = module ? module->name() : "?";
1172     fprintf(stderr, "[%s] %s\n", name, stderr_message ? stderr_message : trouble_msg);
1173   }
1174 
1175   if(!dt_iop_is_hidden(module) && module->gui_data && dt_conf_get_bool("plugins/darkroom/show_warnings"))
1176     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_TROUBLE_MESSAGE,
1177                                   module, trouble_msg, trouble_tooltip);
1178 }
1179 
_iop_gui_update_label(dt_iop_module_t * module)1180 static void _iop_gui_update_label(dt_iop_module_t *module)
1181 {
1182   if(!module->header) return;
1183   GtkWidget *lab = dt_gui_container_nth_child(GTK_CONTAINER(module->header), IOP_MODULE_LABEL);
1184   _iop_panel_label(lab, module);
1185 }
1186 
dt_iop_gui_init(dt_iop_module_t * module)1187 void dt_iop_gui_init(dt_iop_module_t *module)
1188 {
1189   ++darktable.gui->reset;
1190   --darktable.bauhaus->skip_accel;
1191   if(module->gui_init) module->gui_init(module);
1192   ++darktable.bauhaus->skip_accel;
1193   --darktable.gui->reset;
1194 }
1195 
dt_iop_reload_defaults(dt_iop_module_t * module)1196 void dt_iop_reload_defaults(dt_iop_module_t *module)
1197 {
1198   if(darktable.gui) ++darktable.gui->reset;
1199   if(module->reload_defaults)
1200   {
1201     // report if reload_defaults was called unnecessarily => this should be considered a bug
1202     // the whole point of reload_defaults is to update defaults _based on current image_
1203     // any required initialisation should go in init (and not be performed repeatedly here)
1204     if(module->dev)
1205     {
1206       module->reload_defaults(module);
1207       dt_print(DT_DEBUG_PARAMS, "[params] defaults reloaded for %s\n", module->op);
1208     }
1209     else
1210     {
1211       fprintf(stderr, "reload_defaults should not be called without image.\n");
1212     }
1213   }
1214   dt_iop_load_default_params(module);
1215   if(darktable.gui) --darktable.gui->reset;
1216 
1217   if(module->header) _iop_gui_update_header(module);
1218 }
1219 
dt_iop_cleanup_histogram(gpointer data,gpointer user_data)1220 void dt_iop_cleanup_histogram(gpointer data, gpointer user_data)
1221 {
1222   dt_iop_module_t *module = (dt_iop_module_t *)data;
1223 
1224   free(module->histogram);
1225   module->histogram = NULL;
1226   module->histogram_stats.bins_count = 0;
1227   module->histogram_stats.pixels = 0;
1228 }
1229 
init_presets(dt_iop_module_so_t * module_so)1230 static void init_presets(dt_iop_module_so_t *module_so)
1231 {
1232   if(module_so->init_presets) module_so->init_presets(module_so);
1233 
1234   // this seems like a reasonable place to check for and update legacy
1235   // presets.
1236 
1237   const int32_t module_version = module_so->version();
1238 
1239   sqlite3_stmt *stmt;
1240   DT_DEBUG_SQLITE3_PREPARE_V2(
1241       dt_database_get(darktable.db),
1242       "SELECT name, op_version, op_params, blendop_version, blendop_params FROM data.presets WHERE operation = ?1",
1243       -1, &stmt, NULL);
1244   DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, module_so->op, -1, SQLITE_TRANSIENT);
1245 
1246   while(sqlite3_step(stmt) == SQLITE_ROW)
1247   {
1248     const char *name = (char *)sqlite3_column_text(stmt, 0);
1249     int32_t old_params_version = sqlite3_column_int(stmt, 1);
1250     const void *old_params = (void *)sqlite3_column_blob(stmt, 2);
1251     const int32_t old_params_size = sqlite3_column_bytes(stmt, 2);
1252     const int32_t old_blend_params_version = sqlite3_column_int(stmt, 3);
1253     const void *old_blend_params = (void *)sqlite3_column_blob(stmt, 4);
1254     const int32_t old_blend_params_size = sqlite3_column_bytes(stmt, 4);
1255 
1256     if(old_params_version == 0)
1257     {
1258       // this preset doesn't have a version.  go digging through the database
1259       // to find a history entry that matches the preset params, and get
1260       // the module version from that.
1261 
1262       sqlite3_stmt *stmt2;
1263       DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1264                                   "SELECT module FROM main.history WHERE operation = ?1 AND op_params = ?2", -1,
1265                                   &stmt2, NULL);
1266       DT_DEBUG_SQLITE3_BIND_TEXT(stmt2, 1, module_so->op, -1, SQLITE_TRANSIENT);
1267       DT_DEBUG_SQLITE3_BIND_BLOB(stmt2, 2, old_params, old_params_size, SQLITE_TRANSIENT);
1268 
1269       if(sqlite3_step(stmt2) == SQLITE_ROW)
1270       {
1271         old_params_version = sqlite3_column_int(stmt2, 0);
1272       }
1273       else
1274       {
1275         fprintf(stderr, "[imageop_init_presets] WARNING: Could not find versioning information for '%s' "
1276                         "preset '%s'\nUntil some is found, the preset will be unavailable.\n(To make it "
1277                         "return, please load an image that uses the preset.)\n",
1278                 module_so->op, name);
1279         sqlite3_finalize(stmt2);
1280         continue;
1281       }
1282 
1283       sqlite3_finalize(stmt2);
1284 
1285       // we found an old params version.  Update the database with it.
1286 
1287       fprintf(stderr, "[imageop_init_presets] Found version %d for '%s' preset '%s'\n", old_params_version,
1288               module_so->op, name);
1289 
1290       DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1291                                   "UPDATE data.presets SET op_version=?1 WHERE operation=?2 AND name=?3", -1,
1292                                   &stmt2, NULL);
1293       DT_DEBUG_SQLITE3_BIND_INT(stmt2, 1, old_params_version);
1294       DT_DEBUG_SQLITE3_BIND_TEXT(stmt2, 2, module_so->op, -1, SQLITE_TRANSIENT);
1295       DT_DEBUG_SQLITE3_BIND_TEXT(stmt2, 3, name, -1, SQLITE_TRANSIENT);
1296 
1297       sqlite3_step(stmt2);
1298       sqlite3_finalize(stmt2);
1299     }
1300 
1301     if(module_version > old_params_version && module_so->legacy_params != NULL)
1302     {
1303       // we need a dt_iop_module_t for legacy_params()
1304       dt_iop_module_t *module;
1305       module = (dt_iop_module_t *)calloc(1, sizeof(dt_iop_module_t));
1306       if(dt_iop_load_module_by_so(module, module_so, NULL))
1307       {
1308         free(module);
1309         continue;
1310       }
1311 /*
1312       module->init(module);
1313       if(module->params_size == 0)
1314       {
1315         dt_iop_cleanup_module(module);
1316         free(module);
1317         continue;
1318       }
1319       // we call reload_defaults() in case the module defines it
1320       if(module->reload_defaults) module->reload_defaults(module); // why not call dt_iop_reload_defaults? (if needed at all)
1321 */
1322 
1323       const int32_t new_params_size = module->params_size;
1324       void *new_params = calloc(1, new_params_size);
1325 
1326       // convert the old params to new
1327       if(module->legacy_params(module, old_params, old_params_version, new_params, module_version))
1328       {
1329         free(new_params);
1330         dt_iop_cleanup_module(module);
1331         free(module);
1332         continue;
1333       }
1334 
1335       fprintf(stderr, "[imageop_init_presets] updating '%s' preset '%s' from version %d to version %d\nto:'%s'",
1336               module_so->op, name, old_params_version, module_version,
1337               dt_exif_xmp_encode(new_params, new_params_size, NULL));
1338 
1339       // and write the new params back to the database
1340       sqlite3_stmt *stmt2;
1341       DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "UPDATE data.presets "
1342                                                                  "SET op_version=?1, op_params=?2 "
1343                                                                  "WHERE operation=?3 AND name=?4",
1344                                   -1, &stmt2, NULL);
1345       DT_DEBUG_SQLITE3_BIND_INT(stmt2, 1, module->version());
1346       DT_DEBUG_SQLITE3_BIND_BLOB(stmt2, 2, new_params, new_params_size, SQLITE_TRANSIENT);
1347       DT_DEBUG_SQLITE3_BIND_TEXT(stmt2, 3, module->op, -1, SQLITE_TRANSIENT);
1348       DT_DEBUG_SQLITE3_BIND_TEXT(stmt2, 4, name, -1, SQLITE_TRANSIENT);
1349 
1350       sqlite3_step(stmt2);
1351       sqlite3_finalize(stmt2);
1352 
1353       free(new_params);
1354       dt_iop_cleanup_module(module);
1355       free(module);
1356     }
1357     else if(module_version > old_params_version)
1358     {
1359       fprintf(stderr, "[imageop_init_presets] Can't upgrade '%s' preset '%s' from version %d to %d, no "
1360                       "legacy_params() implemented \n",
1361               module_so->op, name, old_params_version, module_version);
1362     }
1363 
1364     if(!old_blend_params || dt_develop_blend_version() > old_blend_params_version)
1365     {
1366       fprintf(stderr,
1367               "[imageop_init_presets] updating '%s' preset '%s' from blendop version %d to version %d\n",
1368               module_so->op, name, old_blend_params_version, dt_develop_blend_version());
1369 
1370       // we need a dt_iop_module_t for dt_develop_blend_legacy_params()
1371       // using dt_develop_blend_legacy_params_by_so won't help as we need "module" anyway
1372       dt_iop_module_t *module;
1373       module = (dt_iop_module_t *)calloc(1, sizeof(dt_iop_module_t));
1374       if(dt_iop_load_module_by_so(module, module_so, NULL))
1375       {
1376         free(module);
1377         continue;
1378       }
1379 
1380       if(module->params_size == 0)
1381       {
1382         dt_iop_cleanup_module(module);
1383         free(module);
1384         continue;
1385       }
1386       void *new_blend_params = malloc(sizeof(dt_develop_blend_params_t));
1387 
1388       // convert the old blend params to new
1389       if(old_blend_params
1390          && dt_develop_blend_legacy_params(module, old_blend_params, old_blend_params_version,
1391                                            new_blend_params, dt_develop_blend_version(),
1392                                            old_blend_params_size) == 0)
1393       {
1394         // do nothing
1395       }
1396       else
1397       {
1398         memcpy(new_blend_params, module->default_blendop_params, sizeof(dt_develop_blend_params_t));
1399       }
1400 
1401       // and write the new blend params back to the database
1402       sqlite3_stmt *stmt2;
1403       DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "UPDATE data.presets "
1404                                                                  "SET blendop_version=?1, blendop_params=?2 "
1405                                                                  "WHERE operation=?3 AND name=?4",
1406                                   -1, &stmt2, NULL);
1407       DT_DEBUG_SQLITE3_BIND_INT(stmt2, 1, dt_develop_blend_version());
1408       DT_DEBUG_SQLITE3_BIND_BLOB(stmt2, 2, new_blend_params, sizeof(dt_develop_blend_params_t),
1409                                  SQLITE_TRANSIENT);
1410       DT_DEBUG_SQLITE3_BIND_TEXT(stmt2, 3, module->op, -1, SQLITE_TRANSIENT);
1411       DT_DEBUG_SQLITE3_BIND_TEXT(stmt2, 4, name, -1, SQLITE_TRANSIENT);
1412 
1413       sqlite3_step(stmt2);
1414       sqlite3_finalize(stmt2);
1415 
1416       free(new_blend_params);
1417       dt_iop_cleanup_module(module);
1418       free(module);
1419     }
1420   }
1421   sqlite3_finalize(stmt);
1422 }
1423 
init_key_accels(dt_iop_module_so_t * module)1424 static void init_key_accels(dt_iop_module_so_t *module)
1425 {
1426   // Calling the accelerator initialization callback, if present
1427   if(module->init_key_accels) (module->init_key_accels)(module);
1428 
1429   /** load shortcuts for presets **/
1430   sqlite3_stmt *stmt;
1431   DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1432                               "SELECT name FROM data.presets WHERE operation=?1 ORDER BY writeprotect DESC, rowid",
1433                               -1, &stmt, NULL);
1434   DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, module->op, -1, SQLITE_TRANSIENT);
1435   while(sqlite3_step(stmt) == SQLITE_ROW)
1436   {
1437     char path[1024];
1438     snprintf(path, sizeof(path), "%s`%s", N_("preset"), (const char *)sqlite3_column_text(stmt, 0));
1439     dt_accel_register_iop(module, FALSE, path, 0, 0);
1440   }
1441   sqlite3_finalize(stmt);
1442 }
1443 
dt_iop_init_module_so(void * m)1444 static void dt_iop_init_module_so(void *m)
1445 {
1446   dt_iop_module_so_t *module = (dt_iop_module_so_t *)m;
1447 
1448   init_presets(module);
1449 
1450   // do not init accelerators if there is no gui
1451   if(darktable.gui)
1452   {
1453     // Calling the accelerator initialization callback, if present
1454     init_key_accels(module);
1455 
1456     // create a gui and have the widgets register their accelerators
1457     dt_iop_module_t *module_instance = (dt_iop_module_t *)calloc(1, sizeof(dt_iop_module_t));
1458 
1459     if(module->gui_init && !dt_iop_load_module_by_so(module_instance, module, NULL))
1460     {
1461       darktable.control->accel_initialising = TRUE;
1462       dt_iop_gui_init(module_instance);
1463 
1464       static gboolean blending_accels_initialized = FALSE;
1465       if(!blending_accels_initialized)
1466       {
1467         dt_iop_colorspace_type_t cst = module->blend_colorspace(module_instance, NULL, NULL);
1468 
1469         if((module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) &&
1470            !(module->flags() & IOP_FLAGS_NO_MASKS) &&
1471            (cst == iop_cs_Lab || cst == iop_cs_rgb))
1472         {
1473           GtkWidget *iopw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1474           dt_iop_gui_init_blending(iopw, module_instance);
1475           dt_iop_gui_cleanup_blending(module_instance);
1476           gtk_widget_destroy(iopw);
1477 
1478           blending_accels_initialized = TRUE;
1479         }
1480       }
1481 
1482       module->gui_cleanup(module_instance);
1483       darktable.control->accel_initialising = FALSE;
1484 
1485       dt_iop_cleanup_module(module_instance);
1486     }
1487 
1488     free(module_instance);
1489 
1490     if(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
1491     {
1492       dt_accel_register_slider_iop(module, FALSE, NC_("accel","fusion") ? "accel|fusion" : "");
1493     }
1494     if(!(module->flags() & IOP_FLAGS_DEPRECATED))
1495     {
1496       dt_accel_register_common_iop(module);
1497     }
1498   }
1499 }
1500 
dt_iop_load_modules_so(void)1501 void dt_iop_load_modules_so(void)
1502 {
1503   darktable.iop = dt_module_load_modules("/plugins", sizeof(dt_iop_module_so_t), dt_iop_load_module_so,
1504                                          dt_iop_init_module_so, NULL);
1505 }
1506 
dt_iop_load_module(dt_iop_module_t * module,dt_iop_module_so_t * module_so,dt_develop_t * dev)1507 int dt_iop_load_module(dt_iop_module_t *module, dt_iop_module_so_t *module_so, dt_develop_t *dev)
1508 {
1509   memset(module, 0, sizeof(dt_iop_module_t));
1510   if(dt_iop_load_module_by_so(module, module_so, dev))
1511   {
1512     free(module);
1513     return 1;
1514   }
1515   return 0;
1516 }
1517 
dt_iop_load_modules_ext(dt_develop_t * dev,gboolean no_image)1518 GList *dt_iop_load_modules_ext(dt_develop_t *dev, gboolean no_image)
1519 {
1520   GList *res = NULL;
1521   dt_iop_module_t *module;
1522   dt_iop_module_so_t *module_so;
1523   dev->iop_instance = 0;
1524   GList *iop = darktable.iop;
1525   while(iop)
1526   {
1527     module_so = (dt_iop_module_so_t *)iop->data;
1528     module = (dt_iop_module_t *)calloc(1, sizeof(dt_iop_module_t));
1529     if(dt_iop_load_module_by_so(module, module_so, dev))
1530     {
1531       free(module);
1532       continue;
1533     }
1534     res = g_list_insert_sorted(res, module, dt_sort_iop_by_order);
1535     module->global_data = module_so->data;
1536     module->so = module_so;
1537     iop = g_list_next(iop);
1538   }
1539 
1540   GList *it = res;
1541   while(it)
1542   {
1543     module = (dt_iop_module_t *)it->data;
1544     module->instance = dev->iop_instance++;
1545     module->multi_name[0] = '\0';
1546     it = g_list_next(it);
1547   }
1548   return res;
1549 }
1550 
dt_iop_load_modules(dt_develop_t * dev)1551 GList *dt_iop_load_modules(dt_develop_t *dev)
1552 {
1553   return dt_iop_load_modules_ext(dev, FALSE);
1554 }
1555 
dt_iop_cleanup_module(dt_iop_module_t * module)1556 void dt_iop_cleanup_module(dt_iop_module_t *module)
1557 {
1558   module->cleanup(module);
1559 
1560   free(module->blend_params);
1561   module->blend_params = NULL;
1562   free(module->default_blendop_params);
1563   module->default_blendop_params = NULL;
1564   module->picker = NULL;
1565   free(module->histogram);
1566   module->histogram = NULL;
1567   g_hash_table_destroy(module->raster_mask.source.users);
1568   g_hash_table_destroy(module->raster_mask.source.masks);
1569   module->raster_mask.source.users = NULL;
1570   module->raster_mask.source.masks = NULL;
1571 }
1572 
dt_iop_unload_modules_so()1573 void dt_iop_unload_modules_so()
1574 {
1575   while(darktable.iop)
1576   {
1577     dt_iop_module_so_t *module = (dt_iop_module_so_t *)darktable.iop->data;
1578     if(module->cleanup_global) module->cleanup_global(module);
1579     if(module->module) g_module_close(module->module);
1580     free(darktable.iop->data);
1581     darktable.iop = g_list_delete_link(darktable.iop, darktable.iop);
1582   }
1583 }
1584 
dt_iop_set_mask_mode(dt_iop_module_t * module,int mask_mode)1585 void dt_iop_set_mask_mode(dt_iop_module_t *module, int mask_mode)
1586 {
1587   static const int key = 0;
1588   // showing raster masks doesn't make sense, one can use the original source instead. or does it?
1589   if(mask_mode & DEVELOP_MASK_ENABLED && !(mask_mode & DEVELOP_MASK_RASTER))
1590   {
1591     char *modulename = dt_history_item_get_name(module);
1592     g_hash_table_insert(module->raster_mask.source.masks, GINT_TO_POINTER(key), modulename);
1593   }
1594   else
1595   {
1596     g_hash_table_remove(module->raster_mask.source.masks, GINT_TO_POINTER(key));
1597   }
1598 }
1599 
1600 // make sure that blend_params are in sync with the iop struct
dt_iop_commit_blend_params(dt_iop_module_t * module,const dt_develop_blend_params_t * blendop_params)1601 void dt_iop_commit_blend_params(dt_iop_module_t *module, const dt_develop_blend_params_t *blendop_params)
1602 {
1603   if(module->raster_mask.sink.source)
1604     g_hash_table_remove(module->raster_mask.sink.source->raster_mask.source.users, module);
1605 
1606   memcpy(module->blend_params, blendop_params, sizeof(dt_develop_blend_params_t));
1607   if(blendop_params->blend_cst == DEVELOP_BLEND_CS_NONE)
1608   {
1609     module->blend_params->blend_cst = dt_develop_blend_default_module_blend_colorspace(module);
1610   }
1611   dt_iop_set_mask_mode(module, blendop_params->mask_mode);
1612 
1613   if(module->dev)
1614   {
1615     for(GList *iter = module->dev->iop; iter; iter = g_list_next(iter))
1616     {
1617       dt_iop_module_t *m = (dt_iop_module_t *)iter->data;
1618       if(!strcmp(m->op, blendop_params->raster_mask_source))
1619       {
1620         if(m->multi_priority == blendop_params->raster_mask_instance)
1621         {
1622           g_hash_table_insert(m->raster_mask.source.users, module, GINT_TO_POINTER(blendop_params->raster_mask_id));
1623           module->raster_mask.sink.source = m;
1624           module->raster_mask.sink.id = blendop_params->raster_mask_id;
1625           return;
1626         }
1627       }
1628     }
1629   }
1630 
1631   module->raster_mask.sink.source = NULL;
1632   module->raster_mask.sink.id = 0;
1633 }
1634 
_iop_validate_params(dt_introspection_field_t * field,dt_iop_params_t * params,gboolean report)1635 gboolean _iop_validate_params(dt_introspection_field_t *field, dt_iop_params_t *params, gboolean report)
1636 {
1637   dt_iop_params_t *p = params + field->header.offset;
1638 
1639   gboolean all_ok = TRUE;
1640 
1641   switch(field->header.type)
1642   {
1643   case DT_INTROSPECTION_TYPE_STRUCT:
1644     for(int i = 0; i < field->Struct.entries; i++)
1645     {
1646       dt_introspection_field_t *entry = field->Struct.fields[i];
1647 
1648       all_ok &= _iop_validate_params(entry, params, report);
1649     }
1650     break;
1651   case DT_INTROSPECTION_TYPE_UNION:
1652     all_ok = FALSE;
1653     for(int i = field->Union.entries - 1; i >= 0 ; i--)
1654     {
1655       dt_introspection_field_t *entry = field->Union.fields[i];
1656 
1657       if(_iop_validate_params(entry, params, report && i == 0))
1658       {
1659         all_ok = TRUE;
1660         break;
1661       }
1662     }
1663     break;
1664   case DT_INTROSPECTION_TYPE_ARRAY:
1665     if(field->Array.type == DT_INTROSPECTION_TYPE_CHAR)
1666     {
1667       if(!memchr(p, '\0', field->Array.count))
1668       {
1669         if(report)
1670           fprintf(stderr, "validation check failed in _iop_validate_params for type \"%s\"; string not null terminated.\n",
1671                           field->header.type_name);
1672         all_ok = FALSE;
1673       }
1674     }
1675     else
1676     {
1677       for(int i = 0, item_offset = 0; i < field->Array.count; i++, item_offset += field->Array.field->header.size)
1678       {
1679         if(!_iop_validate_params(field->Array.field, params + item_offset, report))
1680         {
1681           if(report)
1682             fprintf(stderr, "validation check failed in _iop_validate_params for type \"%s\", for array element \"%d\"\n",
1683                             field->header.type_name, i);
1684           all_ok = FALSE;
1685           break;
1686         }
1687       }
1688     }
1689     break;
1690   case DT_INTROSPECTION_TYPE_FLOAT:
1691     all_ok = isnan(*(float*)p) || ((*(float*)p >= field->Float.Min && *(float*)p <= field->Float.Max));
1692     break;
1693   case DT_INTROSPECTION_TYPE_INT:
1694     all_ok = (*(int*)p >= field->Int.Min && *(int*)p <= field->Int.Max);
1695     break;
1696   case DT_INTROSPECTION_TYPE_UINT:
1697     all_ok = (*(unsigned int*)p >= field->UInt.Min && *(unsigned int*)p <= field->UInt.Max);
1698     break;
1699   case DT_INTROSPECTION_TYPE_USHORT:
1700     all_ok = (*(unsigned short int*)p >= field->UShort.Min && *(unsigned short int*)p <= field->UShort.Max);
1701     break;
1702   case DT_INTROSPECTION_TYPE_INT8:
1703     all_ok = (*(uint8_t*)p >= field->Int8.Min && *(uint8_t*)p <= field->Int8.Max);
1704     break;
1705   case DT_INTROSPECTION_TYPE_CHAR:
1706     all_ok = (*(char*)p >= field->Char.Min && *(char*)p <= field->Char.Max);
1707     break;
1708   case DT_INTROSPECTION_TYPE_FLOATCOMPLEX:
1709     all_ok = creal(*(float complex*)p) >= creal(field->FloatComplex.Min) &&
1710              creal(*(float complex*)p) <= creal(field->FloatComplex.Max) &&
1711              cimag(*(float complex*)p) >= cimag(field->FloatComplex.Min) &&
1712              cimag(*(float complex*)p) <= cimag(field->FloatComplex.Max);
1713     break;
1714   case DT_INTROSPECTION_TYPE_ENUM:
1715     all_ok = FALSE;
1716     for(dt_introspection_type_enum_tuple_t *i = field->Enum.values; i && i->name; i++)
1717     {
1718       if(i->value == *(int*)p)
1719       {
1720         all_ok = TRUE;
1721         break;
1722       }
1723     }
1724     break;
1725   case DT_INTROSPECTION_TYPE_BOOL:
1726     // *(gboolean*)p
1727     break;
1728   case DT_INTROSPECTION_TYPE_OPAQUE:
1729     // TODO: special case float2
1730     break;
1731   default:
1732     fprintf(stderr, "unsupported introspection type \"%s\" encountered in _iop_validate_params (field %s)\n",
1733                     field->header.type_name, field->header.name);
1734     all_ok = FALSE;
1735     break;
1736   }
1737 
1738   if(!all_ok && report)
1739     fprintf(stderr, "validation check failed in _iop_validate_params for type \"%s\"%s%s\n",
1740                     field->header.type_name, (*field->header.name ? ", field: " : ""), field->header.name);
1741 
1742   return all_ok;
1743 }
1744 
dt_iop_commit_params(dt_iop_module_t * module,dt_iop_params_t * params,dt_develop_blend_params_t * blendop_params,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1745 void dt_iop_commit_params(dt_iop_module_t *module, dt_iop_params_t *params,
1746                           dt_develop_blend_params_t *blendop_params, dt_dev_pixelpipe_t *pipe,
1747                           dt_dev_pixelpipe_iop_t *piece)
1748 {
1749   // 1. commit params
1750 
1751   memcpy(piece->blendop_data, blendop_params, sizeof(dt_develop_blend_params_t));
1752   // this should be redundant! (but is not)
1753   dt_iop_commit_blend_params(module, blendop_params);
1754 
1755 #ifdef HAVE_OPENCL
1756   // assume process_cl is ready, commit_params can overwrite this.
1757   if(module->process_cl)
1758     piece->process_cl_ready = 1;
1759 #endif // HAVE_OPENCL
1760 
1761   // register if module allows tiling, commit_params can overwrite this.
1762   if(module->flags() & IOP_FLAGS_ALLOW_TILING)
1763     piece->process_tiling_ready = 1;
1764 
1765   if(darktable.unmuted & DT_DEBUG_PARAMS && module->so->get_introspection())
1766     _iop_validate_params(module->so->get_introspection()->field, params, TRUE);
1767 
1768   module->commit_params(module, params, pipe, piece);
1769 
1770   // 2. compute the hash only if piece is enabled
1771 
1772   piece->hash = 0;
1773 
1774   if(piece->enabled)
1775   {
1776     /* construct module params data for hash calc */
1777     int length = module->params_size;
1778     if(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) length += sizeof(dt_develop_blend_params_t);
1779     dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop, blendop_params->mask_id);
1780     length += dt_masks_group_get_hash_buffer_length(grp);
1781 
1782     char *str = malloc(length);
1783     memcpy(str, module->params, module->params_size);
1784     int pos = module->params_size;
1785     /* if module supports blend op add blend params into account */
1786     if(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
1787     {
1788       memcpy(str + module->params_size, blendop_params, sizeof(dt_develop_blend_params_t));
1789       pos += sizeof(dt_develop_blend_params_t);
1790     }
1791 
1792     /* and we add masks */
1793     dt_masks_group_get_hash_buffer(grp, str + pos);
1794 
1795     uint64_t hash = 5381;
1796     for(int i = 0; i < length; i++) hash = ((hash << 5) + hash) ^ str[i];
1797     piece->hash = hash;
1798 
1799     free(str);
1800 
1801     dt_print(DT_DEBUG_PARAMS, "[params] commit for %s in pipe %i with hash %lu\n", module->op, pipe->type, (long unsigned int)piece->hash);
1802   }
1803   // printf("commit params hash += module %s: %lu, enabled = %d\n", piece->module->op, piece->hash,
1804   // piece->enabled);
1805 }
1806 
dt_iop_gui_cleanup_module(dt_iop_module_t * module)1807 void dt_iop_gui_cleanup_module(dt_iop_module_t *module)
1808 {
1809   while(g_idle_remove_by_data(module->widget))
1810     ; // remove multiple delayed gtk_widget_queue_draw triggers
1811   module->gui_cleanup(module);
1812   dt_iop_gui_cleanup_blending(module);
1813 }
1814 
dt_iop_gui_update(dt_iop_module_t * module)1815 void dt_iop_gui_update(dt_iop_module_t *module)
1816 {
1817   ++darktable.gui->reset;
1818   if(!dt_iop_is_hidden(module))
1819   {
1820     if(module->gui_data)
1821     {
1822       if(module->params && module->gui_update) module->gui_update(module);
1823 
1824       dt_iop_gui_update_blending(module);
1825       dt_iop_gui_update_expanded(module);
1826     }
1827     _iop_gui_update_label(module);
1828     dt_iop_gui_set_enable_button(module);
1829   }
1830   --darktable.gui->reset;
1831 }
1832 
dt_iop_gui_reset(dt_iop_module_t * module)1833 void dt_iop_gui_reset(dt_iop_module_t *module)
1834 {
1835   ++darktable.gui->reset;
1836   if(module->gui_reset && !dt_iop_is_hidden(module)) module->gui_reset(module);
1837   --darktable.gui->reset;
1838 }
1839 
dt_iop_gui_reset_callback(GtkButton * button,GdkEventButton * event,dt_iop_module_t * module)1840 static void dt_iop_gui_reset_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
1841 {
1842   //Ctrl is used to apply any auto-presets to the current module
1843   //If Ctrl was not pressed, or no auto-presets were applied, reset the module parameters
1844   if(!dt_modifier_is(event->state, GDK_CONTROL_MASK) || !dt_gui_presets_autoapply_for_module(module))
1845   {
1846     // if a drawn mask is set, remove it from the list
1847     if(module->blend_params->mask_id > 0)
1848     {
1849       dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop, module->blend_params->mask_id);
1850       if(grp) dt_masks_form_remove(module, NULL, grp);
1851     }
1852     /* reset to default params */
1853     dt_iop_reload_defaults(module);
1854     dt_iop_commit_blend_params(module, module->default_blendop_params);
1855 
1856     /* reset ui to its defaults */
1857     dt_iop_gui_reset(module);
1858 
1859     /* update ui to default params*/
1860     dt_iop_gui_update(module);
1861 
1862     dt_dev_add_history_item(module->dev, module, TRUE);
1863   }
1864 
1865   // rebuild the accelerators
1866   dt_iop_connect_accels_multi(module->so);
1867 }
1868 
1869 #if !GTK_CHECK_VERSION(3, 22, 0)
_preset_popup_position(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer data)1870 static void _preset_popup_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
1871 {
1872   GtkRequisition requisition = { 0 };
1873   gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(data)), x, y);
1874   gtk_widget_get_preferred_size(GTK_WIDGET(menu), &requisition, NULL);
1875 
1876   GtkAllocation allocation;
1877   gtk_widget_get_allocation(GTK_WIDGET(data), &allocation);
1878   (*y) += allocation.height;
1879 }
1880 #endif
1881 
popup_callback(GtkButton * button,dt_iop_module_t * module)1882 static void popup_callback(GtkButton *button, dt_iop_module_t *module)
1883 {
1884   dt_gui_presets_popup_menu_show_for_module(module);
1885   gtk_widget_show_all(GTK_WIDGET(darktable.gui->presets_popup_menu));
1886 
1887   g_signal_connect(G_OBJECT(darktable.gui->presets_popup_menu), "deactivate", G_CALLBACK(_header_menu_deactivate_callback), module->header);
1888 
1889 #if GTK_CHECK_VERSION(3, 22, 0)
1890   gtk_menu_popup_at_widget(darktable.gui->presets_popup_menu, GTK_WIDGET(button), GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST, NULL);
1891 #else
1892   gtk_menu_popup(darktable.gui->presets_popup_menu, NULL, NULL, _preset_popup_position, button, 0,
1893                  gtk_get_current_event_time());
1894   gtk_menu_reposition(GTK_MENU(darktable.gui->presets_popup_menu));
1895 #endif
1896 }
1897 
1898 
dt_iop_request_focus(dt_iop_module_t * module)1899 void dt_iop_request_focus(dt_iop_module_t *module)
1900 {
1901   dt_iop_module_t *out_focus_module = darktable.develop->gui_module;
1902 
1903   if(darktable.gui->reset || (out_focus_module == module)) return;
1904 
1905   darktable.develop->gui_module = module;
1906   darktable.develop->focus_hash++;
1907 
1908   /* lets lose the focus of previous focus module*/
1909   if(out_focus_module)
1910   {
1911     if(out_focus_module->gui_focus)
1912       out_focus_module->gui_focus(out_focus_module, FALSE);
1913 
1914     dt_iop_color_picker_reset(out_focus_module, TRUE);
1915 
1916     gtk_widget_set_state_flags(dt_iop_gui_get_pluginui(out_focus_module), GTK_STATE_FLAG_NORMAL, TRUE);
1917 
1918     if(out_focus_module->operation_tags_filter()) dt_dev_invalidate_from_gui(darktable.develop);
1919 
1920     dt_iop_connect_accels_multi(out_focus_module->so);
1921     dt_accel_disconnect_locals_iop(out_focus_module);
1922 
1923     /* reset mask view */
1924     dt_masks_reset_form_gui();
1925 
1926     /* do stuff needed in the blending gui */
1927     dt_iop_gui_blending_lose_focus(out_focus_module);
1928 
1929     /* redraw the expander */
1930     gtk_widget_queue_draw(out_focus_module->expander);
1931 
1932     /* and finally collection restore hinter messages */
1933     dt_collection_hint_message(darktable.collection);
1934 
1935     // we also remove the focus css class
1936     GtkWidget *iop_w = gtk_widget_get_parent(dt_iop_gui_get_pluginui(out_focus_module));
1937     GtkStyleContext *context = gtk_widget_get_style_context(iop_w);
1938     gtk_style_context_remove_class(context, "dt_module_focus");
1939 
1940     // if the module change the image size, we update the final sizes
1941     if(out_focus_module->modify_roi_out) dt_image_update_final_size(darktable.develop->preview_pipe->output_imgid);
1942   }
1943 
1944   /* set the focus on module */
1945   if(module)
1946   {
1947     gtk_widget_set_state_flags(dt_iop_gui_get_pluginui(module), GTK_STATE_FLAG_SELECTED, TRUE);
1948 
1949     if(module->operation_tags_filter()) dt_dev_invalidate_from_gui(darktable.develop);
1950 
1951     dt_iop_connect_accels_multi(module->so);
1952     dt_accel_connect_locals_iop(module);
1953 
1954     if(module->gui_focus) module->gui_focus(module, TRUE);
1955 
1956     /* redraw the expander */
1957     gtk_widget_queue_draw(module->expander);
1958 
1959     // we also add the focus css class
1960     GtkWidget *iop_w = gtk_widget_get_parent(dt_iop_gui_get_pluginui(darktable.develop->gui_module));
1961     GtkStyleContext *context = gtk_widget_get_style_context(iop_w);
1962     gtk_style_context_add_class(context, "dt_module_focus");
1963   }
1964 
1965   /* update sticky accels window */
1966   if(darktable.view_manager->accels_window.window && darktable.view_manager->accels_window.sticky)
1967     dt_view_accels_refresh(darktable.view_manager);
1968 
1969   dt_control_change_cursor(GDK_LEFT_PTR);
1970   dt_control_queue_redraw_center();
1971 }
1972 
1973 
1974 /*
1975  * NEW EXPANDER
1976  */
1977 
dt_iop_gui_set_single_expanded(dt_iop_module_t * module,gboolean expanded)1978 static void dt_iop_gui_set_single_expanded(dt_iop_module_t *module, gboolean expanded)
1979 {
1980   if(!module->expander) return;
1981 
1982   /* update expander arrow state */
1983   dtgtk_expander_set_expanded(DTGTK_EXPANDER(module->expander), expanded);
1984 
1985   /* store expanded state of module.
1986    * we do that first, so update_expanded won't think it should be visible
1987    * and undo our changes right away. */
1988   module->expanded = expanded;
1989 
1990   /* show / hide plugin widget */
1991   if(expanded)
1992   {
1993     /* set this module to receive focus / draw events*/
1994     dt_iop_request_focus(module);
1995 
1996     /* focus the current module */
1997     for(int k = 0; k < DT_UI_CONTAINER_SIZE; k++)
1998       dt_ui_container_focus_widget(darktable.gui->ui, k, module->expander);
1999 
2000     /* redraw center, iop might have post expose */
2001     dt_control_queue_redraw_center();
2002   }
2003   else
2004   {
2005     if(module->dev->gui_module == module)
2006     {
2007       dt_iop_request_focus(NULL);
2008       dt_control_queue_redraw_center();
2009     }
2010   }
2011 
2012   char var[1024];
2013   snprintf(var, sizeof(var), "plugins/darkroom/%s/expanded", module->op);
2014   dt_conf_set_bool(var, expanded);
2015 }
2016 
dt_iop_gui_set_expanded(dt_iop_module_t * module,gboolean expanded,gboolean collapse_others)2017 void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
2018 {
2019   if(!module->expander) return;
2020   /* handle shiftclick on expander, hide all except this */
2021   if(collapse_others)
2022   {
2023     const int current_group = dt_dev_modulegroups_get_activated(module->dev);
2024     const gboolean group_only = dt_conf_get_bool("darkroom/ui/single_module_group_only");
2025 
2026     GList *iop = module->dev->iop;
2027     gboolean all_other_closed = TRUE;
2028     while(iop)
2029     {
2030       dt_iop_module_t *m = (dt_iop_module_t *)iop->data;
2031       if(m != module && (dt_iop_shown_in_group(m, current_group) || !group_only))
2032       {
2033         all_other_closed = all_other_closed && !m->expanded;
2034         dt_iop_gui_set_single_expanded(m, FALSE);
2035       }
2036 
2037       iop = g_list_next(iop);
2038     }
2039     if(all_other_closed)
2040       dt_iop_gui_set_single_expanded(module, !module->expanded);
2041     else
2042       dt_iop_gui_set_single_expanded(module, TRUE);
2043   }
2044   else
2045   {
2046     /* else just toggle */
2047     dt_iop_gui_set_single_expanded(module, expanded);
2048   }
2049 }
2050 
dt_iop_gui_update_expanded(dt_iop_module_t * module)2051 void dt_iop_gui_update_expanded(dt_iop_module_t *module)
2052 {
2053   if(!module->expander) return;
2054 
2055   const gboolean expanded = module->expanded;
2056 
2057   dtgtk_expander_set_expanded(DTGTK_EXPANDER(module->expander), expanded);
2058 }
2059 
_iop_plugin_body_button_press(GtkWidget * w,GdkEventButton * e,gpointer user_data)2060 static gboolean _iop_plugin_body_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
2061 {
2062   dt_iop_module_t *module = (dt_iop_module_t *)user_data;
2063   if(e->button == 1)
2064   {
2065     dt_iop_request_focus(module);
2066     return TRUE;
2067   }
2068   else if(e->button == 3)
2069   {
2070     dt_gui_presets_popup_menu_show_for_module(module);
2071     gtk_widget_show_all(GTK_WIDGET(darktable.gui->presets_popup_menu));
2072 
2073 #if GTK_CHECK_VERSION(3, 22, 0)
2074     gtk_menu_popup_at_pointer(darktable.gui->presets_popup_menu, (GdkEvent *)e);
2075 #else
2076     gtk_menu_popup(darktable.gui->presets_popup_menu, NULL, NULL, NULL, NULL, e->button, e->time);
2077 #endif
2078 
2079     return TRUE;
2080   }
2081   return FALSE;
2082 }
2083 
_iop_plugin_header_button_press(GtkWidget * w,GdkEventButton * e,gpointer user_data)2084 static gboolean _iop_plugin_header_button_press(GtkWidget *w, GdkEventButton *e, gpointer user_data)
2085 {
2086   if(e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS) return TRUE;
2087 
2088   dt_iop_module_t *module = (dt_iop_module_t *)user_data;
2089 
2090   if(e->button == 1)
2091   {
2092     if(dt_modifier_is(e->state, GDK_SHIFT_MASK | GDK_CONTROL_MASK))
2093     {
2094       GtkBox *container = dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER);
2095       g_object_set_data(G_OBJECT(container), "source_data", user_data);
2096       return FALSE;
2097     }
2098     else if(dt_modifier_is(e->state, GDK_CONTROL_MASK))
2099     {
2100       _iop_gui_rename_module(module);
2101       return TRUE;
2102     }
2103     else
2104     {
2105       // make gtk scroll to the module once it updated its allocation size
2106       if(dt_conf_get_bool("darkroom/ui/scroll_to_module"))
2107         darktable.gui->scroll_to[1] = module->expander;
2108 
2109       const gboolean collapse_others = !dt_conf_get_bool("darkroom/ui/single_module") != (!dt_modifier_is(e->state, GDK_SHIFT_MASK));
2110       dt_iop_gui_set_expanded(module, !module->expanded, collapse_others);
2111 
2112       // rebuild the accelerators
2113       dt_iop_connect_accels_multi(module->so);
2114 
2115       //used to take focus away from module search text input box when module selected
2116       gtk_widget_grab_focus(dt_ui_center(darktable.gui->ui));
2117 
2118       return TRUE;
2119     }
2120   }
2121   else if(e->button == 3)
2122   {
2123     dt_gui_presets_popup_menu_show_for_module(module);
2124     gtk_widget_show_all(GTK_WIDGET(darktable.gui->presets_popup_menu));
2125 
2126     g_signal_connect(G_OBJECT(darktable.gui->presets_popup_menu), "deactivate", G_CALLBACK(_header_menu_deactivate_callback), module->header);
2127 
2128 #if GTK_CHECK_VERSION(3, 22, 0)
2129     gtk_menu_popup_at_pointer(darktable.gui->presets_popup_menu, (GdkEvent *)e);
2130 #else
2131     gtk_menu_popup(darktable.gui->presets_popup_menu, NULL, NULL, NULL, NULL, e->button, e->time);
2132 #endif
2133 
2134     return TRUE;
2135   }
2136   return FALSE;
2137 }
2138 
header_size_callback(GtkWidget * widget,GdkRectangle * allocation,GtkWidget * header)2139 static void header_size_callback(GtkWidget *widget, GdkRectangle *allocation, GtkWidget *header)
2140 {
2141   gchar *config = dt_conf_get_string("darkroom/ui/hide_header_buttons");
2142 
2143   GList *children = gtk_container_get_children(GTK_CONTAINER(header));
2144 
2145   const gint panel_trigger_width = 250;
2146 
2147   GList *button = children;
2148   GtkRequisition button_size;
2149   gtk_widget_show(GTK_WIDGET(button->data));
2150   gtk_widget_get_preferred_size(GTK_WIDGET(button->data), &button_size, NULL);
2151 
2152   int num_buttons = 0;
2153   for(button = g_list_last(children);
2154       button && GTK_IS_BUTTON(button->data);
2155       button = g_list_previous(button)) num_buttons++;
2156 
2157   gboolean hide_all = (allocation->width == 1);
2158   int num_to_unhide = (allocation->width - 2) / button_size.width;
2159   double opacity_leftmost = num_to_unhide > 0 ? 1.0 : (double) allocation->width / button_size.width;
2160   double opacity_others = 1.0;
2161 
2162   if(g_strcmp0(config, "glide")) // glide uses all defaults above
2163   {
2164     // these all (un)hide all buttons at the same time
2165     if(num_to_unhide < num_buttons) num_to_unhide = 0;
2166 
2167     if(!g_strcmp0(config, "smooth"))
2168     {
2169       opacity_others = opacity_leftmost;
2170     }
2171     else
2172     {
2173       if(!g_strcmp0(config, "fit"))
2174       {
2175         opacity_leftmost = 1.0;
2176       }
2177       else
2178       {
2179         GdkRectangle total_alloc;
2180         gtk_widget_get_allocation(header, &total_alloc);
2181 
2182         if(!g_strcmp0(config, "auto"))
2183         {
2184           opacity_leftmost = 1.0;
2185           if(total_alloc.width < panel_trigger_width) hide_all = TRUE;
2186         }
2187         else if(!g_strcmp0(config, "fade"))
2188         {
2189           opacity_leftmost = opacity_others = (total_alloc.width - panel_trigger_width) / 100.;
2190         }
2191         else
2192         {
2193           fprintf(stderr, "unknown darkroom/ui/hide_header_buttons option %s\n", config);
2194         }
2195       }
2196     }
2197   }
2198 
2199   GList *prev_button = NULL;
2200 
2201   for(button = g_list_last(children);
2202       button && GTK_IS_BUTTON(button->data);
2203       button = g_list_previous(button))
2204   {
2205     GtkWidget *b = GTK_WIDGET(button->data);
2206 
2207     if(!gtk_widget_get_visible(b))
2208     {
2209       if(num_to_unhide == 0) break;
2210       --num_to_unhide;
2211     }
2212 
2213     gtk_widget_set_visible(b, !hide_all);
2214     gtk_widget_set_opacity(b, opacity_others);
2215 
2216     prev_button = button;
2217   }
2218   if(prev_button && num_to_unhide == 0)
2219     gtk_widget_set_opacity(GTK_WIDGET(prev_button->data), opacity_leftmost);
2220 
2221   g_list_free(children);
2222   g_free(config);
2223 
2224   GtkAllocation header_allocation;
2225   gtk_widget_get_allocation(header, &header_allocation);
2226   if(header_allocation.width > 1) gtk_widget_size_allocate(header, &header_allocation);
2227 }
2228 
dt_iop_show_hide_header_buttons(GtkWidget * header,GdkEventCrossing * event,gboolean show_buttons,gboolean always_hide)2229 gboolean dt_iop_show_hide_header_buttons(GtkWidget *header, GdkEventCrossing *event, gboolean show_buttons, gboolean always_hide)
2230 {
2231   // check if Entry widget for module name edit exists
2232   GtkWidget *focused = gtk_container_get_focus_child(GTK_CONTAINER(header));
2233   if(focused && GTK_IS_ENTRY(focused)) return TRUE;
2234 
2235   if(event && (darktable.develop->darkroom_skip_mouse_events ||
2236      event->detail == GDK_NOTIFY_INFERIOR ||
2237      event->mode != GDK_CROSSING_NORMAL)) return TRUE;
2238 
2239   gchar *config = dt_conf_get_string("darkroom/ui/hide_header_buttons");
2240 
2241   gboolean dynamic = FALSE;
2242   double opacity = 1.0;
2243   if(!g_strcmp0(config, "always"))
2244   {
2245     show_buttons = TRUE;
2246   }
2247   else if(!g_strcmp0(config, "dim"))
2248   {
2249     if(!show_buttons) opacity = 0.3;
2250     show_buttons = TRUE;
2251   }
2252   else if(!g_strcmp0(config, "active"))
2253     ;
2254   else
2255     dynamic = TRUE;
2256 
2257   g_free(config);
2258 
2259   GList *children = gtk_container_get_children(GTK_CONTAINER(header));
2260 
2261   GList *button;
2262   for(button = g_list_last(children);
2263       button && GTK_IS_BUTTON(button->data);
2264       button = g_list_previous(button))
2265   {
2266     gtk_widget_set_no_show_all(GTK_WIDGET(button->data), TRUE);
2267     gtk_widget_set_visible(GTK_WIDGET(button->data), show_buttons && !always_hide);
2268     gtk_widget_set_opacity(GTK_WIDGET(button->data), opacity);
2269   }
2270   if(GTK_IS_DRAWING_AREA(button->data))
2271   {
2272     // temporarily or permanently (de)activate width trigger widget
2273     if(dynamic)
2274       gtk_widget_set_visible(GTK_WIDGET(button->data), !show_buttons && !always_hide);
2275     else
2276       gtk_widget_destroy(GTK_WIDGET(button->data));
2277   }
2278   else
2279   {
2280     if(dynamic)
2281     {
2282       GtkWidget *space = gtk_drawing_area_new();
2283       gtk_box_pack_end(GTK_BOX(header), space, TRUE, TRUE, 0);
2284       gtk_widget_show(space);
2285       g_signal_connect(G_OBJECT(space), "size-allocate", G_CALLBACK(header_size_callback), header);
2286     }
2287   }
2288 
2289   g_list_free(children);
2290 
2291   if(dynamic && !show_buttons && !always_hide)
2292   {
2293     GdkRectangle fake_allocation = {.width = UINT16_MAX};
2294     header_size_callback(NULL, &fake_allocation, header);
2295   }
2296 
2297   return TRUE;
2298 }
2299 
_display_mask_indicator_callback(GtkToggleButton * bt,dt_iop_module_t * module)2300 static void _display_mask_indicator_callback(GtkToggleButton *bt, dt_iop_module_t *module)
2301 {
2302   if(darktable.gui->reset) return;
2303 
2304   const gboolean is_active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bt));
2305   const dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)module->blend_data;
2306 
2307   module->request_mask_display &= ~DT_DEV_PIXELPIPE_DISPLAY_MASK;
2308   module->request_mask_display |= (is_active ? DT_DEV_PIXELPIPE_DISPLAY_MASK : 0);
2309 
2310   // set the module show mask button too
2311   if(bd->showmask)
2312     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->showmask), is_active);
2313 
2314   dt_iop_request_focus(module);
2315   dt_iop_refresh_center(module);
2316 }
2317 
add_remove_mask_indicator(dt_iop_module_t * module,gboolean add)2318 void add_remove_mask_indicator(dt_iop_module_t *module, gboolean add)
2319 {
2320   const gboolean show = add && dt_conf_get_bool("darkroom/ui/show_mask_indicator");
2321   const gboolean raster = module->blend_params->mask_mode & DEVELOP_MASK_RASTER;
2322 
2323   if(module->mask_indicator)
2324   {
2325     if(!show)
2326     {
2327       gtk_widget_destroy(module->mask_indicator);
2328       module->mask_indicator = NULL;
2329       dt_iop_show_hide_header_buttons(module->header, NULL, FALSE, FALSE);
2330     }
2331     else
2332       gtk_widget_set_sensitive(module->mask_indicator, !raster && module->enabled);
2333   }
2334   else if(show)
2335   {
2336     module->mask_indicator = dtgtk_togglebutton_new(dtgtk_cairo_paint_showmask,
2337                                                     CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, NULL);
2338     gtk_widget_set_name(module->mask_indicator, "module-mask-indicator");
2339     g_signal_connect(G_OBJECT(module->mask_indicator), "toggled",
2340                      G_CALLBACK(_display_mask_indicator_callback), module);
2341     gtk_widget_set_sensitive(module->mask_indicator, !raster && module->enabled);
2342     gtk_box_pack_end(GTK_BOX(module->header), module->mask_indicator, FALSE, FALSE, 0);
2343 
2344     // in dynamic modes, we need to put the mask indicator after the drawing area
2345     GList *children = gtk_container_get_children(GTK_CONTAINER(module->header));
2346     GList *child = g_list_last(children);
2347 
2348     for(child = g_list_last(children); child && GTK_IS_BUTTON(child->data); child = g_list_previous(child));
2349 
2350     if(GTK_IS_DRAWING_AREA(child->data))
2351     {
2352       GValue position = G_VALUE_INIT;
2353       g_value_init (&position, G_TYPE_INT);
2354       gtk_container_child_get_property(GTK_CONTAINER(module->header), child->data ,"position", &position);
2355       gtk_box_reorder_child(GTK_BOX(module->header), module->mask_indicator, g_value_get_int(&position));
2356     }
2357     g_list_free(children);
2358 
2359     dt_iop_show_hide_header_buttons(module->header, NULL, FALSE, FALSE);
2360   }
2361 
2362   if(module->mask_indicator)
2363   {
2364     gchar *type = _("unknown mask");
2365     gchar *tooltip;
2366     const uint32_t mm = module->blend_params->mask_mode;
2367     if((mm & DEVELOP_MASK_MASK) && (mm & DEVELOP_MASK_CONDITIONAL))
2368       type=_("drawn + parametric mask");
2369     else if(mm & DEVELOP_MASK_MASK)
2370       type=_("drawn mask");
2371     else if(mm & DEVELOP_MASK_CONDITIONAL)
2372       type=_("parametric mask");
2373     else if(mm & DEVELOP_MASK_RASTER)
2374       type=_("raster mask");
2375     else
2376       fprintf(stderr, "unknown mask mode '%d' in module '%s'", mm, module->op);
2377     gchar *str1 = g_strconcat(_("this module has a"), " ", type, NULL);
2378     if(raster)
2379       tooltip = g_strdup(str1);
2380     else
2381       tooltip = g_strconcat(str1, "\n", _("click to display (module must be activated first)"), NULL);
2382     gtk_widget_set_tooltip_text(module->mask_indicator, tooltip);
2383     g_free(str1);
2384     g_free(tooltip);
2385   }
2386 }
2387 
dt_iop_gui_set_expander(dt_iop_module_t * module)2388 void dt_iop_gui_set_expander(dt_iop_module_t *module)
2389 {
2390   char tooltip[512];
2391 
2392   GtkWidget *header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2393   gtk_widget_set_name(GTK_WIDGET(header), "module-header");
2394 
2395   GtkWidget *iopw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2396   GtkWidget *expander = dtgtk_expander_new(header, iopw);
2397 
2398   GtkWidget *header_evb = dtgtk_expander_get_header_event_box(DTGTK_EXPANDER(expander));
2399   GtkWidget *body_evb = dtgtk_expander_get_body_event_box(DTGTK_EXPANDER(expander));
2400   GtkWidget *pluginui_frame = dtgtk_expander_get_frame(DTGTK_EXPANDER(expander));
2401 
2402   gtk_widget_set_name(pluginui_frame, "iop-plugin-ui");
2403 
2404   module->header = header;
2405 
2406   /* setup the header box */
2407   g_signal_connect(G_OBJECT(header_evb), "button-press-event", G_CALLBACK(_iop_plugin_header_button_press), module);
2408   gtk_widget_add_events(header_evb, GDK_POINTER_MOTION_MASK);
2409   g_signal_connect(G_OBJECT(header_evb), "enter-notify-event", G_CALLBACK(_header_motion_notify_show_callback), header);
2410   g_signal_connect(G_OBJECT(header_evb), "leave-notify-event", G_CALLBACK(_header_motion_notify_hide_callback), header);
2411 
2412   /* connect mouse button callbacks for focus and presets */
2413   g_signal_connect(G_OBJECT(body_evb), "button-press-event", G_CALLBACK(_iop_plugin_body_button_press), module);
2414   gtk_widget_add_events(body_evb, GDK_POINTER_MOTION_MASK);
2415   g_signal_connect(G_OBJECT(body_evb), "enter-notify-event", G_CALLBACK(_header_motion_notify_show_callback), header);
2416   g_signal_connect(G_OBJECT(body_evb), "leave-notify-event", G_CALLBACK(_header_motion_notify_hide_callback), header);
2417 
2418   /*
2419    * initialize the header widgets
2420    */
2421   GtkWidget *hw[IOP_MODULE_LAST] = { NULL };
2422 
2423   /* init empty place for icon, this is then set in CSS if needed */
2424   char w_name[256] = { 0 };
2425   snprintf(w_name, sizeof(w_name), "iop-panel-icon-%s", module->op);
2426   hw[IOP_MODULE_ICON] = gtk_label_new("");
2427   gtk_widget_set_name(GTK_WIDGET(hw[IOP_MODULE_ICON]), w_name);
2428   gtk_widget_set_valign(GTK_WIDGET(hw[IOP_MODULE_ICON]), GTK_ALIGN_CENTER);
2429 
2430   /* add module label */
2431   hw[IOP_MODULE_LABEL] = gtk_label_new("");
2432   _iop_panel_label(hw[IOP_MODULE_LABEL], module);
2433 
2434   /* add multi instances menu button */
2435   hw[IOP_MODULE_INSTANCE] = dtgtk_button_new(dtgtk_cairo_paint_multiinstance, CPF_STYLE_FLAT, NULL);
2436   module->multimenu_button = GTK_WIDGET(hw[IOP_MODULE_INSTANCE]);
2437   gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_INSTANCE]),
2438                               _("multiple instance actions\nright-click creates new instance"));
2439   g_signal_connect(G_OBJECT(hw[IOP_MODULE_INSTANCE]), "button-press-event", G_CALLBACK(dt_iop_gui_multiinstance_callback),
2440                    module);
2441 
2442   gtk_widget_set_name(GTK_WIDGET(hw[IOP_MODULE_INSTANCE]), "module-instance-button");
2443 
2444   dt_gui_add_help_link(expander, dt_get_help_url(module->op));
2445 
2446   /* add reset button */
2447   hw[IOP_MODULE_RESET] = dtgtk_button_new(dtgtk_cairo_paint_reset, CPF_STYLE_FLAT, NULL);
2448   module->reset_button = GTK_WIDGET(hw[IOP_MODULE_RESET]);
2449   gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_RESET]), _("reset parameters\nctrl+click to reapply any automatic presets"));
2450   g_signal_connect(G_OBJECT(hw[IOP_MODULE_RESET]), "button-press-event", G_CALLBACK(dt_iop_gui_reset_callback), module);
2451   gtk_widget_set_name(GTK_WIDGET(hw[IOP_MODULE_RESET]), "module-reset-button");
2452 
2453   /* add preset button if module has implementation */
2454   hw[IOP_MODULE_PRESETS] = dtgtk_button_new(dtgtk_cairo_paint_presets, CPF_STYLE_FLAT, NULL);
2455   module->presets_button = GTK_WIDGET(hw[IOP_MODULE_PRESETS]);
2456   if (module->flags() & IOP_FLAGS_ONE_INSTANCE)
2457     gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_PRESETS]), _("presets"));
2458   else
2459     gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_PRESETS]), _("presets\nright-click to apply on new instance"));
2460   g_signal_connect(G_OBJECT(hw[IOP_MODULE_PRESETS]), "clicked", G_CALLBACK(popup_callback), module);
2461   gtk_widget_set_name(GTK_WIDGET(hw[IOP_MODULE_PRESETS]), "module-preset-button");
2462 
2463   /* add enabled button */
2464   hw[IOP_MODULE_SWITCH] = dtgtk_togglebutton_new(dtgtk_cairo_paint_switch,
2465                                                  CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, module);
2466   dt_iop_gui_set_enable_button_icon(hw[IOP_MODULE_SWITCH], module);
2467 
2468   gchar *module_label = dt_history_item_get_name(module);
2469   snprintf(tooltip, sizeof(tooltip), module->enabled ? _("%s is switched on") : _("%s is switched off"),
2470            module_label);
2471   g_free(module_label);
2472   gtk_widget_set_tooltip_text(GTK_WIDGET(hw[IOP_MODULE_SWITCH]), tooltip);
2473   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(hw[IOP_MODULE_SWITCH]), module->enabled);
2474   g_signal_connect(G_OBJECT(hw[IOP_MODULE_SWITCH]), "toggled", G_CALLBACK(dt_iop_gui_off_callback), module);
2475   g_signal_connect(G_OBJECT(hw[IOP_MODULE_SWITCH]), "button-press-event", G_CALLBACK(dt_iop_gui_off_button_press), module);
2476   module->off = DTGTK_TOGGLEBUTTON(hw[IOP_MODULE_SWITCH]);
2477   gtk_widget_set_sensitive(GTK_WIDGET(hw[IOP_MODULE_SWITCH]), !module->hide_enable_button);
2478 
2479   /* reorder header, for now, iop are always in the right panel */
2480   for(int i = 0; i <= IOP_MODULE_LABEL; i++)
2481     if(hw[i]) gtk_box_pack_start(GTK_BOX(header), hw[i], FALSE, FALSE, 0);
2482   for(int i = IOP_MODULE_LAST - 1; i > IOP_MODULE_LABEL; i--)
2483     if(hw[i]) gtk_box_pack_end(GTK_BOX(header), hw[i], FALSE, FALSE, 0);
2484 
2485   dt_gui_add_help_link(header, dt_get_help_url("module_interacting"));
2486 
2487   gtk_widget_set_halign(hw[IOP_MODULE_LABEL], GTK_ALIGN_START);
2488   gtk_widget_set_halign(hw[IOP_MODULE_INSTANCE], GTK_ALIGN_END);
2489 
2490   // show deprecated message if any
2491   if(module->deprecated_msg())
2492   {
2493     GtkWidget *lb = gtk_label_new(module->deprecated_msg());
2494     gtk_label_set_line_wrap(GTK_LABEL(lb), TRUE);
2495     gtk_label_set_xalign(GTK_LABEL(lb), 0.0);
2496     gtk_widget_set_name(lb, "iop-plugin-deprecated");
2497     gtk_box_pack_start(GTK_BOX(iopw), lb, TRUE, TRUE, 0);
2498     gtk_widget_show(lb);
2499   }
2500 
2501   /* add the blending ui if supported */
2502   gtk_box_pack_start(GTK_BOX(iopw), module->widget, TRUE, TRUE, 0);
2503   dt_iop_gui_init_blending(iopw, module);
2504   gtk_widget_set_name(module->widget, "iop-plugin-ui-main");
2505   dt_gui_add_help_link(module->widget, dt_get_help_url(module->op));
2506   gtk_widget_hide(iopw);
2507 
2508   module->expander = expander;
2509 
2510   /* update header */
2511   _iop_gui_update_header(module);
2512 
2513   gtk_widget_set_hexpand(module->widget, FALSE);
2514   gtk_widget_set_vexpand(module->widget, FALSE);
2515 
2516   /* connect accelerators */
2517   dt_iop_connect_common_accels(module);
2518   if(module->connect_key_accels) module->connect_key_accels(module);
2519 
2520   dt_ui_container_add_widget(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER, expander);
2521   dt_iop_show_hide_header_buttons(header, NULL, FALSE, FALSE);
2522 }
2523 
dt_iop_gui_get_widget(dt_iop_module_t * module)2524 GtkWidget *dt_iop_gui_get_widget(dt_iop_module_t *module)
2525 {
2526   return dtgtk_expander_get_body(DTGTK_EXPANDER(module->expander));
2527 }
2528 
dt_iop_gui_get_pluginui(dt_iop_module_t * module)2529 GtkWidget *dt_iop_gui_get_pluginui(dt_iop_module_t *module)
2530 {
2531   // return gtkframe (pluginui_frame)
2532   return dtgtk_expander_get_frame(DTGTK_EXPANDER(module->expander));
2533 }
2534 
dt_iop_breakpoint(struct dt_develop_t * dev,struct dt_dev_pixelpipe_t * pipe)2535 int dt_iop_breakpoint(struct dt_develop_t *dev, struct dt_dev_pixelpipe_t *pipe)
2536 {
2537   if(pipe != dev->preview_pipe && pipe != dev->preview2_pipe) sched_yield();
2538   if(pipe != dev->preview_pipe && pipe != dev->preview2_pipe && pipe->changed == DT_DEV_PIPE_ZOOMED) return 1;
2539   if((pipe->changed != DT_DEV_PIPE_UNCHANGED && pipe->changed != DT_DEV_PIPE_ZOOMED) || dev->gui_leaving)
2540     return 1;
2541   return 0;
2542 }
2543 
dt_iop_nap(int32_t usec)2544 void dt_iop_nap(int32_t usec)
2545 {
2546   if(usec <= 0) return;
2547 
2548   // relinquish processor
2549   sched_yield();
2550 
2551   // additionally wait the given amount of time
2552   g_usleep(usec);
2553 }
2554 
dt_iop_get_colorout_module(void)2555 dt_iop_module_t *dt_iop_get_colorout_module(void)
2556 {
2557   return dt_iop_get_module_from_list(darktable.develop->iop, "colorout");
2558 }
2559 
dt_iop_get_module_from_list(GList * iop_list,const char * op)2560 dt_iop_module_t *dt_iop_get_module_from_list(GList *iop_list, const char *op)
2561 {
2562   dt_iop_module_t *result = NULL;
2563 
2564   for(GList *modules = iop_list; modules; modules = g_list_next(modules))
2565   {
2566     dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
2567     if(strcmp(mod->op, op) == 0)
2568     {
2569       result = mod;
2570       break;
2571     }
2572   }
2573 
2574   return result;
2575 }
2576 
dt_iop_get_module(const char * op)2577 dt_iop_module_t *dt_iop_get_module(const char *op)
2578 {
2579   return dt_iop_get_module_from_list(darktable.develop->iop, op);
2580 }
2581 
get_module_flags(const char * op)2582 int get_module_flags(const char *op)
2583 {
2584   GList *modules = darktable.iop;
2585   while(modules)
2586   {
2587     dt_iop_module_so_t *module = (dt_iop_module_so_t *)modules->data;
2588     if(!strcmp(module->op, op)) return module->flags();
2589     modules = g_list_next(modules);
2590   }
2591   return 0;
2592 }
2593 
show_module_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2594 static gboolean show_module_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2595                                      GdkModifierType modifier, gpointer data)
2596 
2597 {
2598   dt_iop_module_t *module = (dt_iop_module_t *)data;
2599 
2600   // Showing the module, if it isn't already visible
2601   if(module->so->state == dt_iop_state_HIDDEN)
2602   {
2603     dt_iop_gui_set_state(module, dt_iop_state_ACTIVE);
2604   }
2605 
2606   const uint32_t current_group = dt_dev_modulegroups_get(module->dev);
2607 
2608   if(!dt_iop_shown_in_group(module, current_group))
2609   {
2610     dt_dev_modulegroups_switch(darktable.develop, module);
2611   }
2612   else
2613   {
2614     dt_dev_modulegroups_set(darktable.develop, current_group);
2615   }
2616 
2617   dt_iop_gui_set_expanded(module, !module->expanded, dt_conf_get_bool("darkroom/ui/single_module"));
2618   if(module->expanded)
2619   {
2620     dt_iop_request_focus(module);
2621   }
2622 
2623   dt_iop_connect_accels_multi(module->so);
2624 
2625   return TRUE;
2626 }
2627 
request_module_focus_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2628 static gboolean request_module_focus_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2629                                      GdkModifierType modifier, gpointer data)
2630 
2631 {
2632   dt_iop_module_t * module = (dt_iop_module_t *)data;
2633   dt_iop_request_focus(darktable.develop->gui_module == module ? NULL : module);
2634   return TRUE;
2635 }
2636 
enable_module_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2637 static gboolean enable_module_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2638                                        GdkModifierType modifier, gpointer data)
2639 
2640 {
2641   dt_iop_module_t *module = (dt_iop_module_t *)data;
2642 
2643   //cannot toggle module if there's no enable button
2644   if(module->hide_enable_button) return TRUE;
2645 
2646   gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(module->off));
2647   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), !active);
2648 
2649   if(dt_conf_get_bool("darkroom/ui/scroll_to_module"))
2650       darktable.gui->scroll_to[1] = module->expander;
2651 
2652   if(dt_conf_get_bool("darkroom/ui/activate_expand"))
2653     dt_iop_gui_set_expanded(module, !active, dt_conf_get_bool("darkroom/ui/single_module"));
2654 
2655   dt_iop_request_focus(module);
2656 
2657   // rebuild the accelerators
2658   dt_iop_connect_accels_multi(module->so);
2659 
2660   return TRUE;
2661 }
2662 
dt_iop_connect_common_accels(dt_iop_module_t * module)2663 void dt_iop_connect_common_accels(dt_iop_module_t *module)
2664 {
2665   GClosure *closure = NULL;
2666   if(module->flags() & IOP_FLAGS_DEPRECATED) return;
2667   // Connecting the (optional) module show accelerator
2668   closure = g_cclosure_new(G_CALLBACK(show_module_callback), module, NULL);
2669   dt_accel_connect_iop(module, "show module", closure);
2670 
2671   // Connecting the (optional) module gui focus accelerator
2672   closure = g_cclosure_new(G_CALLBACK(request_module_focus_callback), module, NULL);
2673   dt_accel_connect_iop(module, "focus module", closure);
2674 
2675   // Connecting the (optional) module switch accelerator
2676   closure = g_cclosure_new(G_CALLBACK(enable_module_callback), module, NULL);
2677   dt_accel_connect_iop(module, "enable module", closure);
2678 
2679   // Connecting the reset and preset buttons
2680   if(module->reset_button)
2681     dt_accel_connect_button_iop(module, "reset module parameters", module->reset_button);
2682   if(module->presets_button) dt_accel_connect_button_iop(module, "show preset menu", module->presets_button);
2683 
2684   if(module->fusion_slider) dt_accel_connect_slider_iop(module, "fusion", module->fusion_slider);
2685 
2686   sqlite3_stmt *stmt;
2687   // don't know for which image. show all we got:
2688   DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
2689                               "SELECT name FROM data.presets WHERE operation=?1 ORDER BY writeprotect DESC, rowid",
2690                               -1, &stmt, NULL);
2691   DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, module->op, -1, SQLITE_TRANSIENT);
2692   while(sqlite3_step(stmt) == SQLITE_ROW)
2693   {
2694     dt_accel_connect_preset_iop(module, (char *)sqlite3_column_text(stmt, 0));
2695   }
2696   sqlite3_finalize(stmt);
2697 }
2698 
2699 // to be called before issuing any query based on memory.darktable_iop_names
dt_iop_set_darktable_iop_table()2700 void dt_iop_set_darktable_iop_table()
2701 {
2702   sqlite3_stmt *stmt;
2703   gchar *module_list = NULL;
2704   for(GList *iop = darktable.iop; iop; iop = g_list_next(iop))
2705   {
2706     dt_iop_module_so_t *module = (dt_iop_module_so_t *)iop->data;
2707     module_list = dt_util_dstrcat(module_list, "(\"%s\",\"%s\"),", module->op, module->name());
2708   }
2709 
2710   if(module_list)
2711   {
2712     module_list[strlen(module_list) - 1] = '\0';
2713     char *query = dt_util_dstrcat(NULL, "INSERT INTO memory.darktable_iop_names (operation, name) VALUES %s", module_list);
2714     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
2715     sqlite3_step(stmt);
2716     sqlite3_finalize(stmt);
2717     g_free(query);
2718     g_free(module_list);
2719   }
2720 }
2721 
dt_iop_get_localized_name(const gchar * op)2722 gchar *dt_iop_get_localized_name(const gchar *op)
2723 {
2724   // Prepare mapping op -> localized name
2725   static GHashTable *module_names = NULL;
2726   if(module_names == NULL)
2727   {
2728     module_names = g_hash_table_new(g_str_hash, g_str_equal);
2729     for(GList *iop = darktable.iop; iop; iop = g_list_next(iop))
2730     {
2731       dt_iop_module_so_t *module = (dt_iop_module_so_t *)iop->data;
2732       g_hash_table_insert(module_names, module->op, g_strdup(module->name()));
2733     }
2734   }
2735   if(op != NULL)
2736   {
2737     return (gchar *)g_hash_table_lookup(module_names, op);
2738   }
2739   else {
2740     return _("ERROR");
2741   }
2742 }
2743 
dt_iop_get_localized_aliases(const gchar * op)2744 gchar *dt_iop_get_localized_aliases(const gchar *op)
2745 {
2746   // Prepare mapping op -> localized name
2747   static GHashTable *module_aliases = NULL;
2748   if(module_aliases == NULL)
2749   {
2750     module_aliases = g_hash_table_new(g_str_hash, g_str_equal);
2751     for(GList *iop = darktable.iop; iop; iop = g_list_next(iop))
2752     {
2753       dt_iop_module_so_t *module = (dt_iop_module_so_t *)iop->data;
2754       g_hash_table_insert(module_aliases, module->op, g_strdup(module->aliases()));
2755     }
2756   }
2757   if(op != NULL)
2758   {
2759     return (gchar *)g_hash_table_lookup(module_aliases, op);
2760   }
2761   else {
2762     return _("ERROR");
2763   }
2764 }
2765 
dt_iop_so_gui_set_state(dt_iop_module_so_t * module,dt_iop_module_state_t state)2766 void dt_iop_so_gui_set_state(dt_iop_module_so_t *module, dt_iop_module_state_t state)
2767 {
2768   module->state = state;
2769 
2770   char option[1024];
2771   GList *mods = NULL;
2772   if(state == dt_iop_state_HIDDEN)
2773   {
2774     for(mods = darktable.develop->iop; mods; mods = g_list_next(mods))
2775     {
2776       dt_iop_module_t *mod = (dt_iop_module_t *)mods->data;
2777       if(mod->so == module && mod->expander) gtk_widget_hide(GTK_WIDGET(mod->expander));
2778     }
2779 
2780     snprintf(option, sizeof(option), "plugins/darkroom/%s/visible", module->op);
2781     dt_conf_set_bool(option, FALSE);
2782     snprintf(option, sizeof(option), "plugins/darkroom/%s/favorite", module->op);
2783     dt_conf_set_bool(option, FALSE);
2784   }
2785   else if(state == dt_iop_state_ACTIVE)
2786   {
2787     if(!darktable.gui->reset)
2788     {
2789       int once = 0;
2790 
2791       for(mods = darktable.develop->iop; mods; mods = g_list_next(mods))
2792       {
2793         dt_iop_module_t *mod = (dt_iop_module_t *)mods->data;
2794         if(mod->so == module && mod->expander)
2795         {
2796           gtk_widget_show(GTK_WIDGET(mod->expander));
2797           if(!once)
2798           {
2799             dt_dev_modulegroups_switch(darktable.develop, mod);
2800             once = 1;
2801           }
2802         }
2803       }
2804     }
2805 
2806     /* module is shown lets set conf values */
2807     snprintf(option, sizeof(option), "plugins/darkroom/%s/visible", module->op);
2808     dt_conf_set_bool(option, TRUE);
2809     snprintf(option, sizeof(option), "plugins/darkroom/%s/favorite", module->op);
2810     dt_conf_set_bool(option, FALSE);
2811   }
2812   else if(state == dt_iop_state_FAVORITE)
2813   {
2814     for(mods = darktable.develop->iop; mods; mods = g_list_next(mods))
2815     {
2816       dt_iop_module_t *mod = (dt_iop_module_t *)mods->data;
2817       if(mod->so == module && mod->expander) gtk_widget_show(GTK_WIDGET(mod->expander));
2818     }
2819 
2820     /* module is shown and favorite lets set conf values */
2821     snprintf(option, sizeof(option), "plugins/darkroom/%s/visible", module->op);
2822     dt_conf_set_bool(option, TRUE);
2823     snprintf(option, sizeof(option), "plugins/darkroom/%s/favorite", module->op);
2824     dt_conf_set_bool(option, TRUE);
2825   }
2826 }
2827 
dt_iop_gui_set_state(dt_iop_module_t * module,dt_iop_module_state_t state)2828 void dt_iop_gui_set_state(dt_iop_module_t *module, dt_iop_module_state_t state)
2829 {
2830   dt_iop_so_gui_set_state(module->so, state);
2831 }
2832 
dt_iop_update_multi_priority(dt_iop_module_t * module,int new_priority)2833 void dt_iop_update_multi_priority(dt_iop_module_t *module, int new_priority)
2834 {
2835   GHashTableIter iter;
2836   gpointer key, value;
2837 
2838   g_hash_table_iter_init(&iter, module->raster_mask.source.users);
2839   while(g_hash_table_iter_next(&iter, &key, &value))
2840   {
2841     dt_iop_module_t *sink_module = (dt_iop_module_t *)key;
2842 
2843     sink_module->blend_params->raster_mask_instance = new_priority;
2844 
2845     // also fix history entries
2846     for(GList *hiter = module->dev->history; hiter; hiter = g_list_next(hiter))
2847     {
2848       dt_dev_history_item_t *hist = (dt_dev_history_item_t *)hiter->data;
2849       if(hist->module == sink_module)
2850         hist->blend_params->raster_mask_instance = new_priority;
2851     }
2852   }
2853 
2854   module->multi_priority = new_priority;
2855 }
2856 
dt_iop_is_raster_mask_used(dt_iop_module_t * module,int id)2857 gboolean dt_iop_is_raster_mask_used(dt_iop_module_t *module, int id)
2858 {
2859   GHashTableIter iter;
2860   gpointer key, value;
2861 
2862   g_hash_table_iter_init(&iter, module->raster_mask.source.users);
2863   while(g_hash_table_iter_next(&iter, &key, &value))
2864   {
2865     if(GPOINTER_TO_INT(value) == id)
2866       return TRUE;
2867   }
2868   return FALSE;
2869 }
2870 
dt_iop_get_module_by_op_priority(GList * modules,const char * operation,const int multi_priority)2871 dt_iop_module_t *dt_iop_get_module_by_op_priority(GList *modules, const char *operation, const int multi_priority)
2872 {
2873   dt_iop_module_t *mod_ret = NULL;
2874 
2875   for(GList *m = modules; m; m = g_list_next(m))
2876   {
2877     dt_iop_module_t *mod = (dt_iop_module_t *)m->data;
2878 
2879     if(strcmp(mod->op, operation) == 0
2880        && (mod->multi_priority == multi_priority || multi_priority == -1))
2881     {
2882       mod_ret = mod;
2883       break;
2884     }
2885   }
2886   return mod_ret;
2887 }
2888 
2889 /** adds keyboard accels to the first module in the pipe to handle where there are multiple instances */
dt_iop_connect_accels_multi(dt_iop_module_so_t * module)2890 void dt_iop_connect_accels_multi(dt_iop_module_so_t *module)
2891 {
2892   /*
2893    decide which module instance keyboard shortcuts will be applied to based on user preferences, as follows
2894     - Use the focused module, if it is an instance of this module type. Otherwise
2895     - prefer expanded instances (when selected and instances of the module are expanded on the RHS of the screen, collapsed instances will be ignored)
2896     - prefer enabled instances (when selected, after applying the above rule, if instances of the module are active, inactive instances will be ignored)
2897     - prefer unmasked instances (when selected, after applying the above rules, if instances of the module are unmasked, masked instances will be ignored)
2898     - selection order (after applying the above rules, apply the shortcut to the first or last instance remaining)
2899   */
2900   const int prefer_expanded = dt_conf_get_bool("accel/prefer_expanded") ? 8 : 0;
2901   const int prefer_enabled = dt_conf_get_bool("accel/prefer_enabled") ? 4 : 0;
2902   const int prefer_unmasked = dt_conf_get_bool("accel/prefer_unmasked") ? 2 : 0;
2903   const int prefer_first = dt_conf_is_equal("accel/select_order", "first instance") ? 1 : 0;
2904 
2905   if(darktable.develop->gui_attached)
2906   {
2907     dt_iop_module_t *accel_mod_new = NULL;  // The module to which accelerators are to be attached
2908 
2909     // if any instance has focus, use that one
2910     if(darktable.develop->gui_module && darktable.develop->gui_module->so == module)
2911       accel_mod_new = darktable.develop->gui_module;
2912     else
2913     {
2914       int best_score = -1;
2915 
2916       for(GList *iop_mods = g_list_last(darktable.develop->iop);
2917           iop_mods;
2918           iop_mods = g_list_previous(iop_mods))
2919       {
2920         dt_iop_module_t *mod = (dt_iop_module_t *)iop_mods->data;
2921 
2922         if(mod->so == module && mod->iop_order != INT_MAX)
2923         {
2924           int score = (mod->expanded ? prefer_expanded : 0)
2925                     + (mod->enabled ? prefer_enabled : 0)
2926                     + (mod->blend_params->mask_mode == DEVELOP_MASK_DISABLED ||
2927                       mod->blend_params->mask_mode == DEVELOP_MASK_ENABLED ? prefer_unmasked : 0);
2928 
2929           if(score + prefer_first > best_score)
2930           {
2931             best_score = score;
2932             accel_mod_new = mod;
2933           }
2934         }
2935       }
2936     }
2937 
2938     // switch accelerators to new module
2939     if(accel_mod_new)
2940     {
2941       dt_accel_connect_instance_iop(accel_mod_new);
2942 
2943       if(!strcmp(accel_mod_new->op, "exposure"))
2944         darktable.develop->proxy.exposure.module = accel_mod_new;
2945     }
2946   }
2947 }
2948 
dt_iop_connect_accels_all()2949 void dt_iop_connect_accels_all()
2950 {
2951   for(const GList *iop_mods = g_list_last(darktable.develop->iop); iop_mods; iop_mods = g_list_previous(iop_mods))
2952   {
2953     dt_iop_module_t *mod = (dt_iop_module_t *)iop_mods->data;
2954     dt_iop_connect_accels_multi(mod->so);
2955   }
2956 }
2957 
dt_iop_get_module_by_instance_name(GList * modules,const char * operation,const char * multi_name)2958 dt_iop_module_t *dt_iop_get_module_by_instance_name(GList *modules, const char *operation, const char *multi_name)
2959 {
2960   dt_iop_module_t *mod_ret = NULL;
2961 
2962   for(GList *m = modules; m; m = g_list_next(m))
2963   {
2964     dt_iop_module_t *mod = (dt_iop_module_t *)m->data;
2965 
2966     if((strcmp(mod->op, operation) == 0)
2967        && ((multi_name == NULL) || (strcmp(mod->multi_name, multi_name) == 0)))
2968     {
2969       mod_ret = mod;
2970       break;
2971     }
2972   }
2973   return mod_ret;
2974 }
2975 
2976 /** count instances of a module **/
dt_iop_count_instances(dt_iop_module_so_t * module)2977 int dt_iop_count_instances(dt_iop_module_so_t *module)
2978 {
2979   int inst_count = 0;
2980 
2981   for(const GList *iop_mods = g_list_last(darktable.develop->iop); iop_mods; iop_mods = g_list_previous(iop_mods))
2982   {
2983     dt_iop_module_t *mod = (dt_iop_module_t *)iop_mods->data;
2984     if(mod->so == module && mod->iop_order != INT_MAX)
2985     {
2986       inst_count++;
2987     }
2988   }
2989   return inst_count;
2990 }
2991 
dt_iop_is_first_instance(GList * modules,dt_iop_module_t * module)2992 gboolean dt_iop_is_first_instance(GList *modules, dt_iop_module_t *module)
2993 {
2994   gboolean is_first = TRUE;
2995   GList *iop = modules;
2996   while(iop)
2997   {
2998     dt_iop_module_t *m = (dt_iop_module_t *)iop->data;
2999     if(!strcmp(m->op, module->op))
3000     {
3001       is_first = (m == module);
3002       break;
3003     }
3004     iop = g_list_next(iop);
3005   }
3006 
3007   return is_first;
3008 }
3009 
dt_iop_refresh_center(dt_iop_module_t * module)3010 void dt_iop_refresh_center(dt_iop_module_t *module)
3011 {
3012   if(darktable.gui->reset) return;
3013   dt_develop_t *dev = module->dev;
3014   if (dev && dev->gui_attached)
3015   {
3016     // invalidate the pixelpipe cache except for the output of the prior module
3017     const uint64_t hash = dt_dev_pixelpipe_cache_basichash_prior(dev->pipe->image.id, dev->pipe, module);
3018     dt_dev_pixelpipe_cache_flush_all_but(&dev->pipe->cache, hash);
3019     dev->pipe->changed |= DT_DEV_PIPE_SYNCH; //ensure that commit_params gets called to pick up any GUI changes
3020     dt_dev_invalidate(dev);
3021     dt_control_queue_redraw_center();
3022   }
3023 }
3024 
dt_iop_refresh_preview(dt_iop_module_t * module)3025 void dt_iop_refresh_preview(dt_iop_module_t *module)
3026 {
3027   if(darktable.gui->reset) return;
3028   dt_develop_t *dev = module->dev;
3029   if (dev && dev->gui_attached)
3030   {
3031     // invalidate the pixelpipe cache except for the output of the prior module
3032     const uint64_t hash = dt_dev_pixelpipe_cache_basichash_prior(dev->pipe->image.id, dev->preview_pipe, module);
3033     dt_dev_pixelpipe_cache_flush_all_but(&dev->preview_pipe->cache, hash);
3034     dev->pipe->changed |= DT_DEV_PIPE_SYNCH; //ensure that commit_params gets called to pick up any GUI changes
3035     dt_dev_invalidate_all(dev);
3036     dt_control_queue_redraw();
3037   }
3038 }
3039 
dt_iop_refresh_preview2(dt_iop_module_t * module)3040 void dt_iop_refresh_preview2(dt_iop_module_t *module)
3041 {
3042   if(darktable.gui->reset) return;
3043   dt_develop_t *dev = module->dev;
3044   if (dev && dev->gui_attached)
3045   {
3046     // invalidate the pixelpipe cache except for the output of the prior module
3047     const uint64_t hash = dt_dev_pixelpipe_cache_basichash_prior(dev->pipe->image.id, dev->preview2_pipe, module);
3048     dt_dev_pixelpipe_cache_flush_all_but(&dev->preview2_pipe->cache, hash);
3049     dev->pipe->changed |= DT_DEV_PIPE_SYNCH; //ensure that commit_params gets called to pick up any GUI changes
3050     dt_dev_invalidate_all(dev);
3051     dt_control_queue_redraw();
3052   }
3053 }
3054 
dt_iop_refresh_all(dt_iop_module_t * module)3055 void dt_iop_refresh_all(dt_iop_module_t *module)
3056 {
3057   dt_iop_refresh_preview(module);
3058   dt_iop_refresh_center(module);
3059   dt_iop_refresh_preview2(module);
3060 }
3061 
_postponed_history_update(gpointer data)3062 static gboolean _postponed_history_update(gpointer data)
3063 {
3064   dt_iop_module_t *self = (dt_iop_module_t*)data;
3065   dt_dev_add_history_item(darktable.develop, self, TRUE);
3066   self->timeout_handle = 0;
3067   return FALSE; //cancel the timer
3068 }
3069 
3070 /** queue a delayed call of the add_history function after user interaction, to capture parameter updates (but not */
3071 /** too often). */
dt_iop_queue_history_update(dt_iop_module_t * module,gboolean extend_prior)3072 void dt_iop_queue_history_update(dt_iop_module_t *module, gboolean extend_prior)
3073 {
3074   if (module->timeout_handle && extend_prior)
3075   {
3076     // we already queued an update, but we don't want to have the update happen until the timeout expires
3077     // without any activity, so cancel the queued callback
3078     g_source_remove(module->timeout_handle);
3079   }
3080   if (!module->timeout_handle || extend_prior)
3081   {
3082     // adaptively set the timeout to 150% of the average time the past several pixelpipe runs took, clamped
3083     //   to keep updates from appearing to be too sluggish (though early iops such as rawdenoise may have
3084     //   multiple very slow iops following them, leading to >1000ms processing times)
3085     const int delay = CLAMP(darktable.develop->average_delay * 3 / 2, 10, 1200);
3086     module->timeout_handle = g_timeout_add(delay, _postponed_history_update, module);
3087   }
3088 }
3089 
dt_iop_cancel_history_update(dt_iop_module_t * module)3090 void dt_iop_cancel_history_update(dt_iop_module_t *module)
3091 {
3092   if (module->timeout_handle)
3093   {
3094     g_source_remove(module->timeout_handle);
3095     module->timeout_handle = 0;
3096   }
3097 }
3098 
dt_iop_warning_message(const char * message)3099 char *dt_iop_warning_message(const char *message)
3100 {
3101   if(dt_conf_get_bool("plugins/darkroom/show_warnings"))
3102     return g_strdup_printf("<span foreground='red'>⚠</span> %s", message);
3103   else
3104     return g_strdup_printf("%s", message);
3105 }
3106 
dt_iop_set_description(dt_iop_module_t * module,const char * main_text,const char * purpose,const char * input,const char * process,const char * output)3107 char *dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process,
3108                              const char *output)
3109 {
3110 #define TAB_SIZE 4.0
3111 #define P_TAB(n) (nb_tab + 1 - (int)ceilf((float)n / TAB_SIZE))
3112 
3113   const char *str_purpose = _("purpose");
3114   const char *str_input   = _("input");
3115   const char *str_process = _("process");
3116   const char *str_output  = _("output");
3117 
3118   const int len_purpose = g_utf8_strlen(str_purpose, -1);
3119   const int len_input   = g_utf8_strlen(str_input, -1);
3120   const int len_process = g_utf8_strlen(str_process, -1);
3121   const int len_output  = g_utf8_strlen(str_output, -1);
3122 
3123   const int max = MAX(len_purpose,
3124                       MAX(len_input, MAX(len_process, len_output)));
3125   const int nb_tab = ceilf((float)max / TAB_SIZE);
3126 
3127 #ifdef _WIN32
3128   // TODO: a windows dev is needed to find 4 icons properly rendered
3129   const char *icon_purpose = "•";
3130   const char *icon_input   = "•";
3131   const char *icon_process = "•";
3132   const char *icon_output  = "•";
3133 #else
3134   const char *icon_purpose = "⟳";
3135   const char *icon_input   = "⇥";
3136   const char *icon_process = "⟴";
3137   const char *icon_output  = "↦";
3138 #endif
3139 
3140   /* if the font can't display icons, default to nothing
3141   * Unfortunately, getting the font from the font desc is another scavenger hunt
3142   * into Gtk useless docs without examples. Good luck.
3143   PangoFontDescription *desc = darktable.bauhaus->pango_font_desc;
3144   if(!pango_font_has_char(desc->get_font(), g_utf8_to_ucs4(icon_purpose, 1)))
3145     icon_purpose = icon_input = icon_process = icon_output = "";
3146   */
3147 
3148   // align on tabs
3149   const char *tabs = "\t\t\t\t\t\t\t\t\t\t";
3150 
3151   char *str_out = g_strdup_printf
3152     ("%s.\n\n"
3153      "%s\t%s%.*s:\t%s\n"
3154      "%s\t%s%.*s:\t%s\n"
3155      "%s\t%s%.*s:\t%s\n"
3156      "%s\t%s%.*s:\t%s",
3157      main_text,
3158      icon_purpose, str_purpose, P_TAB(len_purpose), tabs, purpose,
3159      icon_input,   str_input,   P_TAB(len_input),   tabs, input,
3160      icon_process, str_process, P_TAB(len_process), tabs, process,
3161      icon_output,  str_output,  P_TAB(len_output),  tabs, output);
3162 
3163   return str_out;
3164 
3165 #undef P_TAB
3166 #undef TAB_SIZE
3167 }
3168 
dt_iop_have_required_input_format(const int req_ch,struct dt_iop_module_t * const module,const int ch,const void * const restrict ivoid,void * const restrict ovoid,const dt_iop_roi_t * const roi_in,const dt_iop_roi_t * const roi_out)3169 gboolean dt_iop_have_required_input_format(const int req_ch, struct dt_iop_module_t *const module, const int ch,
3170                                            const void *const restrict ivoid, void *const restrict ovoid,
3171                                            const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
3172 {
3173   if (ch == req_ch)
3174   {
3175     if (module)
3176       dt_iop_set_module_trouble_message(module, NULL, NULL, NULL);
3177     return TRUE;
3178   }
3179   else
3180   {
3181     // copy the input buffer to the output
3182     dt_iop_copy_image_roi(ovoid, ivoid, ch, roi_in, roi_out, TRUE);
3183     // and set the module's trouble message
3184     if (module)
3185       dt_iop_set_module_trouble_message(module, _("unsupported input"),
3186                                         _("you have placed this module at\n"
3187                                           "a position in the pipeline where\n"
3188                                           "the data format does not match\n"
3189                                           "its requirements."),
3190                                         "unsupported data format at current pipeline position");
3191     else
3192     {
3193       //TODO: pop up a toast message?
3194     }
3195     return FALSE;
3196   }
3197 }
3198 
3199 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
3200 // vim: shiftwidth=2 expandtab tabstop=2 cindent
3201 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3202