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