Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
modulegroups.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011-2012 Henrik Andersson.
4 Copyright (C) 2011-2012, 2014 johannes hanika.
5 Copyright (C) 2012, 2014 Jérémy Rosen.
6 Copyright (C) 2012 Pascal de Bruijn.
7 Copyright (C) 2012 Richard Wonka.
8 Copyright (C) 2012-2013 Simon Spannagel.
9 Copyright (C) 2012, 2014-2019 Tobias Ellinghaus.
10 Copyright (C) 2014-2016 Roman Lebedev.
11 Copyright (C) 2018-2019 Edgardo Hoszowski.
12 Copyright (C) 2018 Maurizio Paglia.
13 Copyright (C) 2018-2022 Pascal Obry.
14 Copyright (C) 2018 rawfiner.
15 Copyright (C) 2019-2022 Aldric Renaudin.
16 Copyright (C) 2019-2021, 2023, 2025-2026 Aurélien PIERRE.
17 Copyright (C) 2019, 2021 Hanno Schwalm.
18 Copyright (C) 2020-2022 Chris Elston.
19 Copyright (C) 2020-2021 Hubert Kowalski.
20 Copyright (C) 2020-2022 Nicolas Auffray.
21 Copyright (C) 2020 parafin.
22 Copyright (C) 2020 Sergey Salnikov.
23 Copyright (C) 2021-2022 Diederik Ter Rahe.
24 Copyright (C) 2021 luzpaz.
25 Copyright (C) 2021 Marco.
26 Copyright (C) 2021 Mark-64.
27 Copyright (C) 2021 Ralf Brown.
28 Copyright (C) 2022 Martin Bařinka.
29 Copyright (C) 2022 Philipp Lutz.
30 Copyright (C) 2022 Victor Forsiuk.
31 Copyright (C) 2023 Luca Zulberti.
32
33 darktable is free software: you can redistribute it and/or modify
34 it under the terms of the GNU General Public License as published by
35 the Free Software Foundation, either version 3 of the License, or
36 (at your option) any later version.
37
38 darktable is distributed in the hope that it will be useful,
39 but WITHOUT ANY WARRANTY; without even the implied warranty of
40 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 GNU General Public License for more details.
42
43 You should have received a copy of the GNU General Public License
44 along with darktable. If not, see <http://www.gnu.org/licenses/>.
45*/
46
47
48#include "develop/imageop_gui.h"
50#include "develop/iop_order.h"
51#include "common/logging.h"
52#include "system/macros.h"
55#include "common/conf.h"
56#include "control/signal.h"
57#include "develop/develop.h"
58#include "develop/imageop.h"
59#include "gui/application.h"
61#include "libs/lib.h"
62#include "libs/lib_api.h"
63
64#include <glib/gstdio.h>
65#include "widgets/label.h"
67#include "gui/screen_metrics.h"
68
69DT_MODULE(1)
70
71#define DT_IOP_ORDER_INFO (dt_get_debug_flags() & DT_DEBUG_IOPORDER)
72
84
92
105
106
108{
109 switch(group)
110 {
111 case IOP_GROUP_TONES:
112 case IOP_GROUP_COLOR:
113 case IOP_GROUP_FILM:
114 return MOD_TAB_BASIC;
115
117 return MOD_TAB_EFFECTS;
118
120 return MOD_TAB_SHARPNESS;
121
123 return MOD_TAB_TECHNICAL;
124
125 case IOP_GROUP_REPAIR:
126 return MOD_TAB_REPAIR;
127
128 case IOP_GROUP_LAST:
129 case IOP_GROUP_NONE:
130 return MOD_TAB_ALL;
131 }
132 return MOD_TAB_ACTIVE;
133}
138
139static const GtkTargetEntry _modulegroups_target_list[] = {
140 { "iop", GTK_TARGET_SAME_APP, DT_MODULEGROUPS_DND_TARGET_IOP }
141};
142static const guint _modulegroups_n_targets = G_N_ELEMENTS(_modulegroups_target_list);
143
144/* toggle button callback */
145static void _switch_page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data);
146/* helper function to update iop module view depending on group */
147static gboolean _update_iop_visibility(gpointer user_data);
148
149static void _lib_modulegroups_signal_set(gpointer instance, gpointer module, gpointer user_data);
150static void _lib_modulegroups_refresh(gpointer instance, gpointer user_data);
151
152static gboolean _is_module_in_history(const dt_iop_module_t *module);
154static gboolean _modulegroups_reorder_target(GtkWidget *target);
155
166{
167 GtkWidget *reference = NULL;
168 for(const GList *modules = g_list_first(dt_dev_get_global()->iop); modules; modules = g_list_next(modules))
169 {
170 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
171 if(!dt_iop_is_hidden(module) && module->gui && module->gui->expander)
172 {
173 reference = module->gui->expander;
174 break;
175 }
176 }
177 if(IS_NULL_PTR(reference)) return;
178
179 GtkBorder margin = { 0 };
180 GtkStyleContext *context = gtk_widget_get_style_context(reference);
181 gtk_style_context_get_margin(context, gtk_style_context_get_state(context), &margin);
182
183 for(size_t i = 0; i < TAB_BASIC_LAST; i++)
184 {
185 gtk_widget_set_margin_start(d->sections[i], margin.left);
186 gtk_widget_set_margin_end(d->sections[i], margin.right);
187 gtk_widget_set_margin_top(d->sections[i], margin.top);
188 gtk_widget_set_margin_bottom(d->sections[i], margin.bottom);
189 }
190}
191
201{
202 if(d && d->drag_highlight)
203 {
204 gtk_drag_unhighlight(d->drag_highlight);
205 d->drag_highlight = NULL;
206 }
207
208 if(IS_NULL_PTR(dt_dev_get_global())) return;
209
210 /* Walk every module and clear the before/after classes that motion handlers add. */
211 for(const GList *modules = g_list_last(dt_dev_get_global()->iop); modules; modules = g_list_previous(modules))
212 {
213 dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
214 if(!module->gui || !module->gui->expander) continue;
215 dt_gui_remove_class(module->gui->expander, "iop_drop_after");
216 dt_gui_remove_class(module->gui->expander, "iop_drop_before");
217 }
218}
219
230static void _modulegroups_append_visible_expanders(GtkWidget *widget, GList **widgets)
231{
232 if(!GTK_IS_WIDGET(widget) || !gtk_widget_is_visible(widget)) return;
233
234 if(g_object_get_data(G_OBJECT(widget), "dt-module"))
235 {
236 *widgets = g_list_append(*widgets, widget);
237 return;
238 }
239
240 if(!GTK_IS_CONTAINER(widget)) return;
241
242 /* Recurse over the current page subtree to preserve the visual order seen by the user. */
243 GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
244 for(GList *child = children; child; child = g_list_next(child))
245 _modulegroups_append_visible_expanders(GTK_WIDGET(child->data), widgets);
246 g_list_free(children);
247}
248
258{
259 if(IS_NULL_PTR(d)) return;
260 g_list_free(d->visible_expanders);
261 d->visible_expanders = NULL;
262 d->visible_expanders_tab = MOD_TAB_LAST;
263}
264
276{
279 if(IS_NULL_PTR(d) || IS_NULL_PTR(d->pages[tab])) return;
280
281 _modulegroups_append_visible_expanders(d->pages[tab], &d->visible_expanders);
282 d->visible_expanders_tab = tab;
283}
284
298 dt_iop_module_t *module_src)
299{
300 if(!GTK_IS_WIDGET(page) || !module_src) return NULL;
301
302 GtkAllocation source_allocation = { 0 };
303 gtk_widget_get_allocation(module_src->gui->header, &source_allocation);
304 const int y_slop = source_allocation.height / 2;
305 gboolean after_src = TRUE;
306 dt_iop_module_t *module_dest = NULL;
307
308 GList *children = NULL;
310
311 /* Walk the displayed headers in page coordinates and pick the closest valid insertion anchor. */
312 for(GList *l = children; l; l = g_list_next(l))
313 {
314 GtkWidget *w = GTK_WIDGET(l->data);
315 if(w == module_src->gui->expander) after_src = FALSE;
316
317 int widget_x = 0;
318 int widget_y = 0;
319 if(!gtk_widget_translate_coordinates(w, page, 0, 0, &widget_x, &widget_y)) continue;
320
321 GtkAllocation allocation = { 0 };
322 gtk_widget_get_allocation(w, &allocation);
323 if((after_src && y <= widget_y + y_slop)
324 || (!after_src && y <= widget_y + allocation.height + y_slop))
325 {
326 module_dest = (dt_iop_module_t *)g_object_get_data(G_OBJECT(w), "dt-module");
327 break;
328 }
329 }
330
331 g_list_free(children);
332 return module_dest;
333}
334
335static void _modulegroups_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data);
336static void _modulegroups_drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data);
337static void _modulegroups_drag_data_get(GtkWidget *widget, GdkDragContext *context,
338 GtkSelectionData *selection_data, guint info, guint time,
339 gpointer user_data);
340static gboolean _modulegroups_drag_drop(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
341 guint time, gpointer user_data);
342static gboolean _modulegroups_drag_motion(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
343 guint time, gpointer user_data);
344static void _modulegroups_drag_data_received(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
345 GtkSelectionData *selection_data, guint info, guint time,
346 gpointer user_data);
347static void _modulegroups_drag_leave(GtkWidget *widget, GdkDragContext *dc, guint time, gpointer user_data);
348
350{
351 *slot = widget;
352 g_object_add_weak_pointer(G_OBJECT(widget), (gpointer *)slot);
353}
354
355// Because pages are attached to the view right center container,
356// and not the our module widget, they are not stable across the lifetime
357// of the module, meaning they can get deleted anytime be the view.
358// So we need to recreate them dynamically on demand.
360{
363 if(IS_NULL_PTR(root)) return;
364
365 // Prepare tab pages
366 for(int i = 0; i < MOD_TAB_LAST; i++)
367 {
368 if(!IS_NULL_PTR(d->pages[i])) continue;
369 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
370 dt_gui_add_class(container, "module-groups-container");
372 gtk_widget_show(d->pages[i]);
373 gtk_drag_dest_set(d->pages[i], 0, _modulegroups_target_list, _modulegroups_n_targets, GDK_ACTION_COPY);
374 g_signal_connect(d->pages[i], "drag-data-received", G_CALLBACK(_modulegroups_drag_data_received), self);
375 g_signal_connect(d->pages[i], "drag-drop", G_CALLBACK(_modulegroups_drag_drop), self);
376 g_signal_connect(d->pages[i], "drag-motion", G_CALLBACK(_modulegroups_drag_motion), self);
377 g_signal_connect(d->pages[i], "drag-leave", G_CALLBACK(_modulegroups_drag_leave), self);
378 gtk_box_pack_start(root, d->pages[i], FALSE, FALSE, 0);
379 }
380
381 // Prepare the basic tab page
382 for(int i = 0; i < TAB_BASIC_LAST; i++)
383 {
384 if(IS_NULL_PTR(d->sections[i]))
385 {
386 switch(i)
387 {
388 case TAB_BASIC_COLOR:
389 _modulegroups_track_widget(&d->sections[i], dt_ui_section_label_new(_("color")));
390 break;
391 case TAB_BASIC_FILM:
392 _modulegroups_track_widget(&d->sections[i], dt_ui_section_label_new(_("film")));
393 break;
394 case TAB_BASIC_TONES:
395 _modulegroups_track_widget(&d->sections[i], dt_ui_section_label_new(_("tones")));
396 break;
397 }
398 gtk_box_pack_start(GTK_BOX(d->pages[MOD_TAB_BASIC]), d->sections[i], FALSE, FALSE, 0);
399 gtk_widget_show(d->sections[i]);
400 }
401 if(IS_NULL_PTR(d->containers[i]))
402 {
403 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
404 dt_gui_add_class(container, "module-groups-container");
405 _modulegroups_track_widget(&d->containers[i], container);
406 gtk_box_pack_start(GTK_BOX(d->pages[MOD_TAB_BASIC]), d->containers[i], FALSE, FALSE, 0);
407 gtk_widget_show(d->containers[i]);
408 }
409 }
410}
411
413{
414 return CLAMP(tab, 0, MOD_TAB_LAST - 1);
415}
416
418{
420 if(!IS_NULL_PTR(d) && GTK_IS_NOTEBOOK(d->notebook))
421 {
422 const gint page = gtk_notebook_get_current_page(GTK_NOTEBOOK(d->notebook));
423 if(page >= 0 && page < MOD_TAB_LAST) return (dt_modulesgroups_tabs_t)page;
424 }
425
426 // Fall back to persisted state when notebook page is temporarily unavailable.
427 return _modulegroups_cycle_tabs(dt_conf_get_int("plugins/darkroom/moduletab"));
428}
429
431{
433 if(!IS_NULL_PTR(d) && GTK_IS_NOTEBOOK(d->notebook))
434 gtk_notebook_set_current_page(GTK_NOTEBOOK(d->notebook), tab);
435}
436
438{
439 const dt_modulesgroups_tabs_t tab = _group_to_tab(group);
440 _set_current_tab(self, tab);
441}
442
443static gboolean _is_module_in_tab(dt_iop_module_t *module, dt_modulesgroups_tabs_t current_tab)
444{
445 if(IS_NULL_PTR(module) || dt_iop_is_hidden(module)) return FALSE;
446
447 switch(current_tab)
448 {
449 case MOD_TAB_ACTIVE:
450 // The one place this rule is written. Anything else that needs to list the pipeline's
451 // modules -- the shape manager's module chooser, say -- asks the same function, so the
452 // two cannot answer differently.
453 return dt_iop_module_is_in_pipeline(module);
454 case MOD_TAB_ALL:
455 return (_is_module_in_history(module) || module->enabled || !(module->flags() & IOP_FLAGS_DEPRECATED));
456
457 default:
458 {
459 const dt_iop_group_t group = module->default_group();
460 const dt_modulesgroups_tabs_t tab = _group_to_tab(group);
461 return (tab == current_tab) && (_is_module_in_history(module) || module->enabled || !(module->flags() & IOP_FLAGS_DEPRECATED));
462 }
463 }
464
465 return FALSE;
466}
467
479{
482
483 switch(tab)
484 {
485 case MOD_TAB_BASIC:
486 {
487 if(IS_NULL_PTR(module)) return NULL;
488
489 // IOP groups tones, color and film go from 1 to 3,
490 // and sections go from 0 to 2, so we simply manage the offset.
491 dt_iop_group_t group = module->default_group();
492 if(group == IOP_GROUP_TONES)
493 return d->containers[TAB_BASIC_TONES];
494 if(group == IOP_GROUP_COLOR)
495 return d->containers[TAB_BASIC_COLOR];
496 if(group == IOP_GROUP_FILM)
497 return d->containers[TAB_BASIC_FILM];
498 return NULL;
499 }
500
501 default:
502 return d->pages[tab];
503 }
504}
505
516{
517 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->expander)) return;
518 GtkWidget *widget = module->gui->expander;
519 g_object_set_data(G_OBJECT(widget), "dt-module", module);
520
521 if(g_object_get_data(G_OBJECT(widget), "modulegroups-dnd")) return;
522
523 gtk_drag_source_set(widget, GDK_BUTTON1_MASK, _modulegroups_target_list, _modulegroups_n_targets, GDK_ACTION_COPY);
524 g_signal_connect(widget, "drag-begin", G_CALLBACK(_modulegroups_drag_begin), self);
525 g_signal_connect(widget, "drag-data-get", G_CALLBACK(_modulegroups_drag_data_get), self);
526 g_signal_connect(widget, "drag-end", G_CALLBACK(_modulegroups_drag_end), self);
527 g_object_set_data(G_OBJECT(widget), "modulegroups-dnd", GINT_TO_POINTER(TRUE));
528}
529
541{
542 gboolean has_visible = FALSE;
543 int position = 0;
544
545 /* Walk the whole pipeline in reverse order and keep only the modules currently parented here. */
546 for(GList *modules = g_list_last(dt_dev_get_global()->iop); modules; modules = g_list_previous(modules))
547 {
548 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
549 if(dt_iop_is_hidden(module) || !module->gui || !module->gui->expander
550 || !gtk_widget_get_visible(module->gui->expander)) continue;
551 if(gtk_widget_get_parent(module->gui->expander) != target) continue;
552
553 gtk_box_reorder_child(GTK_BOX(target), module->gui->expander, position++);
554 has_visible = TRUE;
555 }
556
557 return has_visible;
558}
559
560static void _modulegroups_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
561{
562 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
564 dt_iop_module_t *module_src = (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module");
565
566 d->drag_source = module_src;
568 g_object_set_data(G_OBJECT(widget), "dt-module-dragged", GINT_TO_POINTER(TRUE));
569
570 if(!module_src || !module_src->gui->header) return;
571
572 GdkWindow *window = gtk_widget_get_parent_window(module_src->gui->header);
573 if(IS_NULL_PTR(window)) return;
574
575 GtkAllocation allocation = { 0 };
576 gtk_widget_get_allocation(module_src->gui->header, &allocation);
577 cairo_surface_t *surface = dt_cairo_image_surface_create(CAIRO_FORMAT_RGB24, allocation.width, allocation.height);
578 cairo_t *cr = cairo_create(surface);
579
580 dt_gui_add_class(module_src->gui->header, "iop_drag_icon");
581 gtk_widget_draw(module_src->gui->header, cr);
582 dt_gui_remove_class(module_src->gui->header, "iop_drag_icon");
583
584 cairo_surface_set_device_offset(surface, -allocation.width * dt_gui_get_global()->ppd / 2,
585 -allocation.height * dt_gui_get_global()->ppd / 2);
586 gtk_drag_set_icon_surface(context, surface);
587
588 cairo_destroy(cr);
589 cairo_surface_destroy(surface);
590}
591
592static void _modulegroups_drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
593{
594 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
596
598 d->drag_source = NULL;
599 g_object_set_data(G_OBJECT(widget), "dt-module-dragged", NULL);
600}
601
602static void _modulegroups_drag_data_get(GtkWidget *widget, GdkDragContext *context,
603 GtkSelectionData *selection_data, guint info, guint time,
604 gpointer user_data)
605{
606 const guint number_data = 1;
607 gtk_selection_data_set(selection_data, gdk_atom_intern("iop", TRUE), 32, (const guchar *)&number_data, 1);
608}
609
610static gboolean _modulegroups_drag_drop(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
611 guint time, gpointer user_data)
612{
613 gtk_drag_get_data(widget, dc, gdk_atom_intern("iop", TRUE), time);
614 return TRUE;
615}
616
617static gboolean _modulegroups_drag_motion(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
618 guint time, gpointer user_data)
619{
620 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
622 dt_iop_module_t *module_src = d->drag_source;
623 if(IS_NULL_PTR(module_src)) return FALSE;
624
625 dt_iop_module_t *module_dest = _modulegroups_get_dnd_dest_module(widget, y, module_src);
626 gboolean can_move = FALSE;
628
629 if(module_dest && module_src != module_dest)
630 {
631 if(module_src->iop_order < module_dest->iop_order)
632 can_move = dt_ioppr_check_can_move_after_iop(dt_dev_get_global()->iop, module_src, module_dest);
633 else
634 can_move = dt_ioppr_check_can_move_before_iop(dt_dev_get_global()->iop, module_src, module_dest);
635 }
636
637 if(!can_move)
638 {
639 gdk_drag_status(dc, 0, time);
640 return FALSE;
641 }
642
643 if(module_src->iop_order < module_dest->iop_order)
644 dt_gui_add_class(module_dest->gui->expander, "iop_drop_after");
645 else
646 dt_gui_add_class(module_dest->gui->expander, "iop_drop_before");
647
648 d->drag_highlight = module_dest->gui->expander;
649 gtk_drag_highlight(module_dest->gui->expander);
650 gdk_drag_status(dc, GDK_ACTION_COPY, time);
651 return TRUE;
652}
653
654static void _modulegroups_drag_data_received(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
655 GtkSelectionData *selection_data, guint info, guint time,
656 gpointer user_data)
657{
658 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
660 dt_iop_module_t *module_src = d->drag_source;
661 dt_iop_module_t *module_dest = _modulegroups_get_dnd_dest_module(widget, y, module_src);
662
663 if(module_src && module_dest && module_src != module_dest)
664 {
665 if(module_src->iop_order < module_dest->iop_order)
666 dt_iop_gui_move_module_after(module_src, module_dest, "_modulegroups_drag_data_received");
667 else
668 dt_iop_gui_move_module_before(module_src, module_dest, "_modulegroups_drag_data_received");
669 }
670
671 gtk_drag_finish(dc, TRUE, FALSE, time);
673 d->drag_source = NULL;
674}
675
676static void _modulegroups_drag_leave(GtkWidget *widget, GdkDragContext *dc, guint time, gpointer user_data)
677{
678 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
681}
682
683const char *name(struct dt_lib_module_t *self)
684{
685 return _("modulegroups");
686}
687
688const char **views(dt_lib_module_t *self)
689{
690 static const char *v[] = { "darkroom", NULL };
691 return v;
692}
693
695{
697}
698
699
700/* this module should always be shown without expander */
702{
703 return 0;
704}
705
707{
708 return 999;
709}
710
711
712static gboolean _modulegroups_switch_tab_next(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
713 GdkModifierType modifier, gpointer data)
714{
715 dt_lib_module_t *self = (dt_lib_module_t *)data;
717 if(focused) dt_iop_gui_set_expanded(focused, FALSE, TRUE);
718
719 const dt_modulesgroups_tabs_t current = _get_current_tab(self);
720 _set_current_tab(self, _modulegroups_cycle_tabs(current + 1));
722
723 return TRUE;
724}
725
726static gboolean _modulegroups_switch_tab_previous(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
727 GdkModifierType modifier, gpointer data)
728{
729 dt_lib_module_t *self = (dt_lib_module_t *)data;
731 if(focused) dt_iop_gui_set_expanded(focused, FALSE, TRUE);
732
733 const dt_modulesgroups_tabs_t current = _get_current_tab(self);
734 _set_current_tab(self, _modulegroups_cycle_tabs(current - 1));
736
737 return TRUE;
738}
739
740static gboolean _scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
741{
742 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
743 int delta_x, delta_y;
744 if(dt_gui_get_scroll_unit_deltas(event, &delta_x, &delta_y))
745 {
746 const dt_modulesgroups_tabs_t current = _get_current_tab(self);
747 dt_modulesgroups_tabs_t future = 0;
748 if(delta_x > 0. || delta_y > 0.)
749 future = current + 1;
750 else if(delta_x < 0. || delta_y < 0.)
751 future = current - 1;
752
755 }
756
757 return TRUE;
758}
759
760
761static void _focus_module(dt_iop_module_t *module)
762{
763 if(module && dt_iop_gui_module_is_visible(module))
764 {
765 dt_iop_request_focus(module);
767 }
768 else
769 {
770 // we reached the extremity of the list.
772 }
773}
774
775static gboolean _focus_previous_module(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
776 GdkModifierType modifier, gpointer user_data)
777{
778 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
781
782 // When filmstrip owns keyboard focus, keep PageUp routed to filmstrip navigation.
784 if(!IS_NULL_PTR(filmstrip) && !IS_NULL_PTR(filmstrip->grid) && gtk_widget_has_focus(filmstrip->grid))
785 return FALSE;
786 if(d->visible_expanders_tab != _get_current_tab(self))
787 return TRUE;
788
789 const GList *children = d->visible_expanders;
790 if(IS_NULL_PTR(focused))
791 {
792 GList *first = g_list_first((GList *)children);
793 GtkWidget *widget = first ? GTK_WIDGET(first->data) : NULL;
794 _focus_module(widget ? (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module") : NULL);
795 }
796 else
797 {
798 dt_iop_module_t *target = NULL;
799
800 /* Page Up follows the displayed module order. The basic tab is split in
801 * section containers, so we walk the actual visible expander tree instead
802 * of the pipeline list. */
803 for(const GList *module = g_list_first((GList *)children); module; module = g_list_next(module))
804 {
805 GtkWidget *widget = GTK_WIDGET(module->data);
806 if(widget == focused->gui->expander) break;
807 target = (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module");
808 }
809 if(IS_NULL_PTR(target))
810 {
811 GList *last = g_list_last((GList *)children);
812 GtkWidget *widget = last ? GTK_WIDGET(last->data) : NULL;
813 target = widget ? (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module") : NULL;
814 }
815
817 _focus_module(target);
818 }
819
820 return TRUE;
821}
822
823static gboolean _focus_next_module(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
824 GdkModifierType modifier, gpointer user_data)
825{
826 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
829
830 // When filmstrip owns keyboard focus, keep PageDown routed to filmstrip navigation.
832 if(!IS_NULL_PTR(filmstrip) && !IS_NULL_PTR(filmstrip->grid) && gtk_widget_has_focus(filmstrip->grid))
833 return FALSE;
834 if(d->visible_expanders_tab != _get_current_tab(self))
835 return TRUE;
836
837 const GList *children = d->visible_expanders;
838 if(IS_NULL_PTR(focused))
839 {
840 GList *last = g_list_last((GList *)children);
841 GtkWidget *widget = last ? GTK_WIDGET(last->data) : NULL;
842 _focus_module(widget ? (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module") : NULL);
843 }
844 else
845 {
846 dt_iop_module_t *target = NULL;
847
848 /* Page Down follows the displayed module order. The basic tab is split in
849 * section containers, so we walk the actual visible expander tree instead
850 * of the pipeline list. */
851 for(const GList *module = g_list_last((GList *)children); module; module = g_list_previous(module))
852 {
853 GtkWidget *widget = GTK_WIDGET(module->data);
854 if(widget == focused->gui->expander) break;
855 target = (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module");
856 }
857 if(IS_NULL_PTR(target))
858 {
859 GList *first = g_list_first((GList *)children);
860 GtkWidget *widget = first ? GTK_WIDGET(first->data) : NULL;
861 target = widget ? (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module") : NULL;
862 }
863
865 _focus_module(target);
866 }
867
868 return TRUE;
869}
870
871static gboolean _is_valid_widget(GtkWidget *widget)
872{
873 if(IS_NULL_PTR(widget))
874 {
875 dt_print(DT_DEBUG_SHORTCUTS, "[modulegroups] _is_valid_widget skip: widget=NULL\n");
876 return FALSE;
877 }
878
879 // The parent will always be a GtkBox
880 GtkWidget *parent = gtk_widget_get_parent(widget);
881 if(IS_NULL_PTR(parent))
882 {
883 dt_print(DT_DEBUG_SHORTCUTS, "[modulegroups] _is_valid_widget skip: parent=NULL widget=%s\n",
884 gtk_widget_get_name(widget));
885 return FALSE;
886 }
887
888 GtkWidget *grandparent = gtk_widget_get_parent(parent);
889 if(IS_NULL_PTR(grandparent))
890 {
891 dt_print(DT_DEBUG_SHORTCUTS, "[modulegroups] _is_valid_widget skip: grandparent=NULL widget=%s parent=%s\n",
892 gtk_widget_get_name(widget), gtk_widget_get_name(parent));
893 return FALSE;
894 }
895
896 GType type = G_OBJECT_TYPE(grandparent);
897
898 gboolean visible_parent = TRUE;
899
900 if(type == GTK_TYPE_NOTEBOOK)
901 {
902 // Find the page in which the current widget is and try to show it
903 gint page_num = gtk_notebook_page_num(GTK_NOTEBOOK(grandparent), parent);
904 if(page_num > -1)
905 gtk_notebook_set_current_page(GTK_NOTEBOOK(grandparent), page_num);
906 else
907 visible_parent = FALSE;
908 }
909 else if(type == GTK_TYPE_STACK)
910 {
911 // Stack pages are enabled based on user parameteters,
912 // so if not visible, then do nothing.
913 GtkWidget *visible_child = gtk_stack_get_visible_child(GTK_STACK(grandparent));
914 if(visible_child != parent) visible_parent = FALSE;
915 }
916
917 return gtk_widget_is_visible(widget) && gtk_widget_is_sensitive(widget)
918 && visible_parent;
919}
920
921
922// Because Gtk can't focus on invisible widgets without errors
923// (and weird behaviour on user's end), getting the first widget in the list is not enough.
924static GList *_find_next_visible_widget(GList *widgets)
925{
926 for(GList *first = widgets; first; first = g_list_next(first))
927 {
928 GtkWidget *widget = (GtkWidget *)first->data;
929 if(_is_valid_widget(widget)) return first;
930 }
931 return NULL;
932}
933
934
935static GList *_find_previous_visible_widget(GList *widgets)
936{
937 for(GList *last = widgets; last; last = g_list_previous(last))
938 {
939 GtkWidget *widget = (GtkWidget *)last->data;
940 if(_is_valid_widget(widget)) return last;
941 }
942 return NULL;
943}
944
945static void _focus_widget(GtkWidget *widget)
946{
947 gtk_widget_grab_focus(widget);
949}
950
951
952static gboolean _focus_next_control()
953{
955 dt_gui_module_t *m = DT_GUI_MODULE(focused);
956 if(!focused || !m->widget_list) return FALSE;
957
958 GtkWidget *current_widget = dt_widget_scroll_focus();
959 GList *first_item = _find_next_visible_widget(g_list_first(m->widget_list));
960
961 if(!current_widget && first_item)
962 {
963 // No active widget, start by the first
964 _focus_widget(GTK_WIDGET(first_item->data));
965 }
966 else
967 {
968 GList *current_item = g_list_find(m->widget_list, current_widget);
969 GList *next_item = _find_next_visible_widget(g_list_next(current_item));
970
971 // Select the next visible item, if any
972 if(next_item)
973 _focus_widget(GTK_WIDGET(next_item->data));
974 // Cycle back to the beginning
975 else if(first_item)
976 _focus_widget(GTK_WIDGET(first_item->data));
977 }
978
979 return TRUE;
980}
981
982static gboolean _focus_previous_control()
983{
985 dt_gui_module_t *m = DT_GUI_MODULE(focused);
986 if(!focused || !m->widget_list) return FALSE;
987
988 GtkWidget *current_widget = dt_widget_scroll_focus();
989 GList *last_item = _find_previous_visible_widget(g_list_last(m->widget_list));
990
991 if(!current_widget && last_item)
992 {
993 // No active widget, start by the last
994 _focus_widget(GTK_WIDGET(last_item->data));
995 }
996 else
997 {
998 GList *current_item = g_list_find(m->widget_list, current_widget);
999 GList *previous_item = _find_previous_visible_widget(g_list_previous(current_item));
1000
1001 // Select the previous item, if any
1002 if(previous_item)
1003 _focus_widget(GTK_WIDGET(previous_item->data));
1004 // Cycle back to the end
1005 else if(last_item)
1006 _focus_widget(GTK_WIDGET(last_item->data));
1007 }
1008
1009 return TRUE;
1010}
1011
1013{
1014 /* initialize ui widgets */
1016 self->data = (void *)d;
1017 d->inited = FALSE;
1018 d->visible_expanders = NULL;
1019 d->visible_expanders_tab = MOD_TAB_LAST;
1020
1021 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1023 gtk_widget_set_name(self->widget, "modules-tabs");
1024
1025 /* Tabs */
1026 d->notebook = GTK_WIDGET(gtk_notebook_new());
1027 dt_gui_add_class(d->notebook, "empty");
1028 char *labels[] = { _("Pipeline"), _("Basic"), _("Repair"), _("Sharpness"), _("Effects"), _("Technics"), _("All") };
1029 char *tooltips[]
1030 = { _("List all modules currently enabled in the reverse order of application in the pipeline."),
1031 _("Modules destined to adjust brightness, contrast and dynamic range, work with film scans, and perform color-grading."),
1032 _("Modules destined to repair and reconstruct noisy or missing pixels."),
1033 _("Modules destined to manipulate local contrast, sharpness and blur."),
1034 _("Modules applying special effects."),
1035 _("Technical modules that can be ignored in most situations."),
1036 _("All modules available in the software.") };
1037
1038 for(int i = 0; i < MOD_TAB_LAST; i++)
1039 {
1040 GtkWidget *label = gtk_label_new(labels[i]);
1041 dt_gui_add_class(label, "dt_modulegroups_tab_label");
1042 gtk_widget_set_tooltip_text(label, tooltips[i]);
1043 gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
1044 gtk_widget_set_hexpand(label, TRUE);
1045 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
1046
1047 GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1048 gtk_notebook_append_page(GTK_NOTEBOOK(d->notebook), page, label);
1049 gtk_container_child_set(GTK_CONTAINER(d->notebook), page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
1050 gtk_widget_show(page);
1051
1052 d->pages[i] = NULL;
1053 }
1054 gtk_notebook_set_current_page(GTK_NOTEBOOK(d->notebook), dt_conf_get_int("plugins/darkroom/moduletab"));
1055 gtk_notebook_popup_enable(GTK_NOTEBOOK(d->notebook));
1056 gtk_notebook_set_scrollable(GTK_NOTEBOOK(d->notebook), TRUE);
1057 g_signal_connect(G_OBJECT(d->notebook), "switch_page", G_CALLBACK(_switch_page), self);
1058 g_signal_connect(G_OBJECT(d->notebook), "scroll-event", G_CALLBACK(_scroll_event), self);
1059 gtk_widget_add_events(GTK_WIDGET(d->notebook), dt_widget_scroll_mask());
1060
1061 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->notebook), TRUE, TRUE, 0);
1062 gtk_widget_show_all(self->widget);
1063
1065 G_CALLBACK(_lib_modulegroups_signal_set), self);
1067 G_CALLBACK(_lib_modulegroups_refresh), self);
1069 G_CALLBACK(_lib_modulegroups_refresh), self);
1071 G_CALLBACK(_lib_modulegroups_refresh), self);
1072 // History edits already trigger explicit modulegroups visibility updates through
1073 // DT_SIGNAL_DEVELOP_MODULEGROUPS_SET. Listening to generic history changes here
1074 // causes unnecessary expander reparenting, which drops native Gtk focus from
1075 // focused Bauhaus controls after committed key/scroll edits.
1076
1077 // Kept as the literal Ctrl key (not DT_PRIMARY_MASK): Cmd+Tab is reserved system-wide by
1078 // macOS for the application switcher and never reaches any app's event loop, so binding
1079 // this to Cmd would make it silently dead on that platform.
1080 dt_accels_new_darkroom_action(_modulegroups_switch_tab_next, self, NULL, N_("Darkroom/Actions"),
1081 N_("move to the next modules tab"), GDK_KEY_Tab, GDK_CONTROL_MASK, _("Triggers the action"));
1082 dt_accels_new_darkroom_action(_modulegroups_switch_tab_previous, self, NULL, N_("Darkroom/Actions"),
1083 N_("move to the previous modules tab"), GDK_KEY_Tab,
1084 GDK_CONTROL_MASK | GDK_SHIFT_MASK, _("Triggers the action"));
1085
1086 dt_accels_new_darkroom_locked_action(_focus_next_module, self, NULL, N_("Darkroom/Actions"),
1087 N_("Focus on the next module"), GDK_KEY_Page_Down, 0, _("Triggers the action"));
1088 dt_accels_new_darkroom_locked_action(_focus_previous_module, self, NULL, N_("Darkroom/Actions"),
1089 N_("Focus on the previous module"), GDK_KEY_Page_Up, 0, _("Triggers the action"));
1090
1091 dt_accels_new_darkroom_locked_action(_focus_next_control, self, NULL, N_("Darkroom/Actions"),
1092 N_("Focus on the next module control"), GDK_KEY_Down, DT_PRIMARY_MASK, _("Triggers the action"));
1093 dt_accels_new_darkroom_locked_action(_focus_previous_control, self, NULL, N_("Darkroom/Actions"),
1094 N_("Focus on the previous module control"), GDK_KEY_Up, DT_PRIMARY_MASK, _("Triggers the action"));
1095}
1096
1097static gboolean _modulegroups_move_widget(GtkWidget *widget, GtkWidget *target);
1098
1100{
1103
1104 if(self->data)
1105 {
1110 if(dt_dev_get_global() && root)
1111 {
1112 /* Hand module expanders back to the right-panel root before destroying
1113 * our page boxes, otherwise Gtk would destroy the module widgets along
1114 * with the page containers. */
1115 for(GList *modules = g_list_first(dt_dev_get_global()->iop); modules; modules = g_list_next(modules))
1116 {
1117 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
1118 if(module->gui && module->gui->expander) _modulegroups_move_widget(module->gui->expander, GTK_WIDGET(root));
1119 }
1120 }
1121 for(int i = 0; i < MOD_TAB_ALL; i++)
1122 {
1123 if(!IS_NULL_PTR(d->pages[i]) && GTK_IS_WIDGET(d->pages[i]))
1124 gtk_widget_destroy(d->pages[i]);
1125 d->pages[i] = NULL;
1126 }
1127 dt_free(d);
1128 self->data = NULL;
1129 }
1130}
1131
1132static gboolean _is_module_in_history(const dt_iop_module_t *module)
1133{
1134 for(GList *history = g_list_last(dt_dev_get_global()->history); history; history = g_list_previous(history))
1135 {
1136 const dt_dev_history_item_t *hitem = (dt_dev_history_item_t *)(history->data);
1137 if(hitem->module == module) return TRUE;
1138 }
1139
1140 return FALSE;
1141}
1142
1143static gboolean _modulegroups_move_widget(GtkWidget *widget, GtkWidget *target)
1144{
1145 if(!GTK_IS_WIDGET(widget) || !GTK_IS_BOX(target)) return FALSE;
1146
1147 GtkWidget *parent = gtk_widget_get_parent(widget);
1148 if(parent == target) return FALSE;
1149
1150 g_object_ref(widget);
1151 if(GTK_IS_CONTAINER(parent)) gtk_container_remove(GTK_CONTAINER(parent), widget);
1152 gtk_box_pack_start(GTK_BOX(target), widget, FALSE, FALSE, 0);
1153 g_object_unref(widget);
1154 return TRUE;
1155}
1156
1157static gboolean _update_iop_visibility(gpointer user_data)
1158{
1159 dt_develop_t *const dev = dt_dev_get_global();
1160 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1161 if(IS_NULL_PTR(dev)) return G_SOURCE_REMOVE;
1163 const dt_modulesgroups_tabs_t tab = _get_current_tab(self);
1164
1167
1168 // Update notebook pages visibility
1169 for(int i = 0; i < MOD_TAB_LAST; i++) gtk_widget_set_visible(d->pages[i], i == tab);
1170
1171 /* Walk every develop module and decide whether it belongs to the active tab and which box should host it. */
1172 for(GList *modules = g_list_last(dev->iop); modules; modules = g_list_previous(modules))
1173 {
1174 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
1175 if(dt_iop_is_hidden(module)) continue; // Hidden modules don't have a widget
1176
1177 GtkWidget *w = module->gui ? module->gui->expander : NULL;
1178 if(IS_NULL_PTR(w)) continue; // Module GUI may not be inited yet
1179
1180 const gboolean visible = _is_module_in_tab(module, tab);
1181 GtkWidget *target = _get_target_container(self, module);
1182
1183 // Extra module instances that are "added" by history items past the current history end conceptually don't exist yet.
1184 // They exist as disabled pipeline nodes only because they are referenced by an history entry.
1185 // They will be brought back to life if user move the history end cursor past their history item.
1186 // For consistency, we hide them from GUI until then.
1187 // This doesn't apply to base instances.
1188 // FIXME: at some point, we will need to embrace the nodal paradigm and use a "create instance"
1189 // approach, even for the first instance, instead of mixing GUI toolboxes à la Lightroom for the first
1190 // (base) instance and then nodal approach for the others.
1191 if(visible && dt_iop_module_instance_exists(module))
1192 {
1193 _modulegroups_setup_drag_source(self, module);
1194 _modulegroups_move_widget(w, target);
1195 gtk_widget_show(w);
1196 }
1197 else
1198 {
1199 if(dev->gui_module == module)
1200 {
1203 }
1204
1205 gtk_widget_hide(w);
1206 }
1207 }
1208
1209 /* Multishow may hide extra instances, so we only compute section occupancy
1210 * and final ordering after it has settled the visible module set. */
1211 // FIXME: ditch that
1213
1214 // Ensure the module is visible
1215 dt_iop_module_t *active = dev->gui_module;
1216 if(!IS_NULL_PTR(active) && !IS_NULL_PTR(active->gui->expander))
1217 {
1218 if(dt_gui_get_global()->scroll_to[1] != active->gui->header)
1219 {
1220 const gboolean scroll_new_instance_to_header
1222 && !IS_NULL_PTR(active->gui->header) && GTK_IS_WIDGET(active->gui->header));
1223
1224 dt_gui_get_global()->scroll_to[1] = scroll_new_instance_to_header ? active->gui->header : active->gui->expander;
1225
1226 if(scroll_new_instance_to_header) dt_gui_get_global()->scroll_to_header_once = NULL;
1227 }
1228 }
1229
1230 if(tab == MOD_TAB_BASIC)
1231 {
1232 for(int i = 0; i < TAB_BASIC_LAST; i++)
1233 {
1234 const gboolean has_section = _modulegroups_reorder_target(d->containers[i]);
1235 gtk_widget_set_visible(d->sections[i], has_section);
1236 gtk_widget_set_visible(d->containers[i], has_section);
1237 }
1238 }
1239 else
1240 {
1241 _modulegroups_reorder_target(d->pages[tab]);
1242 }
1243
1244 // Ensure the parent get refreshed
1246 gtk_widget_queue_resize(d->pages[tab]);
1247 gtk_widget_queue_draw(d->pages[tab]);
1248
1249 return G_SOURCE_REMOVE;
1250}
1251
1252static void _switch_page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
1253{
1254 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1255 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return;
1256 g_idle_add((GSourceFunc)_update_iop_visibility, self);
1257 dt_conf_set_int("plugins/darkroom/moduletab", page_num);
1258}
1259
1260static void _lib_modulegroups_signal_set(gpointer instance, gpointer module, gpointer user_data)
1261{
1262 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1263 dt_iop_module_t *iop_module = (dt_iop_module_t *)module;
1264 if(IS_NULL_PTR(iop_module)) return;
1265
1266 const dt_modulesgroups_tabs_t current_tab = _get_current_tab(self);
1267 GtkWidget *expander = iop_module->gui ? iop_module->gui->expander : NULL;
1268 const gboolean prefer_active_once
1269 = !IS_NULL_PTR(expander)
1270 && GPOINTER_TO_INT(g_object_get_data(G_OBJECT(expander),
1271 "dt-modulegroups-prefer-active-once"));
1272 const gboolean allow_switch_from_active
1273 = !IS_NULL_PTR(expander)
1274 && GPOINTER_TO_INT(g_object_get_data(G_OBJECT(expander),
1275 "dt-modulegroups-switch-from-active-once"));
1276 const gboolean module_in_active_tab = _is_module_in_tab(iop_module, MOD_TAB_ACTIVE);
1277 if(!IS_NULL_PTR(expander))
1278 {
1279 g_object_set_data(G_OBJECT(expander), "dt-modulegroups-prefer-active-once", NULL);
1280 g_object_set_data(G_OBJECT(expander), "dt-modulegroups-switch-from-active-once", NULL);
1281 }
1282
1283 // Direct actions (enable/toggle) should prioritize Pipeline for the focused module.
1284 if(prefer_active_once && module_in_active_tab && current_tab != MOD_TAB_ACTIVE)
1286 // Focus-only requests from accel search: stay in Pipeline when the module is
1287 // already there, otherwise jump to the module group tab.
1288 else if(allow_switch_from_active && module_in_active_tab && current_tab != MOD_TAB_ACTIVE)
1290 // If module not in current tab: switch tab
1291 // Keep users on the Pipeline tab when they duplicate/create modules from it.
1292 // Pipeline is an activity-centered view and should not auto-jump to category tabs.
1293 else if((current_tab != MOD_TAB_ACTIVE || allow_switch_from_active)
1294 && !_is_module_in_tab(iop_module, current_tab))
1295 _set_current_tab_from_module_group(self, iop_module->default_group());
1296
1297 // If module in current tab but not visible: refresh tab
1298 // This happens when adding new instances or enabling modules through shortcuts
1299 g_idle_add((GSourceFunc)_update_iop_visibility, self);
1300}
1301
1302static void _lib_modulegroups_refresh(gpointer instance, gpointer user_data)
1303{
1304 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1305 g_idle_add((GSourceFunc)_update_iop_visibility, self);
1306}
1307
1308// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1309// vim: shiftwidth=2 expandtab tabstop=2 cindent
1310// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
Handle default and user-set shortcuts (accelerators)
#define dt_accels_new_darkroom_action(a, b, w, c, d, e, f, g)
#define DT_PRIMARY_MASK
struct dt_ui_t * dt_gui_get_ui(void)
void dt_gui_add_help_link(GtkWidget *widget, char *link)
#define dt_accels_new_darkroom_locked_action(a, b, w, c, d, e, f, g)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
static const float x
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
void dt_dev_modules_update_multishow(dt_develop_t *dev)
Definition develop.c:1545
GtkWidget * window
#define DT_GUI_MODULE(x)
gboolean dt_iop_module_is_in_pipeline(dt_iop_module_t *iop)
Definition imageop.c:778
gboolean dt_iop_is_hidden(dt_iop_module_t *module)
Definition imageop.c:736
gboolean dt_iop_gui_move_module_before(dt_iop_module_t *module, dt_iop_module_t *module_next, const char *reason)
Move a module before another one and commit the GUI-side effects.
Definition imageop.c:707
gboolean dt_iop_module_instance_exists(dt_iop_module_t *iop)
Definition imageop.c:741
gboolean dt_iop_gui_move_module_after(dt_iop_module_t *module, dt_iop_module_t *module_prev, const char *reason)
Move a module after another one and commit the GUI-side effects.
Definition imageop.c:714
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
dt_iop_group_t
Definition imageop.h:154
@ IOP_GROUP_LAST
Definition imageop.h:163
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
@ IOP_GROUP_FILM
Definition imageop.h:157
@ IOP_GROUP_TECHNICAL
Definition imageop.h:162
@ IOP_GROUP_COLOR
Definition imageop.h:158
@ IOP_GROUP_REPAIR
Definition imageop.h:159
@ IOP_GROUP_SHARPNESS
Definition imageop.h:160
@ IOP_GROUP_TONES
Definition imageop.h:156
@ IOP_GROUP_NONE
Definition imageop.h:155
gboolean dt_iop_gui_module_is_visible(dt_iop_module_t *module)
gboolean dt_ioppr_check_can_move_before_iop(GList *iop_list, dt_iop_module_t *module, dt_iop_module_t *module_next)
Validate whether module can be moved before module_next.
Definition iop_order.c:1989
gboolean dt_ioppr_check_can_move_after_iop(GList *iop_list, dt_iop_module_t *module, dt_iop_module_t *module_prev)
Validate whether module can be moved after module_prev.
Definition iop_order.c:2158
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_SHORTCUTS
Definition logging.h:73
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
static dt_modulesgroups_tabs_t _get_current_tab(dt_lib_module_t *self)
dt_modulegroups_dnd_target_t
@ DT_MODULEGROUPS_DND_TARGET_IOP
static void _modulegroups_clear_drop_state(dt_lib_modulegroups_t *d)
Remove all drag-and-drop visual feedback from module headers.
static gboolean _focus_previous_control()
static GList * _find_previous_visible_widget(GList *widgets)
static void _modulegroups_drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, guint info, guint time, gpointer user_data)
static gboolean _focus_next_module(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer user_data)
static void _modulegroups_clear_visible_expanders_cache(dt_lib_modulegroups_t *d)
Drop the cached list of visible expanders used for keyboard module focus.
dt_modulesgroups_tabs_t
@ MOD_TAB_ALL
@ MOD_TAB_REPAIR
@ MOD_TAB_LAST
@ MOD_TAB_EFFECTS
@ MOD_TAB_ACTIVE
@ MOD_TAB_SHARPNESS
@ MOD_TAB_BASIC
@ MOD_TAB_TECHNICAL
static void _modulegroups_setup_drag_source(dt_lib_module_t *self, dt_iop_module_t *module)
Attach the module drag source handlers once to an expander widget.
dt_modulesgroups_tabs_t _modulegroups_cycle_tabs(int tab)
static gboolean _modulegroups_drag_drop(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint time, gpointer user_data)
static GtkWidget * _get_target_container(dt_lib_module_t *self, const dt_iop_module_t *module)
Return the GtkBox currently hosting a module for the active tab.
void gui_cleanup(dt_lib_module_t *self)
static dt_modulesgroups_tabs_t _group_to_tab(dt_iop_group_t group)
static GList * _find_next_visible_widget(GList *widgets)
static void _modulegroups_append_visible_expanders(GtkWidget *widget, GList **widgets)
Append visible module expanders in display order from a page subtree.
static void _ensure_page_widgets(dt_lib_module_t *self)
static void _set_current_tab_from_module_group(dt_lib_module_t *self, dt_iop_group_t group)
static const guint _modulegroups_n_targets
static dt_iop_module_t * _modulegroups_get_dnd_dest_module(GtkWidget *page, const gint y, dt_iop_module_t *module_src)
Find the module header under the current drop position.
static void _modulegroups_track_widget(GtkWidget **slot, GtkWidget *widget)
static gboolean _modulegroups_switch_tab_next(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
static void _modulegroups_drag_data_received(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time, gpointer user_data)
uint32_t container(dt_lib_module_t *self)
static void _set_current_tab(dt_lib_module_t *self, dt_modulesgroups_tabs_t tab)
static void _modulegroups_drag_leave(GtkWidget *widget, GdkDragContext *dc, guint time, gpointer user_data)
static gboolean _update_iop_visibility(gpointer user_data)
static gboolean _is_valid_widget(GtkWidget *widget)
static gboolean _is_module_in_history(const dt_iop_module_t *module)
static const GtkTargetEntry _modulegroups_target_list[]
void gui_init(dt_lib_module_t *self)
static gboolean _modulegroups_drag_motion(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint time, gpointer user_data)
int position()
const char ** views(dt_lib_module_t *self)
dt_modulesgroups_basic_sections_t
@ TAB_BASIC_FILM
@ TAB_BASIC_COLOR
@ TAB_BASIC_TONES
@ TAB_BASIC_LAST
int expandable(dt_lib_module_t *self)
static void _modulegroups_sync_section_label_margins(dt_lib_modulegroups_t *d)
Align the basic-tab section labels with the module expander margins.
static void _modulegroups_refresh_visible_expanders_cache(dt_lib_module_t *self, dt_modulesgroups_tabs_t tab)
Rebuild the visible expander cache for one tab after GUI update.
static gboolean _is_module_in_tab(dt_iop_module_t *module, dt_modulesgroups_tabs_t current_tab)
static gboolean _modulegroups_switch_tab_previous(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
static void _modulegroups_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
static gboolean _scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
static gboolean _modulegroups_reorder_target(GtkWidget *target)
Reorder one page or subgroup container to match reverse pipeline order.
static void _lib_modulegroups_signal_set(gpointer instance, gpointer module, gpointer user_data)
static void _modulegroups_drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
static void _lib_modulegroups_refresh(gpointer instance, gpointer user_data)
static void _switch_page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
static gboolean _modulegroups_move_widget(GtkWidget *widget, GtkWidget *target)
static gboolean _focus_next_control()
static void _focus_widget(GtkWidget *widget)
static void _focus_module(dt_iop_module_t *module)
static gboolean _focus_previous_module(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer user_data)
static double reference(const op_t op, const double x)
const char * name
Definition pdf.h:90
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_DEVELOP_INITIALIZE
This signal is raised when darktable.develop is initialized.
Definition signal.h:172
@ DT_SIGNAL_DEVELOP_IMAGE_CHANGED
This signal is raised when image is changed in darkroom.
Definition signal.h:224
@ DT_SIGNAL_DEVELOP_MODULE_MOVED
This signal is raised when order of modules in pipeline is changed.
Definition signal.h:221
@ DT_SIGNAL_DEVELOP_MODULEGROUPS_SET
This signal is raised to request a modulegroups update. 1 : dt_iop_module_t *module,...
Definition signal.h:194
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
GList * iop
Definition develop.h:269
struct dt_iop_module_t * gui_module
Definition develop.h:170
GtkWidget * scroll_to[2]
GtkWidget * scroll_to_header_once
The dt_gui_module_t type is the intersection between a dt_lib_module_t and a dt_iop_module_t structur...
GtkWidget * header
Definition imageop_gui.h:53
GtkWidget * expander
Definition imageop_gui.h:57
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
gboolean enabled
Definition imageop.h:313
char plugin_name[128]
Definition lib.h:81
GModule *void * data
Definition lib.h:79
GtkWidget * widget
Definition lib.h:83
GtkWidget * containers[TAB_BASIC_LAST]
GtkWidget * sections[TAB_BASIC_LAST]
GtkWidget * drag_highlight
dt_modulesgroups_tabs_t visible_expanders_tab
GtkWidget * pages[MOD_TAB_LAST]
dt_iop_module_t * drag_source
GtkWidget * grid
Definition thumbtable.h:110
dt_thumbtable_t * thumbtable_filmstrip
GtkWidget * notebook
char * dt_get_help_url(char *name)
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
GtkWidget * dt_widget_scroll_focus(void)
GdkEventMask dt_widget_scroll_mask(void)
void dt_widget_set_scroll_focus(GtkWidget *widget)
#define DT_GUI_BOX_SPACING
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
GtkBox * dt_ui_get_container(dt_ui_t *ui, const dt_ui_container_t c)
@ DT_UI_CONTAINER_PANEL_RIGHT_CENTER
@ DT_UI_CONTAINER_PANEL_RIGHT_TOP