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 "bauhaus/bauhaus.h"
49#include "common/darktable.h"
50#include "common/debug.h"
51#include "common/image_cache.h"
52#include "common/iop_order.h"
53#include "control/conf.h"
54#include "control/control.h"
55#include "control/signal.h"
56#include "develop/develop.h"
57#include "develop/imageop.h"
58#include "gui/gtk.h"
59#include "libs/lib.h"
60#include "libs/lib_api.h"
61
62DT_MODULE(1)
63
64#define DT_IOP_ORDER_INFO (darktable.unmuted & DT_DEBUG_IOPORDER)
65
77
85
98
99
101{
102 switch(group)
103 {
104 case IOP_GROUP_TONES:
105 case IOP_GROUP_COLOR:
106 case IOP_GROUP_FILM:
107 return MOD_TAB_BASIC;
108
110 return MOD_TAB_EFFECTS;
111
113 return MOD_TAB_SHARPNESS;
114
116 return MOD_TAB_TECHNICAL;
117
118 case IOP_GROUP_REPAIR:
119 return MOD_TAB_REPAIR;
120
121 case IOP_GROUP_LAST:
122 case IOP_GROUP_NONE:
123 return MOD_TAB_ALL;
124 }
125 return MOD_TAB_ACTIVE;
126}
131
132static const GtkTargetEntry _modulegroups_target_list[] = {
133 { "iop", GTK_TARGET_SAME_APP, DT_MODULEGROUPS_DND_TARGET_IOP }
134};
135static const guint _modulegroups_n_targets = G_N_ELEMENTS(_modulegroups_target_list);
136
137/* toggle button callback */
138static void _switch_page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data);
139/* helper function to update iop module view depending on group */
140static gboolean _update_iop_visibility(gpointer user_data);
141
142static void _lib_modulegroups_signal_set(gpointer instance, gpointer module, gpointer user_data);
143static void _lib_modulegroups_refresh(gpointer instance, gpointer user_data);
144
145static gboolean _is_module_in_history(const dt_iop_module_t *module);
147static gboolean _modulegroups_reorder_target(GtkWidget *target);
148
159{
160 GtkWidget *reference = NULL;
161 for(const GList *modules = g_list_first(darktable.develop->iop); modules; modules = g_list_next(modules))
162 {
163 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
164 if(!dt_iop_is_hidden(module) && module->expander)
165 {
166 reference = module->expander;
167 break;
168 }
169 }
170 if(IS_NULL_PTR(reference)) return;
171
172 GtkBorder margin = { 0 };
173 GtkStyleContext *context = gtk_widget_get_style_context(reference);
174 gtk_style_context_get_margin(context, gtk_style_context_get_state(context), &margin);
175
176 for(size_t i = 0; i < TAB_BASIC_LAST; i++)
177 {
178 gtk_widget_set_margin_start(d->sections[i], margin.left);
179 gtk_widget_set_margin_end(d->sections[i], margin.right);
180 gtk_widget_set_margin_top(d->sections[i], margin.top);
181 gtk_widget_set_margin_bottom(d->sections[i], margin.bottom);
182 }
183}
184
194{
195 if(d && d->drag_highlight)
196 {
197 gtk_drag_unhighlight(d->drag_highlight);
198 d->drag_highlight = NULL;
199 }
200
201 if(IS_NULL_PTR(darktable.develop)) return;
202
203 /* Walk every module and clear the before/after classes that motion handlers add. */
204 for(const GList *modules = g_list_last(darktable.develop->iop); modules; modules = g_list_previous(modules))
205 {
206 dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
207 if(!module->expander) continue;
208 dt_gui_remove_class(module->expander, "iop_drop_after");
209 dt_gui_remove_class(module->expander, "iop_drop_before");
210 }
211}
212
223static void _modulegroups_append_visible_expanders(GtkWidget *widget, GList **widgets)
224{
225 if(!GTK_IS_WIDGET(widget) || !gtk_widget_is_visible(widget)) return;
226
227 if(g_object_get_data(G_OBJECT(widget), "dt-module"))
228 {
229 *widgets = g_list_append(*widgets, widget);
230 return;
231 }
232
233 if(!GTK_IS_CONTAINER(widget)) return;
234
235 /* Recurse over the current page subtree to preserve the visual order seen by the user. */
236 GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
237 for(GList *child = children; child; child = g_list_next(child))
238 _modulegroups_append_visible_expanders(GTK_WIDGET(child->data), widgets);
239 g_list_free(children);
240}
241
251{
252 if(IS_NULL_PTR(d)) return;
253 g_list_free(d->visible_expanders);
254 d->visible_expanders = NULL;
255 d->visible_expanders_tab = MOD_TAB_LAST;
256}
257
269{
272 if(IS_NULL_PTR(d) || IS_NULL_PTR(d->pages[tab])) return;
273
274 _modulegroups_append_visible_expanders(d->pages[tab], &d->visible_expanders);
275 d->visible_expanders_tab = tab;
276}
277
291 dt_iop_module_t *module_src)
292{
293 if(!GTK_IS_WIDGET(page) || !module_src) return NULL;
294
295 GtkAllocation source_allocation = { 0 };
296 gtk_widget_get_allocation(module_src->header, &source_allocation);
297 const int y_slop = source_allocation.height / 2;
298 gboolean after_src = TRUE;
299 dt_iop_module_t *module_dest = NULL;
300
301 GList *children = NULL;
303
304 /* Walk the displayed headers in page coordinates and pick the closest valid insertion anchor. */
305 for(GList *l = children; l; l = g_list_next(l))
306 {
307 GtkWidget *w = GTK_WIDGET(l->data);
308 if(w == module_src->expander) after_src = FALSE;
309
310 int widget_x = 0;
311 int widget_y = 0;
312 if(!gtk_widget_translate_coordinates(w, page, 0, 0, &widget_x, &widget_y)) continue;
313
314 GtkAllocation allocation = { 0 };
315 gtk_widget_get_allocation(w, &allocation);
316 if((after_src && y <= widget_y + y_slop)
317 || (!after_src && y <= widget_y + allocation.height + y_slop))
318 {
319 module_dest = (dt_iop_module_t *)g_object_get_data(G_OBJECT(w), "dt-module");
320 break;
321 }
322 }
323
324 g_list_free(children);
325 return module_dest;
326}
327
328static void _modulegroups_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data);
329static void _modulegroups_drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data);
330static void _modulegroups_drag_data_get(GtkWidget *widget, GdkDragContext *context,
331 GtkSelectionData *selection_data, guint info, guint time,
332 gpointer user_data);
333static gboolean _modulegroups_drag_drop(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
334 guint time, gpointer user_data);
335static gboolean _modulegroups_drag_motion(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
336 guint time, gpointer user_data);
337static void _modulegroups_drag_data_received(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
338 GtkSelectionData *selection_data, guint info, guint time,
339 gpointer user_data);
340static void _modulegroups_drag_leave(GtkWidget *widget, GdkDragContext *dc, guint time, gpointer user_data);
341
343{
344 *slot = widget;
345 g_object_add_weak_pointer(G_OBJECT(widget), (gpointer *)slot);
346}
347
348// Because pages are attached to the view right center container,
349// and not the our module widget, they are not stable across the lifetime
350// of the module, meaning they can get deleted anytime be the view.
351// So we need to recreate them dynamically on demand.
353{
356 if(IS_NULL_PTR(root)) return;
357
358 // Prepare tab pages
359 for(int i = 0; i < MOD_TAB_LAST; i++)
360 {
361 if(!IS_NULL_PTR(d->pages[i])) continue;
362 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
363 dt_gui_add_class(container, "module-groups-container");
365 gtk_widget_show(d->pages[i]);
366 gtk_drag_dest_set(d->pages[i], 0, _modulegroups_target_list, _modulegroups_n_targets, GDK_ACTION_COPY);
367 g_signal_connect(d->pages[i], "drag-data-received", G_CALLBACK(_modulegroups_drag_data_received), self);
368 g_signal_connect(d->pages[i], "drag-drop", G_CALLBACK(_modulegroups_drag_drop), self);
369 g_signal_connect(d->pages[i], "drag-motion", G_CALLBACK(_modulegroups_drag_motion), self);
370 g_signal_connect(d->pages[i], "drag-leave", G_CALLBACK(_modulegroups_drag_leave), self);
371 gtk_box_pack_start(root, d->pages[i], FALSE, FALSE, 0);
372 }
373
374 // Prepare the basic tab page
375 for(int i = 0; i < TAB_BASIC_LAST; i++)
376 {
377 if(IS_NULL_PTR(d->sections[i]))
378 {
379 switch(i)
380 {
381 case TAB_BASIC_COLOR:
382 _modulegroups_track_widget(&d->sections[i], dt_ui_section_label_new(_("color")));
383 break;
384 case TAB_BASIC_FILM:
385 _modulegroups_track_widget(&d->sections[i], dt_ui_section_label_new(_("film")));
386 break;
387 case TAB_BASIC_TONES:
388 _modulegroups_track_widget(&d->sections[i], dt_ui_section_label_new(_("tones")));
389 break;
390 }
391 gtk_box_pack_start(GTK_BOX(d->pages[MOD_TAB_BASIC]), d->sections[i], FALSE, FALSE, 0);
392 gtk_widget_show(d->sections[i]);
393 }
394 if(IS_NULL_PTR(d->containers[i]))
395 {
396 GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
397 dt_gui_add_class(container, "module-groups-container");
398 _modulegroups_track_widget(&d->containers[i], container);
399 gtk_box_pack_start(GTK_BOX(d->pages[MOD_TAB_BASIC]), d->containers[i], FALSE, FALSE, 0);
400 gtk_widget_show(d->containers[i]);
401 }
402 }
403}
404
406{
407 return CLAMP(tab, 0, MOD_TAB_LAST - 1);
408}
409
411{
413 if(!IS_NULL_PTR(d) && GTK_IS_NOTEBOOK(d->notebook))
414 {
415 const gint page = gtk_notebook_get_current_page(GTK_NOTEBOOK(d->notebook));
416 if(page >= 0 && page < MOD_TAB_LAST) return (dt_modulesgroups_tabs_t)page;
417 }
418
419 // Fall back to persisted state when notebook page is temporarily unavailable.
420 return _modulegroups_cycle_tabs(dt_conf_get_int("plugins/darkroom/moduletab"));
421}
422
424{
426 if(!IS_NULL_PTR(d) && GTK_IS_NOTEBOOK(d->notebook))
427 gtk_notebook_set_current_page(GTK_NOTEBOOK(d->notebook), tab);
428}
429
431{
432 const dt_modulesgroups_tabs_t tab = _group_to_tab(group);
433 _set_current_tab(self, tab);
434}
435
436static gboolean _is_module_in_tab(dt_iop_module_t *module, dt_modulesgroups_tabs_t current_tab)
437{
438 if(IS_NULL_PTR(module) || dt_iop_is_hidden(module)) return FALSE;
439
440 switch(current_tab)
441 {
442 case MOD_TAB_ACTIVE:
443 return (_is_module_in_history(module) || module->enabled);
444 case MOD_TAB_ALL:
445 return (_is_module_in_history(module) || module->enabled || !(module->flags() & IOP_FLAGS_DEPRECATED));
446
447 default:
448 {
449 const dt_iop_group_t group = module->default_group();
450 const dt_modulesgroups_tabs_t tab = _group_to_tab(group);
451 return (tab == current_tab) && (_is_module_in_history(module) || module->enabled || !(module->flags() & IOP_FLAGS_DEPRECATED));
452 }
453 }
454
455 return FALSE;
456}
457
469{
472
473 switch(tab)
474 {
475 case MOD_TAB_BASIC:
476 {
477 if(IS_NULL_PTR(module)) return NULL;
478
479 // IOP groups tones, color and film go from 1 to 3,
480 // and sections go from 0 to 2, so we simply manage the offset.
481 dt_iop_group_t group = module->default_group();
482 if(group == IOP_GROUP_TONES)
483 return d->containers[TAB_BASIC_TONES];
484 if(group == IOP_GROUP_COLOR)
485 return d->containers[TAB_BASIC_COLOR];
486 if(group == IOP_GROUP_FILM)
487 return d->containers[TAB_BASIC_FILM];
488 return NULL;
489 }
490
491 default:
492 return d->pages[tab];
493 }
494}
495
506{
507 GtkWidget *widget = module->expander;
508 g_object_set_data(G_OBJECT(widget), "dt-module", module);
509
510 if(g_object_get_data(G_OBJECT(widget), "modulegroups-dnd")) return;
511
512 gtk_drag_source_set(widget, GDK_BUTTON1_MASK, _modulegroups_target_list, _modulegroups_n_targets, GDK_ACTION_COPY);
513 g_signal_connect(widget, "drag-begin", G_CALLBACK(_modulegroups_drag_begin), self);
514 g_signal_connect(widget, "drag-data-get", G_CALLBACK(_modulegroups_drag_data_get), self);
515 g_signal_connect(widget, "drag-end", G_CALLBACK(_modulegroups_drag_end), self);
516 g_object_set_data(G_OBJECT(widget), "modulegroups-dnd", GINT_TO_POINTER(TRUE));
517}
518
530{
531 gboolean has_visible = FALSE;
532 int position = 0;
533
534 /* Walk the whole pipeline in reverse order and keep only the modules currently parented here. */
535 for(GList *modules = g_list_last(darktable.develop->iop); modules; modules = g_list_previous(modules))
536 {
537 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
538 if(dt_iop_is_hidden(module) || !module->expander || !gtk_widget_get_visible(module->expander)) continue;
539 if(gtk_widget_get_parent(module->expander) != target) continue;
540
541 gtk_box_reorder_child(GTK_BOX(target), module->expander, position++);
542 has_visible = TRUE;
543 }
544
545 return has_visible;
546}
547
548static void _modulegroups_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
549{
550 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
552 dt_iop_module_t *module_src = (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module");
553
554 d->drag_source = module_src;
556 g_object_set_data(G_OBJECT(widget), "dt-module-dragged", GINT_TO_POINTER(TRUE));
557
558 if(!module_src || !module_src->header) return;
559
560 GdkWindow *window = gtk_widget_get_parent_window(module_src->header);
561 if(IS_NULL_PTR(window)) return;
562
563 GtkAllocation allocation = { 0 };
564 gtk_widget_get_allocation(module_src->header, &allocation);
565 cairo_surface_t *surface = dt_cairo_image_surface_create(CAIRO_FORMAT_RGB24, allocation.width, allocation.height);
566 cairo_t *cr = cairo_create(surface);
567
568 dt_gui_add_class(module_src->header, "iop_drag_icon");
569 gtk_widget_draw(module_src->header, cr);
570 dt_gui_remove_class(module_src->header, "iop_drag_icon");
571
572 cairo_surface_set_device_offset(surface, -allocation.width * darktable.gui->ppd / 2,
573 -allocation.height * darktable.gui->ppd / 2);
574 gtk_drag_set_icon_surface(context, surface);
575
576 cairo_destroy(cr);
577 cairo_surface_destroy(surface);
578}
579
580static void _modulegroups_drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
581{
582 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
584
586 d->drag_source = NULL;
587 g_object_set_data(G_OBJECT(widget), "dt-module-dragged", NULL);
588}
589
590static void _modulegroups_drag_data_get(GtkWidget *widget, GdkDragContext *context,
591 GtkSelectionData *selection_data, guint info, guint time,
592 gpointer user_data)
593{
594 const guint number_data = 1;
595 gtk_selection_data_set(selection_data, gdk_atom_intern("iop", TRUE), 32, (const guchar *)&number_data, 1);
596}
597
598static gboolean _modulegroups_drag_drop(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
599 guint time, gpointer user_data)
600{
601 gtk_drag_get_data(widget, dc, gdk_atom_intern("iop", TRUE), time);
602 return TRUE;
603}
604
605static gboolean _modulegroups_drag_motion(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
606 guint time, gpointer user_data)
607{
608 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
610 dt_iop_module_t *module_src = d->drag_source;
611 if(IS_NULL_PTR(module_src)) return FALSE;
612
613 dt_iop_module_t *module_dest = _modulegroups_get_dnd_dest_module(widget, y, module_src);
614 gboolean can_move = FALSE;
616
617 if(module_dest && module_src != module_dest)
618 {
619 if(module_src->iop_order < module_dest->iop_order)
620 can_move = dt_ioppr_check_can_move_after_iop(darktable.develop->iop, module_src, module_dest);
621 else
622 can_move = dt_ioppr_check_can_move_before_iop(darktable.develop->iop, module_src, module_dest);
623 }
624
625 if(!can_move)
626 {
627 gdk_drag_status(dc, 0, time);
628 return FALSE;
629 }
630
631 if(module_src->iop_order < module_dest->iop_order)
632 dt_gui_add_class(module_dest->expander, "iop_drop_after");
633 else
634 dt_gui_add_class(module_dest->expander, "iop_drop_before");
635
636 d->drag_highlight = module_dest->expander;
637 gtk_drag_highlight(module_dest->expander);
638 gdk_drag_status(dc, GDK_ACTION_COPY, time);
639 return TRUE;
640}
641
642static void _modulegroups_drag_data_received(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
643 GtkSelectionData *selection_data, guint info, guint time,
644 gpointer user_data)
645{
646 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
648 dt_iop_module_t *module_src = d->drag_source;
649 dt_iop_module_t *module_dest = _modulegroups_get_dnd_dest_module(widget, y, module_src);
650
651 if(module_src && module_dest && module_src != module_dest)
652 {
653 if(module_src->iop_order < module_dest->iop_order)
654 dt_iop_gui_move_module_after(module_src, module_dest, "_modulegroups_drag_data_received");
655 else
656 dt_iop_gui_move_module_before(module_src, module_dest, "_modulegroups_drag_data_received");
657 }
658
659 gtk_drag_finish(dc, TRUE, FALSE, time);
661 d->drag_source = NULL;
662}
663
664static void _modulegroups_drag_leave(GtkWidget *widget, GdkDragContext *dc, guint time, gpointer user_data)
665{
666 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
669}
670
671const char *name(struct dt_lib_module_t *self)
672{
673 return _("modulegroups");
674}
675
676const char **views(dt_lib_module_t *self)
677{
678 static const char *v[] = { "darkroom", NULL };
679 return v;
680}
681
683{
685}
686
687
688/* this module should always be shown without expander */
690{
691 return 0;
692}
693
695{
696 return 999;
697}
698
699
700static gboolean _modulegroups_switch_tab_next(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
701 GdkModifierType modifier, gpointer data)
702{
703 dt_lib_module_t *self = (dt_lib_module_t *)data;
705 if(focused) dt_iop_gui_set_expanded(focused, FALSE, TRUE);
706
707 const dt_modulesgroups_tabs_t current = _get_current_tab(self);
708 _set_current_tab(self, _modulegroups_cycle_tabs(current + 1));
710
711 return TRUE;
712}
713
714static gboolean _modulegroups_switch_tab_previous(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
715 GdkModifierType modifier, gpointer data)
716{
717 dt_lib_module_t *self = (dt_lib_module_t *)data;
719 if(focused) dt_iop_gui_set_expanded(focused, FALSE, TRUE);
720
721 const dt_modulesgroups_tabs_t current = _get_current_tab(self);
722 _set_current_tab(self, _modulegroups_cycle_tabs(current - 1));
724
725 return TRUE;
726}
727
728static gboolean _scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
729{
730 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
731 int delta_x, delta_y;
732 if(dt_gui_get_scroll_unit_deltas(event, &delta_x, &delta_y))
733 {
734 const dt_modulesgroups_tabs_t current = _get_current_tab(self);
735 dt_modulesgroups_tabs_t future = 0;
736 if(delta_x > 0. || delta_y > 0.)
737 future = current + 1;
738 else if(delta_x < 0. || delta_y < 0.)
739 future = current - 1;
740
743 }
744
745 return TRUE;
746}
747
748
749static void _focus_module(dt_iop_module_t *module)
750{
751 if(module && dt_iop_gui_module_is_visible(module))
752 {
753 dt_iop_request_focus(module);
755 }
756 else
757 {
758 // we reached the extremity of the list.
760 }
761}
762
763static gboolean _focus_previous_module(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
764 GdkModifierType modifier, gpointer user_data)
765{
766 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
769
770 // When filmstrip owns keyboard focus, keep PageUp routed to filmstrip navigation.
772 if(!IS_NULL_PTR(filmstrip) && !IS_NULL_PTR(filmstrip->grid) && gtk_widget_has_focus(filmstrip->grid))
773 return FALSE;
774 if(d->visible_expanders_tab != _get_current_tab(self))
775 return TRUE;
776
777 const GList *children = d->visible_expanders;
778 if(IS_NULL_PTR(focused))
779 {
780 GList *first = g_list_first((GList *)children);
781 GtkWidget *widget = first ? GTK_WIDGET(first->data) : NULL;
782 _focus_module(widget ? (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module") : NULL);
783 }
784 else
785 {
786 dt_iop_module_t *target = NULL;
787
788 /* Page Up follows the displayed module order. The basic tab is split in
789 * section containers, so we walk the actual visible expander tree instead
790 * of the pipeline list. */
791 for(const GList *module = g_list_first((GList *)children); module; module = g_list_next(module))
792 {
793 GtkWidget *widget = GTK_WIDGET(module->data);
794 if(widget == focused->expander) break;
795 target = (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module");
796 }
797 if(IS_NULL_PTR(target))
798 {
799 GList *last = g_list_last((GList *)children);
800 GtkWidget *widget = last ? GTK_WIDGET(last->data) : NULL;
801 target = widget ? (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module") : NULL;
802 }
803
805 _focus_module(target);
806 }
807
808 return TRUE;
809}
810
811static gboolean _focus_next_module(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
812 GdkModifierType modifier, gpointer user_data)
813{
814 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
817
818 // When filmstrip owns keyboard focus, keep PageDown routed to filmstrip navigation.
820 if(!IS_NULL_PTR(filmstrip) && !IS_NULL_PTR(filmstrip->grid) && gtk_widget_has_focus(filmstrip->grid))
821 return FALSE;
822 if(d->visible_expanders_tab != _get_current_tab(self))
823 return TRUE;
824
825 const GList *children = d->visible_expanders;
826 if(IS_NULL_PTR(focused))
827 {
828 GList *last = g_list_last((GList *)children);
829 GtkWidget *widget = last ? GTK_WIDGET(last->data) : NULL;
830 _focus_module(widget ? (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module") : NULL);
831 }
832 else
833 {
834 dt_iop_module_t *target = NULL;
835
836 /* Page Down follows the displayed module order. The basic tab is split in
837 * section containers, so we walk the actual visible expander tree instead
838 * of the pipeline list. */
839 for(const GList *module = g_list_last((GList *)children); module; module = g_list_previous(module))
840 {
841 GtkWidget *widget = GTK_WIDGET(module->data);
842 if(widget == focused->expander) break;
843 target = (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module");
844 }
845 if(IS_NULL_PTR(target))
846 {
847 GList *first = g_list_first((GList *)children);
848 GtkWidget *widget = first ? GTK_WIDGET(first->data) : NULL;
849 target = widget ? (dt_iop_module_t *)g_object_get_data(G_OBJECT(widget), "dt-module") : NULL;
850 }
851
853 _focus_module(target);
854 }
855
856 return TRUE;
857}
858
859static gboolean _is_valid_widget(GtkWidget *widget)
860{
861 if(IS_NULL_PTR(widget))
862 {
863 dt_print(DT_DEBUG_SHORTCUTS, "[modulegroups] _is_valid_widget skip: widget=NULL\n");
864 return FALSE;
865 }
866
867 // The parent will always be a GtkBox
868 GtkWidget *parent = gtk_widget_get_parent(widget);
869 if(IS_NULL_PTR(parent))
870 {
871 dt_print(DT_DEBUG_SHORTCUTS, "[modulegroups] _is_valid_widget skip: parent=NULL widget=%s\n",
872 gtk_widget_get_name(widget));
873 return FALSE;
874 }
875
876 GtkWidget *grandparent = gtk_widget_get_parent(parent);
877 if(IS_NULL_PTR(grandparent))
878 {
879 dt_print(DT_DEBUG_SHORTCUTS, "[modulegroups] _is_valid_widget skip: grandparent=NULL widget=%s parent=%s\n",
880 gtk_widget_get_name(widget), gtk_widget_get_name(parent));
881 return FALSE;
882 }
883
884 GType type = G_OBJECT_TYPE(grandparent);
885
886 gboolean visible_parent = TRUE;
887
888 if(type == GTK_TYPE_NOTEBOOK)
889 {
890 // Find the page in which the current widget is and try to show it
891 gint page_num = gtk_notebook_page_num(GTK_NOTEBOOK(grandparent), parent);
892 if(page_num > -1)
893 gtk_notebook_set_current_page(GTK_NOTEBOOK(grandparent), page_num);
894 else
895 visible_parent = FALSE;
896 }
897 else if(type == GTK_TYPE_STACK)
898 {
899 // Stack pages are enabled based on user parameteters,
900 // so if not visible, then do nothing.
901 GtkWidget *visible_child = gtk_stack_get_visible_child(GTK_STACK(grandparent));
902 if(visible_child != parent) visible_parent = FALSE;
903 }
904
905 return gtk_widget_is_visible(widget) && gtk_widget_is_sensitive(widget)
906 && visible_parent;
907}
908
909
910// Because Gtk can't focus on invisible widgets without errors
911// (and weird behaviour on user's end), getting the first widget in the list is not enough.
912static GList *_find_next_visible_widget(GList *widgets)
913{
914 for(GList *first = widgets; first; first = g_list_next(first))
915 {
916 GtkWidget *widget = (GtkWidget *)first->data;
917 if(_is_valid_widget(widget)) return first;
918 }
919 return NULL;
920}
921
922
923static GList *_find_previous_visible_widget(GList *widgets)
924{
925 for(GList *last = widgets; last; last = g_list_previous(last))
926 {
927 GtkWidget *widget = (GtkWidget *)last->data;
928 if(_is_valid_widget(widget)) return last;
929 }
930 return NULL;
931}
932
933static void _focus_widget(GtkWidget *widget)
934{
935 gtk_widget_grab_focus(widget);
937}
938
939
940static gboolean _focus_next_control()
941{
943 dt_gui_module_t *m = DT_GUI_MODULE(focused);
944 if(!focused || !m->widget_list) return FALSE;
945
946 GtkWidget *current_widget = darktable.gui->has_scroll_focus;
947 GList *first_item = _find_next_visible_widget(g_list_first(m->widget_list));
948
949 if(!current_widget && first_item)
950 {
951 // No active widget, start by the first
952 _focus_widget(GTK_WIDGET(first_item->data));
953 }
954 else
955 {
956 GList *current_item = g_list_find(m->widget_list, current_widget);
957 GList *next_item = _find_next_visible_widget(g_list_next(current_item));
958
959 // Select the next visible item, if any
960 if(next_item)
961 _focus_widget(GTK_WIDGET(next_item->data));
962 // Cycle back to the beginning
963 else if(first_item)
964 _focus_widget(GTK_WIDGET(first_item->data));
965 }
966
967 return TRUE;
968}
969
970static gboolean _focus_previous_control()
971{
973 dt_gui_module_t *m = DT_GUI_MODULE(focused);
974 if(!focused || !m->widget_list) return FALSE;
975
976 GtkWidget *current_widget = darktable.gui->has_scroll_focus;
977 GList *last_item = _find_previous_visible_widget(g_list_last(m->widget_list));
978
979 if(!current_widget && last_item)
980 {
981 // No active widget, start by the last
982 _focus_widget(GTK_WIDGET(last_item->data));
983 }
984 else
985 {
986 GList *current_item = g_list_find(m->widget_list, current_widget);
987 GList *previous_item = _find_previous_visible_widget(g_list_previous(current_item));
988
989 // Select the previous item, if any
990 if(previous_item)
991 _focus_widget(GTK_WIDGET(previous_item->data));
992 // Cycle back to the end
993 else if(last_item)
994 _focus_widget(GTK_WIDGET(last_item->data));
995 }
996
997 return TRUE;
998}
999
1001{
1002 /* initialize ui widgets */
1004 self->data = (void *)d;
1005 d->inited = FALSE;
1006 d->visible_expanders = NULL;
1007 d->visible_expanders_tab = MOD_TAB_LAST;
1008
1009 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1011 gtk_widget_set_name(self->widget, "modules-tabs");
1012
1013 /* Tabs */
1014 d->notebook = GTK_WIDGET(gtk_notebook_new());
1015 dt_gui_add_class(d->notebook, "empty");
1016 char *labels[] = { _("Pipeline"), _("Basic"), _("Repair"), _("Sharpness"), _("Effects"), _("Technics"), _("All") };
1017 char *tooltips[]
1018 = { _("List all modules currently enabled in the reverse order of application in the pipeline."),
1019 _("Modules destined to adjust brightness, contrast and dynamic range, work with film scans, and perform color-grading."),
1020 _("Modules destined to repair and reconstruct noisy or missing pixels."),
1021 _("Modules destined to manipulate local contrast, sharpness and blur."),
1022 _("Modules applying special effects."),
1023 _("Technical modules that can be ignored in most situations."),
1024 _("All modules available in the software.") };
1025
1026 for(int i = 0; i < MOD_TAB_LAST; i++)
1027 {
1028 GtkWidget *label = gtk_label_new(labels[i]);
1029 dt_gui_add_class(label, "dt_modulegroups_tab_label");
1030 gtk_widget_set_tooltip_text(label, tooltips[i]);
1031 gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
1032 gtk_widget_set_hexpand(label, TRUE);
1033 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
1034
1035 GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1036 gtk_notebook_append_page(GTK_NOTEBOOK(d->notebook), page, label);
1037 gtk_container_child_set(GTK_CONTAINER(d->notebook), page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
1038 gtk_widget_show(page);
1039
1040 d->pages[i] = NULL;
1041 }
1042 gtk_notebook_set_current_page(GTK_NOTEBOOK(d->notebook), dt_conf_get_int("plugins/darkroom/moduletab"));
1043 gtk_notebook_popup_enable(GTK_NOTEBOOK(d->notebook));
1044 gtk_notebook_set_scrollable(GTK_NOTEBOOK(d->notebook), TRUE);
1045 g_signal_connect(G_OBJECT(d->notebook), "switch_page", G_CALLBACK(_switch_page), self);
1046 g_signal_connect(G_OBJECT(d->notebook), "scroll-event", G_CALLBACK(_scroll_event), self);
1047 gtk_widget_add_events(GTK_WIDGET(d->notebook), darktable.gui->scroll_mask);
1048
1049 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->notebook), TRUE, TRUE, 0);
1050 gtk_widget_show_all(self->widget);
1051
1053 G_CALLBACK(_lib_modulegroups_signal_set), self);
1055 G_CALLBACK(_lib_modulegroups_refresh), self);
1057 G_CALLBACK(_lib_modulegroups_refresh), self);
1059 G_CALLBACK(_lib_modulegroups_refresh), self);
1060 // History edits already trigger explicit modulegroups visibility updates through
1061 // DT_SIGNAL_DEVELOP_MODULEGROUPS_SET. Listening to generic history changes here
1062 // causes unnecessary expander reparenting, which drops native Gtk focus from
1063 // focused Bauhaus controls after committed key/scroll edits.
1064
1066 N_("move to the next modules tab"), GDK_KEY_Tab, GDK_CONTROL_MASK, _("Triggers the action"));
1068 N_("move to the previous modules tab"), GDK_KEY_Tab,
1069 GDK_CONTROL_MASK | GDK_SHIFT_MASK, _("Triggers the action"));
1070
1071 dt_accels_new_darkroom_locked_action(_focus_next_module, self, N_("Darkroom/Actions"),
1072 N_("Focus on the next module"), GDK_KEY_Page_Down, 0, _("Triggers the action"));
1074 N_("Focus on the previous module"), GDK_KEY_Page_Up, 0, _("Triggers the action"));
1075
1076 dt_accels_new_darkroom_locked_action(_focus_next_control, self, N_("Darkroom/Actions"),
1077 N_("Focus on the next module control"), GDK_KEY_Down, GDK_CONTROL_MASK, _("Triggers the action"));
1079 N_("Focus on the previous module control"), GDK_KEY_Up, GDK_CONTROL_MASK, _("Triggers the action"));
1080}
1081
1082static gboolean _modulegroups_move_widget(GtkWidget *widget, GtkWidget *target);
1083
1085{
1088
1089 if(self->data)
1090 {
1095 if(darktable.develop && root)
1096 {
1097 /* Hand module expanders back to the right-panel root before destroying
1098 * our page boxes, otherwise Gtk would destroy the module widgets along
1099 * with the page containers. */
1100 for(GList *modules = g_list_first(darktable.develop->iop); modules; modules = g_list_next(modules))
1101 {
1102 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
1103 if(module->expander) _modulegroups_move_widget(module->expander, GTK_WIDGET(root));
1104 }
1105 }
1106 for(int i = 0; i < MOD_TAB_ALL; i++)
1107 {
1108 if(!IS_NULL_PTR(d->pages[i]) && GTK_IS_WIDGET(d->pages[i]))
1109 gtk_widget_destroy(d->pages[i]);
1110 d->pages[i] = NULL;
1111 }
1112 dt_free(d);
1113 self->data = NULL;
1114 }
1115}
1116
1117static gboolean _is_module_in_history(const dt_iop_module_t *module)
1118{
1119 for(GList *history = g_list_last(darktable.develop->history); history; history = g_list_previous(history))
1120 {
1121 const dt_dev_history_item_t *hitem = (dt_dev_history_item_t *)(history->data);
1122 if(hitem->module == module) return TRUE;
1123 }
1124
1125 return FALSE;
1126}
1127
1128static gboolean _modulegroups_move_widget(GtkWidget *widget, GtkWidget *target)
1129{
1130 if(!GTK_IS_WIDGET(widget) || !GTK_IS_BOX(target)) return FALSE;
1131
1132 GtkWidget *parent = gtk_widget_get_parent(widget);
1133 if(parent == target) return FALSE;
1134
1135 g_object_ref(widget);
1136 if(GTK_IS_CONTAINER(parent)) gtk_container_remove(GTK_CONTAINER(parent), widget);
1137 gtk_box_pack_start(GTK_BOX(target), widget, FALSE, FALSE, 0);
1138 g_object_unref(widget);
1139 return TRUE;
1140}
1141
1142static gboolean _update_iop_visibility(gpointer user_data)
1143{
1144 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1145 if(IS_NULL_PTR(darktable.develop)) return G_SOURCE_REMOVE;
1147 const dt_modulesgroups_tabs_t tab = _get_current_tab(self);
1148
1151
1152 // Update notebook pages visibility
1153 for(int i = 0; i < MOD_TAB_LAST; i++) gtk_widget_set_visible(d->pages[i], i == tab);
1154
1155 /* Walk every develop module and decide whether it belongs to the active tab and which box should host it. */
1156 const int history_end = dt_dev_get_history_end_ext(darktable.develop);
1157
1158 for(GList *modules = g_list_last(darktable.develop->iop); modules; modules = g_list_previous(modules))
1159 {
1160 dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
1161 if(dt_iop_is_hidden(module)) continue; // Hidden modules don't have a widget
1162
1163 GtkWidget *w = module->expander;
1164 if(IS_NULL_PTR(w)) continue; // Module GUI may not be inited yet
1165
1166 const gboolean visible = _is_module_in_tab(module, tab);
1167 GtkWidget *target = _get_target_container(self, module);
1168
1169 // Extra module instances that are "added" by history items past the current history end conceptually don't exist yet.
1170 // They exist as disabled pipeline nodes only because they are referenced by an history entry.
1171 // They will be brought back to life if user move the history end cursor past their history item.
1172 // For consistency, we hide them from GUI until then.
1173 // This doesn't apply to base instances.
1174 // FIXME: at some point, we will need to embrace the nodal paradigm and use a "create instance"
1175 // approach, even for the first instance, instead of mixing GUI toolboxes à la Lightroom for the first
1176 // (base) instance and then nodal approach for the others.
1178 const gboolean in_history = !IS_NULL_PTR(dt_dev_history_get_last_item_by_module(darktable.develop->history, module, history_end));
1180
1181 if(visible && (in_history || module->multi_priority == 0))
1182 {
1183 _modulegroups_setup_drag_source(self, module);
1184 _modulegroups_move_widget(w, target);
1185 gtk_widget_show(w);
1186 }
1187 else
1188 {
1189 if(darktable.develop->gui_module == module)
1190 {
1193 }
1194
1195 gtk_widget_hide(w);
1196 }
1197 }
1198
1199 /* Multishow may hide extra instances, so we only compute section occupancy
1200 * and final ordering after it has settled the visible module set. */
1201 // FIXME: ditch that
1203
1204 // Ensure the module is visible
1206 if(!IS_NULL_PTR(active) && !IS_NULL_PTR(active->expander))
1207 {
1208 if(darktable.gui->scroll_to[1] != active->header)
1209 {
1210 const gboolean scroll_new_instance_to_header
1212 && !IS_NULL_PTR(active->header) && GTK_IS_WIDGET(active->header));
1213
1214 darktable.gui->scroll_to[1] = scroll_new_instance_to_header ? active->header : active->expander;
1215
1216 if(scroll_new_instance_to_header) darktable.gui->scroll_to_header_once = NULL;
1217 }
1218 }
1219
1220 if(tab == MOD_TAB_BASIC)
1221 {
1222 for(int i = 0; i < TAB_BASIC_LAST; i++)
1223 {
1224 const gboolean has_section = _modulegroups_reorder_target(d->containers[i]);
1225 gtk_widget_set_visible(d->sections[i], has_section);
1226 gtk_widget_set_visible(d->containers[i], has_section);
1227 }
1228 }
1229 else
1230 {
1231 _modulegroups_reorder_target(d->pages[tab]);
1232 }
1233
1234 // Ensure the parent get refreshed
1236 gtk_widget_queue_resize(d->pages[tab]);
1237 gtk_widget_queue_draw(d->pages[tab]);
1238
1239 return G_SOURCE_REMOVE;
1240}
1241
1242static void _switch_page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
1243{
1244 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1245 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->data)) return;
1246 g_idle_add((GSourceFunc)_update_iop_visibility, self);
1247 dt_conf_set_int("plugins/darkroom/moduletab", page_num);
1248}
1249
1250static void _lib_modulegroups_signal_set(gpointer instance, gpointer module, gpointer user_data)
1251{
1252 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1253 dt_iop_module_t *iop_module = (dt_iop_module_t *)module;
1254 if(IS_NULL_PTR(iop_module)) return;
1255
1256 const dt_modulesgroups_tabs_t current_tab = _get_current_tab(self);
1257 const gboolean prefer_active_once
1258 = !IS_NULL_PTR(iop_module->expander)
1259 && GPOINTER_TO_INT(g_object_get_data(G_OBJECT(iop_module->expander),
1260 "dt-modulegroups-prefer-active-once"));
1261 const gboolean allow_switch_from_active
1262 = !IS_NULL_PTR(iop_module->expander)
1263 && GPOINTER_TO_INT(g_object_get_data(G_OBJECT(iop_module->expander),
1264 "dt-modulegroups-switch-from-active-once"));
1265 const gboolean module_in_active_tab = _is_module_in_tab(iop_module, MOD_TAB_ACTIVE);
1266 if(!IS_NULL_PTR(iop_module->expander))
1267 {
1268 g_object_set_data(G_OBJECT(iop_module->expander), "dt-modulegroups-prefer-active-once", NULL);
1269 g_object_set_data(G_OBJECT(iop_module->expander), "dt-modulegroups-switch-from-active-once", NULL);
1270 }
1271
1272 // Direct actions (enable/toggle) should prioritize Pipeline for the focused module.
1273 if(prefer_active_once && module_in_active_tab && current_tab != MOD_TAB_ACTIVE)
1275 // Focus-only requests from accel search: stay in Pipeline when the module is
1276 // already there, otherwise jump to the module group tab.
1277 else if(allow_switch_from_active && module_in_active_tab && current_tab != MOD_TAB_ACTIVE)
1279 // If module not in current tab: switch tab
1280 // Keep users on the Pipeline tab when they duplicate/create modules from it.
1281 // Pipeline is an activity-centered view and should not auto-jump to category tabs.
1282 else if((current_tab != MOD_TAB_ACTIVE || allow_switch_from_active)
1283 && !_is_module_in_tab(iop_module, current_tab))
1284 _set_current_tab_from_module_group(self, iop_module->default_group());
1285
1286 // If module in current tab but not visible: refresh tab
1287 // This happens when adding new instances or enabling modules through shortcuts
1288 g_idle_add((GSourceFunc)_update_iop_visibility, self);
1289}
1290
1291static void _lib_modulegroups_refresh(gpointer instance, gpointer user_data)
1292{
1293 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1294 g_idle_add((GSourceFunc)_update_iop_visibility, self);
1295}
1296
1297// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1298// vim: shiftwidth=2 expandtab tabstop=2 cindent
1299// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:278
int type
char * name
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_SHORTCUTS
Definition darktable.h:738
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:456
#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 darktable.h:281
dt_dev_history_item_t * dt_dev_history_get_last_item_by_module(GList *history_list, dt_iop_module_t *module, int history_end)
Find the last history item referencing a module up to history_end.
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1659
void dt_dev_modules_update_multishow(dt_develop_t *dev)
Definition develop.c:1390
#define dt_pthread_rwlock_unlock
Definition dtpthread.h:392
#define dt_pthread_rwlock_rdlock
Definition dtpthread.h:393
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
Definition gtk.c:219
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:143
void dt_gui_add_help_link(GtkWidget *widget, char *link)
Definition gtk.c:2022
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
#define dt_accels_new_darkroom_locked_action(a, b, c, d, e, f, g)
Definition gtk.h:440
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
#define dt_accels_new_darkroom_action(a, b, c, d, e, f, g)
Definition gtk.h:430
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:451
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
GtkBox * dt_ui_get_container(dt_ui_t *ui, const dt_ui_container_t c)
#define DT_GUI_MODULE(x)
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2169
gboolean dt_iop_is_hidden(dt_iop_module_t *module)
Definition imageop.c:1127
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:754
void dt_iop_gui_set_expanded(dt_iop_module_t *module, gboolean expanded, gboolean collapse_others)
Definition imageop.c:2321
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:761
gboolean dt_iop_gui_module_is_visible(dt_iop_module_t *module)
Definition imageop.c:734
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:168
dt_iop_group_t
Definition imageop.h:135
@ IOP_GROUP_LAST
Definition imageop.h:144
@ IOP_GROUP_EFFECTS
Definition imageop.h:142
@ IOP_GROUP_FILM
Definition imageop.h:138
@ IOP_GROUP_TECHNICAL
Definition imageop.h:143
@ IOP_GROUP_COLOR
Definition imageop.h:139
@ IOP_GROUP_REPAIR
Definition imageop.h:140
@ IOP_GROUP_SHARPNESS
Definition imageop.h:141
@ IOP_GROUP_TONES
Definition imageop.h:137
@ IOP_GROUP_NONE
Definition imageop.h:136
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:2015
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:2184
static const float x
const float v
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)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
@ DT_SIGNAL_DEVELOP_INITIALIZE
This signal is raised when darktable.develop is initialized.
Definition signal.h:169
@ DT_SIGNAL_DEVELOP_IMAGE_CHANGED
This signal is raised when image is changed in darkroom.
Definition signal.h:221
@ DT_SIGNAL_DEVELOP_MODULE_MOVED
This signal is raised when order of modules in pipeline is changed.
Definition signal.h:218
@ DT_SIGNAL_DEVELOP_MODULEGROUPS_SET
This signal is raised to request a modulegroups update. 1 : dt_iop_module_t *module,...
Definition signal.h:191
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_develop_t * develop
Definition darktable.h:770
GList * iop
Definition develop.h:279
struct dt_iop_module_t * gui_module
Definition develop.h:165
dt_pthread_rwlock_t history_mutex
Definition develop.h:263
GList * history
Definition develop.h:275
double ppd
Definition gtk.h:200
gint scroll_mask
Definition gtk.h:224
GtkWidget * has_scroll_focus
Definition gtk.h:228
dt_ui_t * ui
Definition gtk.h:164
GtkWidget * scroll_to[2]
Definition gtk.h:221
GtkWidget * scroll_to_header_once
Definition gtk.h:222
The dt_gui_module_t type is the intersection between a dt_lib_module_t and a dt_iop_module_t structur...
gboolean enabled
Definition imageop.h:298
GtkWidget * expander
Definition imageop.h:345
GtkWidget * header
Definition imageop.h:341
char plugin_name[128]
Definition lib.h:82
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
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:100
dt_thumbtable_t * thumbtable_filmstrip
char * dt_get_help_url(char *name)
@ DT_UI_CONTAINER_PANEL_RIGHT_CENTER
@ DT_UI_CONTAINER_PANEL_RIGHT_TOP