Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
window_manager.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2023, 2025-2026 Aurélien PIERRE.
4 Copyright (C) 2023 Luca Zulberti.
5 Copyright (C) 2025 Alynx Zhou.
6
7 Ansel is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 Ansel is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
19*/
20#include "common/darktable.h"
21#include "control/control.h"
22#include "develop/develop.h"
23#include "develop/imageop.h"
24#include "views/view.h"
25#include "bauhaus/bauhaus.h"
26#include "gui/window_manager.h"
27#include "gui/actions/menu.h"
28#include "dtgtk/sidepanel.h"
29#include "libs/lib.h"
30
31#define WINDOW_DEBUG 0
32
45
52
54 = { "header", "toolbar_top", "toolbar_bottom", "left", "right", "bottom" };
55
56
57gchar * panels_get_view_path(char *suffix)
58{
59
60 if(IS_NULL_PTR(darktable.view_manager)) return NULL;
62 if(IS_NULL_PTR(cv)) return NULL;
63 char lay[32] = "";
64
65 if(!strcmp(cv->module_name, "lighttable"))
66 g_snprintf(lay, sizeof(lay), "%d/", 0);
67 else if(!strcmp(cv->module_name, "darkroom"))
68 g_snprintf(lay, sizeof(lay), "%d/", dt_view_darkroom_get_layout(darktable.view_manager));
69
70 return g_strdup_printf("%s/ui/%s%s", cv->module_name, lay, suffix);
71}
72
73gchar * panels_get_panel_path(dt_ui_panel_t panel, char *suffix)
74{
75 gchar *v = panels_get_view_path("");
76 if(IS_NULL_PTR(v)) return NULL;
77 return dt_util_dstrcat(v, "%s%s", _ui_panel_config_names[panel], suffix);
78}
79
81{
82 gchar *key = NULL;
83
85 {
86 int size = 0;
87
88 key = panels_get_panel_path(p, "_size");
90 {
92 dt_free(key);
93 }
94 else // size hasn't been adjusted, so return default sizes
95 {
98 else
100
101 if(!IS_NULL_PTR(key)) dt_free(key);
102 }
103 return size;
104 }
105 return -1;
106}
107
109{
110 g_return_val_if_fail(GTK_IS_WIDGET(ui->panels[p]), FALSE);
111 return gtk_widget_is_ancestor(w, ui->panels[p]) || gtk_widget_is_ancestor(ui->panels[p], w);
112}
113
115{
116 return ui->center;
117}
119{
120 return ui->center_base;
121}
122
124{
125 return ui->log_msg;
126}
128{
129 return ui->toast_msg;
130}
131
133{
134 return ui->main_window;
135}
136
138{
139 return GTK_BOX(ui->containers[c]);
140}
141
143{
144 switch(c)
145 {
146 /* These should be flexboxes/flowboxes so line wrapping is turned on when line width is too small to contain everything
147 * but flexboxes don't seem to work here as advertised (everything either goes to new line or same line, no wrapping),
148 * maybe because they will get added to boxes at the end, and Gtk heuristics to decide final width are weird.
149 */
150 /* if box is right lets pack at end for nicer alignment */
151 /* if box is center we want it to fill as much as it can */
153 gtk_box_pack_start(GTK_BOX(ui->containers[c]), w, TRUE, TRUE, 0);
154 break;
155
156 default:
157 gtk_box_pack_start(GTK_BOX(ui->containers[c]), w, FALSE, FALSE, 0);
158 break;
159 }
160 gtk_widget_show_all(w);
161}
162
163static void _ui_init_panel_size(GtkWidget *widget, dt_ui_t *ui)
164{
165 gchar *key = NULL;
166 int s = DT_UI_PANEL_SIDE_DEFAULT_SIZE; // default panel size
167 if(strcmp(gtk_widget_get_name(widget), "right") == 0)
168 {
171 s = MAX(dt_conf_get_int(key), 120);
172 if(key) gtk_widget_set_size_request(widget, s, -1);
173 }
174 else if(strcmp(gtk_widget_get_name(widget), "left") == 0)
175 {
178 s = MAX(dt_conf_get_int(key), 120);
179 if(key) gtk_widget_set_size_request(widget, s, -1);
180 }
181 else if(strcmp(gtk_widget_get_name(widget), "bottom") == 0)
182 {
184 s = DT_UI_PANEL_BOTTOM_DEFAULT_SIZE; // default panel size
186 s = MAX(dt_conf_get_int(key), 48);
187 if(key) gtk_widget_set_size_request(widget, -1, s);
188 }
189
190 dt_free(key);
191}
192
194{
195 /* restore left & right panel size */
199
200 /* restore from a previous collapse all panel state if enabled */
201 gchar *key = panels_get_view_path("panel_collaps_state");
202 const uint32_t state = dt_conf_get_int(key);
203 dt_free(key);
204 if(state)
205 {
206 /* hide all panels (we let saved state as it is, to recover them when pressing TAB)*/
207 for(int k = 0; k < DT_UI_PANEL_SIZE; k++) dt_ui_panel_show(ui, k, FALSE, FALSE);
208 }
209 else
210 {
211 /* restore the visible state of panels */
212 for(int k = 0; k < DT_UI_PANEL_SIZE; k++)
213 {
214 key = panels_get_panel_path(k, "_visible");
217 else
219
220 dt_free(key);
221 }
222 }
223}
224
225/* The main panels share the generic resize-handle primitive (dt_bauhaus_resize_handle_new),
226 * so they get the same grip visual, hover border and cursor as every other resizable area.
227 * These two callbacks let the handle query and apply the panel size; the panel widget is passed
228 * as user_data and identified by its name ("left"/"right"/"bottom"). */
229static int _panel_handle_get_size(gpointer user_data)
230{
231 GtkWidget *widget = GTK_WIDGET(user_data);
232 const gboolean bottom = (strcmp(gtk_widget_get_name(widget), "bottom") == 0);
233 gint w = 0, h = 0;
234 gtk_widget_get_size_request(widget, &w, &h);
235 if(bottom) return (h > 0) ? h : gtk_widget_get_allocated_height(widget);
236 return (w > 0) ? w : gtk_widget_get_allocated_width(widget);
237}
238
239static int _panel_handle_resize(int requested_size, gboolean finished, gpointer user_data)
240{
241 GtkWidget *widget = GTK_WIDGET(user_data);
242 const char *name = gtk_widget_get_name(widget);
244 int win_w = 0, win_h = 0;
245 gtk_window_get_size(GTK_WINDOW(window), &win_w, &win_h);
246
247 gchar *key = NULL;
248 int size = requested_size;
249 if(strcmp(name, "right") == 0)
250 {
251 size = CLAMP(requested_size, 150, win_w / 2);
253 gtk_widget_set_size_request(widget, size, -1);
254 }
255 else if(strcmp(name, "left") == 0)
256 {
257 size = CLAMP(requested_size, 150, win_w / 2);
259 gtk_widget_set_size_request(widget, size, -1);
260 }
261 else if(strcmp(name, "bottom") == 0)
262 {
263 size = CLAMP(requested_size, 48, win_h / 3);
265 gtk_widget_set_size_request(widget, -1, size);
266 }
267
268 // Persist only when the gesture ends, but apply the size live during the drag.
269 if(finished && key) dt_conf_set_int(key, size);
270 dt_free(key);
271
272 return size;
273}
274
275/* initialize the top container of panel */
277{
278 GtkWidget *w = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
279 gtk_box_pack_start(GTK_BOX(container), w, FALSE, FALSE, 0);
280 return w;
281}
282
294static gboolean _ui_scroll_target_is_live_widget(const GtkWidget *target, const int side)
295{
296 if(IS_NULL_PTR(target)) return FALSE;
297 if(side != LEFT_PANNEL && side != RIGHT_PANNEL) return FALSE;
298
300 {
301 // Walk all lib modules and look for the exact expander address queued for scrolling.
302 for(const GList *libs = darktable.lib->plugins; libs; libs = g_list_next(libs))
303 {
304 const dt_lib_module_t *module = (const dt_lib_module_t *)libs->data;
305 if(!IS_NULL_PTR(module) && module->expander == target) return TRUE;
306 }
307 }
308
310 {
311 // Walk darkroom iop modules and accept either header or expander scroll anchors.
312 for(const GList *iops = darktable.develop->iop; iops; iops = g_list_next(iops))
313 {
314 const dt_iop_module_t *module = (const dt_iop_module_t *)iops->data;
315 if(IS_NULL_PTR(module)) continue;
316 if(module->expander == target || module->header == target) return TRUE;
317 }
318 }
319
320 return FALSE;
321}
322
323// this should work as long as everything happens in the gui thread
324static void _ui_panel_size_changed(GtkAdjustment *adjustment, GParamSpec *pspec, gpointer user_data)
325{
326 GtkAllocation allocation;
327 static float last_height[PANEL_SIDE_COUNT] = { 0 };
328
329 const int side = GPOINTER_TO_INT(user_data);
330 if(side != LEFT_PANNEL && side != RIGHT_PANNEL) return;
331
332 // don't do anything when the size didn't actually change.
333 const float height = gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_lower(adjustment);
334
335 if(height == last_height[side]) return;
336 last_height[side] = height;
337
338 if(IS_NULL_PTR(darktable.gui->scroll_to[side])) return;
340 {
341 darktable.gui->scroll_to[side] = NULL;
342 return;
343 }
344
345 if(GTK_IS_WIDGET(darktable.gui->scroll_to[side]))
346 {
347 gtk_widget_get_allocation(darktable.gui->scroll_to[side], &allocation);
348 gtk_adjustment_set_value(adjustment, allocation.y);
349 }
350
351 darktable.gui->scroll_to[side] = NULL;
352}
353
354/* initialize the center container of panel */
356{
357 GtkWidget *widget;
358 GtkAdjustment *a[4];
359
360 a[0] = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 100, 1, 10, 10));
361 a[1] = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 100, 1, 10, 10));
362 a[2] = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 100, 1, 10, 10));
363 a[3] = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 100, 1, 10, 10));
364
365 /* create the scrolled window */
366 widget = gtk_scrolled_window_new(a[0], a[1]);
367 // Named so the theme can zero its "scrollbar-spacing" style property (a legacy GtkWidget style
368 // property, not a CSS box property), removing the 3px gutter between the modules and the scrollbar.
369 gtk_widget_set_name(widget, "panel-scroll");
370 gtk_widget_set_can_focus(widget, TRUE);
371 gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(widget),
372 left ? GTK_CORNER_TOP_LEFT : GTK_CORNER_TOP_RIGHT);
373 gtk_box_pack_start(GTK_BOX(container), widget, TRUE, TRUE, 0);
374 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
375
376 g_signal_connect(G_OBJECT(gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(widget))), "notify::lower",
377 G_CALLBACK(_ui_panel_size_changed),
378 GINT_TO_POINTER(left ? RIGHT_PANNEL : LEFT_PANNEL));
379
380 /* create the scrolled viewport */
381 container = widget;
382 widget = gtk_viewport_new(a[2], a[3]);
383 gtk_viewport_set_shadow_type(GTK_VIEWPORT(widget), GTK_SHADOW_NONE);
384 gtk_container_add(GTK_CONTAINER(container), widget);
385
386 /* create the container */
387 container = widget;
388 // WARNING: no spacing between modules in left sidebar
389 widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
390 gtk_widget_set_name(widget, "plugins_box");
391 gtk_container_add(GTK_CONTAINER(container), widget);
392
393 return widget;
394}
395
396/* initialize the bottom container of panel */
398{
399 GtkWidget *w = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
400 gtk_box_pack_start(GTK_BOX(container), w, FALSE, FALSE, 0);
401 return w;
402}
403
404/* initialize the whole left panel */
406{
407 GtkWidget *widget;
408
409 /* create left panel main widget and add it to ui */
411 gtk_widget_set_name(widget, "left");
412 _ui_init_panel_size(widget, ui);
413
414 GtkWidget *over = gtk_overlay_new();
415 gtk_container_add(GTK_CONTAINER(over), widget);
416 // resize grip overlaid on the panel's inner (right) edge: drag right to grow
417 GtkWidget *handle = dt_bauhaus_resize_handle_new(GTK_ORIENTATION_HORIZONTAL, FALSE,
418 _("Drag to resize panel"),
420 gtk_overlay_add_overlay(GTK_OVERLAY(over), handle);
421 gtk_widget_show(handle);
422
423 gtk_grid_attach(GTK_GRID(container), over, 1, 1, 1, 1);
424
425 /* add top,center,bottom*/
426 container = widget;
430
431 /* lets show all widgets */
432 gtk_widget_show_all(ui->panels[DT_UI_PANEL_LEFT]);
433}
434
435/* initialize the whole right panel */
437{
438 GtkWidget *widget;
439
440 /* create right panel main widget and add it to ui */
442 gtk_widget_set_name(widget, "right");
443 _ui_init_panel_size(widget, ui);
444
445 GtkWidget *over = gtk_overlay_new();
446 gtk_container_add(GTK_CONTAINER(over), widget);
447 // resize grip overlaid on the panel's inner (left) edge: drag left to grow (inverted)
448 GtkWidget *handle = dt_bauhaus_resize_handle_new(GTK_ORIENTATION_HORIZONTAL, TRUE,
449 _("Drag to resize panel"),
451 gtk_overlay_add_overlay(GTK_OVERLAY(over), handle);
452 gtk_widget_show(handle);
453
454 gtk_grid_attach(GTK_GRID(container), over, 3, 1, 1, 1);
455
456 /* add top,center,bottom*/
457 container = widget;
461
462 /* lets show all widgets */
463 gtk_widget_show_all(ui->panels[DT_UI_PANEL_RIGHT]);
464}
465
466/* initialize the top container of panel */
468{
469 GtkWidget *widget;
470
471 /* create the panel box */
472 // Warning: No spacing between lines !!!
473 ui->panels[DT_UI_PANEL_TOP] = widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
474 gtk_widget_set_name(ui->panels[DT_UI_PANEL_TOP], "top");
475 gtk_widget_set_hexpand(GTK_WIDGET(widget), TRUE);
476 gtk_grid_attach(GTK_GRID(container), widget, 1, 0, 3, 1);
477
478 /* add container for top center */
479 ui->top_panel = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
480 gtk_widget_set_name(ui->top_panel, "top-first-line");
481 gtk_box_pack_start(GTK_BOX(widget), ui->top_panel, FALSE, FALSE,
483
484 ui->containers[DT_UI_CONTAINER_PANEL_TOP_SECOND_ROW] = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
485 gtk_widget_set_name(ui->containers[DT_UI_CONTAINER_PANEL_TOP_SECOND_ROW], "top-second-line");
486 gtk_box_pack_start(GTK_BOX(widget), ui->containers[DT_UI_CONTAINER_PANEL_TOP_SECOND_ROW], FALSE, FALSE,
488}
489
490
491/* initialize the bottom panel */
493{
494 /* create the panel box */
495 GtkWidget *over = gtk_overlay_new();
497 gtk_container_add(GTK_CONTAINER(over), ui->thumbtable_filmstrip->parent_overlay);
498
500 gtk_widget_set_name(ui->thumbtable_filmstrip->parent_overlay, "bottom");
502 gtk_grid_attach(GTK_GRID(container), over, 1, 2, 3, 1);
503
504 // resize grip overlaid on the panel's top edge: drag up to grow (inverted).
505 // We resize the actual bottom panel widget (named "bottom"), not the overlay wrapper.
506 // Otherwise the panel can be grown (outer overlay expands) but not shrunk because the
507 // filmstrip panel keeps its previous size request until the view is recreated.
508 GtkWidget *handle = dt_bauhaus_resize_handle_new(GTK_ORIENTATION_VERTICAL, TRUE,
509 _("Drag to resize panel"),
512 gtk_overlay_add_overlay(GTK_OVERLAY(over), handle);
513 gtk_widget_show(handle);
514}
515
516/* this is called as a signal handler, the signal raising logic asserts the gdk lock. */
517static void _ui_widget_redraw_callback(gpointer instance, GtkWidget *widget)
518{
519 gtk_widget_queue_draw(widget);
520}
521
523{
524 GtkWidget *widget;
525
526 // Creating the table
527 GtkWidget *container = gtk_grid_new();
528 gtk_box_pack_start(GTK_BOX(parent), container, TRUE, TRUE, 0);
529 gtk_widget_show(container);
530
531 /* initialize toolboxes panels */
536
537 /* initialize the main drawing widget (center) */
538 widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
539 gtk_widget_set_name(widget, "main-widget");
540 gtk_widget_set_hexpand(GTK_WIDGET(widget), TRUE);
541 gtk_widget_set_vexpand(GTK_WIDGET(widget), TRUE);
542 gtk_grid_attach(GTK_GRID(container), widget, 2, 1, 1, 1);
543
544 /* initialize the thumb panel */
546
547 /* setup center drawing area */
548 GtkWidget *ocda = gtk_overlay_new();
549 gtk_widget_set_size_request(ocda, DT_PIXEL_APPLY_DPI(200), DT_PIXEL_APPLY_DPI(200));
550 gtk_widget_show(ocda);
551
552 GtkWidget *cda = gtk_drawing_area_new();
553 gtk_widget_set_hexpand(ocda, TRUE);
554 gtk_widget_set_vexpand(ocda, TRUE);
555 gtk_widget_set_app_paintable(cda, TRUE);
556 gtk_widget_set_events(cda, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK
557 | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
559 gtk_overlay_add_overlay(GTK_OVERLAY(ocda), cda);
560
561 // Add the reserved overlay for the thumbtable in central position
562 // Then we insert into container, instead of dynamically adding/removing a new overlay
563 // because log and toast messages need to go on top too.
564 gtk_overlay_add_overlay(GTK_OVERLAY(ocda), ui->thumbtable_lighttable->parent_overlay);
565
566 gtk_box_pack_start(GTK_BOX(widget), ocda, TRUE, TRUE, 0);
567
568 ui->center = cda;
569 ui->center_base = ocda;
570
571 /* center should redraw when signal redraw center is raised*/
573 G_CALLBACK(_ui_widget_redraw_callback), ui->center);
574
575 gtk_widget_show_all(container);
576}
577
579{
580 // Avoid dangling UI pointers during shutdown: background threads might query
581 // thumbnail info while mipmap cache is being flushed.
582 dt_thumbtable_t *filmstrip = ui->thumbtable_filmstrip;
583 dt_thumbtable_t *lighttable = ui->thumbtable_lighttable;
584
585 ui->thumbtable_filmstrip = NULL;
586 ui->thumbtable_lighttable = NULL;
587
588 if(filmstrip) dt_thumbtable_cleanup(filmstrip);
589 if(lighttable) dt_thumbtable_cleanup(lighttable);
590}
591
592
594{
595 ui->header = g_malloc0(sizeof(dt_header_t));
596
597#ifdef MERGE_MENUBAR
598 // Remove useless desktop environment titlebar. We will handle closing buttons internally
599 ui->header->titlebar = gtk_header_bar_new();
600 gtk_widget_set_size_request(ui->header->titlebar, -1, -1);
601 gtk_window_set_titlebar(GTK_WINDOW(ui->main_window), ui->header->titlebar);
602
603 // Reset header bar properties
604 gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(ui->header->titlebar), FALSE);
605 gtk_header_bar_set_decoration_layout(GTK_HEADER_BAR(ui->header->titlebar), NULL);
606
607 // Gtk mandatorily adds an empty label that is still "visible" for the title.
608 // Since it's centered, it can collide with the hinter width.
609 // Plus it adds mandatory padding. AKA scrap that.
610 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
611 gtk_header_bar_set_custom_title(GTK_HEADER_BAR(ui->header->titlebar), box);
612 gtk_widget_set_no_show_all(box, TRUE);
613#else
614 ui->header->titlebar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
615#endif
616
617 gtk_widget_show(ui->header->titlebar);
618}
619
621{
622#ifdef MERGE_MENUBAR
623 gtk_header_bar_pack_start(GTK_HEADER_BAR(ui->header->titlebar), widget);
624#else
625 gtk_box_pack_start(GTK_BOX(ui->header->titlebar), widget, FALSE, FALSE, 0);
626#endif
627}
628
630{
631#ifdef MERGE_MENUBAR
632 gtk_header_bar_pack_end(GTK_HEADER_BAR(ui->header->titlebar), widget);
633#else
634 gtk_box_pack_end(GTK_BOX(ui->header->titlebar), widget, FALSE, FALSE, 0);
635#endif
636}
637
639{
640 dt_ctl_switch_mode_to("lighttable");
641}
642
643void _close_callback(GtkWidget *w, gpointer data)
644{
645 gtk_window_close(GTK_WINDOW((GtkWidget *)data));
646}
647
648void _iconify_callback(GtkWidget *w, gpointer data)
649{
650 gtk_window_iconify(GTK_WINDOW((GtkWidget *)data));
651}
652
654{
656}
657
659{
660 /* if user_pref != merge menubar in titlebar */
661 GtkWidget *parent = ui->top_panel;
662 gtk_box_pack_start(GTK_BOX(parent), ui->header->titlebar, TRUE, TRUE, 0);
663 /* endif */
664
665 /* Init top-level menus */
666 ui->header->menu_bar = gtk_menu_bar_new();
667 gtk_widget_set_name(ui->header->menu_bar, "menu-bar");
668 gchar *labels [DT_MENU_LAST] = { _("_File"), _("_Edit"), _("_Selection"), _("_Image"), _("_Styles"), _("_Run"), _("_Display"), _("_Ateliers"), _("_Help") };
669 for(int i = 0; i < DT_MENU_LAST; i++)
670 {
671 ui->header->item_lists[i] = NULL;
672 add_top_menu_entry(ui->header->menu_bar, ui->header->menus, &ui->header->item_lists[i], i, labels[i]);
673 }
674
675 gtk_widget_set_halign(ui->header->menu_bar, GTK_ALIGN_START);
676 gtk_widget_set_hexpand(ui->header->menu_bar, FALSE);
677
678 /* Populate sub-menus */
688
690 gtk_widget_show_all(ui->header->menu_bar);
691
692 GtkWidget *search_button = gtk_button_new_from_icon_name("edit-find", GTK_ICON_SIZE_SMALL_TOOLBAR);
693 gtk_button_set_label (GTK_BUTTON(search_button), _("Search actions..."));
694 gtk_widget_set_halign(search_button, GTK_ALIGN_CENTER);
695 gtk_widget_set_valign(search_button, GTK_ALIGN_CENTER);
696 gtk_widget_set_hexpand(search_button, TRUE);
697 gtk_widget_set_name(search_button, "search-button");
698 g_signal_connect(G_OBJECT(search_button), "clicked", G_CALLBACK(_open_accel_search_callback), NULL);
699 dt_ui_titlebar_pack_start(ui, search_button);
700 gtk_widget_show(search_button);
701
702 // From there, we pack_end meaning it should be done in reverse order of appearance
703 ui->header->close = gtk_button_new_from_icon_name("window-close", GTK_ICON_SIZE_LARGE_TOOLBAR);
704 g_signal_connect(G_OBJECT(ui->header->close), "clicked", G_CALLBACK(_close_callback), ui->main_window);
705 gtk_widget_set_size_request(ui->header->close, 24, 24);
706 dt_gui_add_class(ui->header->close, "window-button");
708
709 ui->header->iconify = gtk_button_new_from_icon_name("window-minimize", GTK_ICON_SIZE_LARGE_TOOLBAR);
710 g_signal_connect(G_OBJECT(ui->header->iconify), "clicked", G_CALLBACK(_iconify_callback), ui->main_window);
711 gtk_widget_set_size_request(ui->header->iconify, 24, 24);
712 dt_gui_add_class(ui->header->iconify, "window-button");
714
715 ui->header->home = gtk_button_new_from_icon_name("go-home", GTK_ICON_SIZE_LARGE_TOOLBAR);
716 gtk_widget_set_tooltip_text(ui->header->home, _("Go back to lighttable"));
717 g_signal_connect(G_OBJECT(ui->header->home), "clicked", _home_callback, NULL);
718 gtk_widget_set_size_request(ui->header->home, 24, 24);
719 dt_gui_add_class(ui->header->home, "window-button");
721 gtk_widget_show(ui->header->home);
722
723 GtkWidget *spacer = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
724 dt_ui_titlebar_pack_end(ui, spacer);
725 gtk_widget_show(spacer);
726
727 /* Init hinter */
728 ui->header->hinter = gtk_label_new("");
729 gtk_label_set_ellipsize(GTK_LABEL(ui->header->hinter), PANGO_ELLIPSIZE_END);
730 gtk_widget_set_name(ui->header->hinter, "hinter");
731 gtk_widget_set_halign(ui->header->hinter, GTK_ALIGN_END);
732 gtk_label_set_justify(GTK_LABEL(ui->header->hinter), GTK_JUSTIFY_RIGHT);
733 gtk_label_set_line_wrap(GTK_LABEL(ui->header->hinter), TRUE);
735 gtk_widget_show(ui->header->hinter);
736
737 spacer = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
738 dt_ui_titlebar_pack_end(ui, spacer);
739 gtk_widget_show(spacer);
740
741 /* Image info */
742 ui->header->image_info = gtk_label_new("");
743 gtk_label_set_ellipsize(GTK_LABEL(ui->header->image_info), PANGO_ELLIPSIZE_MIDDLE);
744 gtk_widget_set_name(ui->header->image_info, "image-info");
746 gtk_widget_show(ui->header->image_info);
747}
748
749void dt_ui_set_image_info_label(dt_ui_t *ui, const char *label)
750{
751 if(IS_NULL_PTR(ui) || IS_NULL_PTR(ui->header) || !GTK_IS_LABEL(ui->header->image_info)) return;
752 gtk_label_set_markup(GTK_LABEL(ui->header->image_info), label);
753}
754
755void dt_ui_set_window_buttons_visible(dt_ui_t *ui, gboolean visible)
756{
757 gtk_widget_set_visible(ui->header->close, visible);
758 gtk_widget_set_visible(ui->header->iconify, visible);
759}
760
761void dt_hinter_set_message(dt_ui_t *ui, const char *message)
762{
763 if(IS_NULL_PTR(ui) || IS_NULL_PTR(ui->header) || !GTK_IS_LABEL(ui->header->hinter)) return;
764 // Remove hacky attempts of line wrapping with hardcoded newline :
765 // Line wrap is handled by Gtk at the label scope.
766 char **split = g_strsplit(message, "\n", -1);
767 char *joined = g_strjoinv(", ", split);
768 gtk_label_set_markup(GTK_LABEL(ui->header->hinter), joined);
769 dt_free(joined);
770 g_strfreev(split);
771}
772
773
775{
776 if(IS_NULL_PTR(ui) || IS_NULL_PTR(ui->header)) return;
777
778 if(!IS_NULL_PTR(ui->header->titlebar) && GTK_IS_WIDGET(ui->header->titlebar))
779 {
780 gtk_widget_destroy(ui->header->titlebar);
781 ui->header->titlebar = NULL;
782 }
783
784 for(int i = 0; i < DT_MENU_LAST; i++)
785 {
786 g_list_free(ui->header->item_lists[i]);
787 ui->header->item_lists[i] = NULL;
788 }
789 dt_free(ui->header);
790 ui->header = NULL;
791}
void dt_accels_search(dt_accels_t *accels, GtkWindow *main_window, GtkWidget *anchor)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
uint32_t container(dt_lib_module_t *self)
GtkWidget * dt_bauhaus_resize_handle_new(GtkOrientation orientation, gboolean invert, const char *tooltip, dt_bauhaus_resize_handle_get_size_f get_size, dt_bauhaus_resize_handle_resize_f resize, gpointer user_data)
Create a themed handle widget driving one-dimensional resize gestures.
Definition bauhaus.c:1179
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
char * key
char * name
int dt_conf_get_bool(const char *name)
int dt_conf_key_exists(const char *key)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_ctl_switch_mode_to(const char *mode)
Definition control.c:657
darktable_t darktable
Definition darktable.c:181
#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
void dt_ui_panel_show(dt_ui_t *ui, const dt_ui_panel_t p, gboolean show, gboolean write)
shows/hide a panel
Definition display.c:124
void append_display(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition display.c:451
void append_edit(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition edit.c:489
void append_file(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition file.c:214
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
void append_image(GtkWidget **menus, GList **lists, const dt_menus_t index)
void append_help(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition help.c:220
const float v
float *const restrict const size_t k
void add_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index, gchar *label)
Definition menu.c:486
void append_views(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition views.c:63
void append_select(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition select.c:69
void append_run(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition run.c:201
void append_styles(GtkWidget **menus, GList **lists, const dt_menus_t index)
@ DT_MENU_IMAGE
Definition menu.h:46
@ DT_MENU_FILE
Definition menu.h:43
@ DT_MENU_STYLES
Definition menu.h:47
@ DT_MENU_EDIT
Definition menu.h:44
@ DT_MENU_DISPLAY
Definition menu.h:49
@ DT_MENU_SELECTION
Definition menu.h:45
@ DT_MENU_LAST
Definition menu.h:52
@ DT_MENU_ATELIERS
Definition menu.h:50
@ DT_MENU_HELP
Definition menu.h:51
@ DT_MENU_RUN
Definition menu.h:48
size_t size
Definition mipmap_cache.c:3
GtkWidget * dtgtk_side_panel_new()
Definition sidepanel.c:60
@ DT_SIGNAL_CONTROL_REDRAW_CENTER
This signal is raised when dt_control_queue_redraw_center() is called. no param, no returned value.
Definition signal.h:74
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
struct dt_lib_t * lib
Definition darktable.h:771
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
struct dt_view_manager_t * view_manager
Definition darktable.h:772
GList * iop
Definition develop.h:279
gint scroll_mask
Definition gtk.h:224
dt_accels_t * accels
Definition gtk.h:194
dt_ui_t * ui
Definition gtk.h:164
GtkWidget * scroll_to[2]
Definition gtk.h:221
GtkWidget * iconify
GtkWidget * menu_bar
GtkWidget * titlebar
GtkWidget * home
GtkWidget * hinter
GList * item_lists[DT_MENU_LAST]
GtkWidget * menus[DT_MENU_LAST]
GtkWidget * close
GtkWidget * image_info
GList * plugins
Definition lib.h:55
GtkWidget * parent_overlay
Definition thumbtable.h:153
GtkWidget * top_panel
dt_thumbtable_t * thumbtable_lighttable
GtkWidget * containers[DT_UI_CONTAINER_SIZE]
GtkWidget * main_window
GtkWidget * center_base
struct dt_header_t * header
GtkWidget * log_msg
dt_thumbtable_t * thumbtable_filmstrip
GtkWidget * center
GtkWidget * panels[DT_UI_PANEL_SIZE]
GtkWidget * toast_msg
char module_name[64]
Definition view.h:153
#define MAX(a, b)
Definition thinplate.c:29
void dt_thumbtable_cleanup(dt_thumbtable_t *table)
dt_thumbtable_t * dt_thumbtable_new(dt_thumbtable_mode_t mode)
Create a new thumbnail table widget.
@ DT_THUMBTABLE_MODE_FILMSTRIP
Definition thumbtable.h:65
@ DT_THUMBTABLE_MODE_FILEMANAGER
Definition thumbtable.h:64
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:95
dt_darkroom_layout_t dt_view_darkroom_get_layout(dt_view_manager_t *vm)
Definition view.c:1332
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:140
static void _ui_panel_size_changed(GtkAdjustment *adjustment, GParamSpec *pspec, gpointer user_data)
GtkWidget * dt_ui_toast_msg(dt_ui_t *ui)
get the toast message widget
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
static void _ui_widget_redraw_callback(gpointer instance, GtkWidget *widget)
gchar * panels_get_view_path(char *suffix)
void dt_ui_cleanup_titlebar(dt_ui_t *ui)
dt_panel_side_t
@ LEFT_PANNEL
@ PANEL_SIDE_COUNT
@ RIGHT_PANNEL
static void _ui_init_panel_right(dt_ui_t *ui, GtkWidget *container)
void _open_accel_search_callback(GtkWidget *w, gpointer data)
static void _ui_init_panel_size(GtkWidget *widget, dt_ui_t *ui)
gchar * panels_get_panel_path(dt_ui_panel_t panel, char *suffix)
void dt_ui_init_titlebar(dt_ui_t *ui)
static GtkWidget * _ui_init_panel_container_bottom(GtkWidget *container)
int dt_ui_panel_get_size(dt_ui_t *ui, const dt_ui_panel_t p)
get width of right, left, or bottom panel
void dt_ui_set_window_buttons_visible(dt_ui_t *ui, gboolean visible)
gboolean dt_ui_panel_ancestor(dt_ui_t *ui, const dt_ui_panel_t p, GtkWidget *w)
is the panel ancestor of widget
void dt_ui_cleanup_main_table(dt_ui_t *ui)
static void _ui_init_panel_bottom(dt_ui_t *ui, GtkWidget *container)
void _iconify_callback(GtkWidget *w, gpointer data)
void dt_ui_titlebar_pack_start(dt_ui_t *ui, GtkWidget *widget)
GtkWidget * dt_ui_log_msg(dt_ui_t *ui)
get the log message widget
void dt_ui_init_global_menu(dt_ui_t *ui)
static void _ui_init_panel_left(dt_ui_t *ui, GtkWidget *container)
static GtkWidget * _ui_init_panel_container_top(GtkWidget *container)
void dt_ui_titlebar_pack_end(dt_ui_t *ui, GtkWidget *widget)
void _close_callback(GtkWidget *w, gpointer data)
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
void dt_ui_restore_panels(dt_ui_t *ui)
static GtkWidget * _ui_init_panel_container_center(GtkWidget *container, gboolean left)
void dt_ui_init_main_table(GtkWidget *parent, dt_ui_t *ui)
void _home_callback()
void dt_ui_set_image_info_label(dt_ui_t *ui, const char *label)
static gboolean _ui_scroll_target_is_live_widget(const GtkWidget *target, const int side)
Check whether a deferred panel scroll target is still a live module widget.
static void _ui_init_panel_top(dt_ui_t *ui, GtkWidget *container)
void dt_ui_container_add_widget(dt_ui_t *ui, const dt_ui_container_t c, GtkWidget *w)
void dt_hinter_set_message(dt_ui_t *ui, const char *message)
GtkWidget * dt_ui_center_base(dt_ui_t *ui)
static int _panel_handle_resize(int requested_size, gboolean finished, gpointer user_data)
const char * _ui_panel_config_names[]
static int _panel_handle_get_size(gpointer user_data)
GtkBox * dt_ui_get_container(dt_ui_t *ui, const dt_ui_container_t c)
dt_ui_container_t
@ DT_UI_CONTAINER_PANEL_LEFT_BOTTOM
@ DT_UI_CONTAINER_PANEL_RIGHT_BOTTOM
@ DT_UI_CONTAINER_PANEL_RIGHT_CENTER
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER
@ DT_UI_CONTAINER_PANEL_LEFT_TOP
@ DT_UI_CONTAINER_PANEL_RIGHT_TOP
@ DT_UI_CONTAINER_PANEL_TOP_SECOND_ROW
#define DT_UI_PANEL_MODULE_SPACING
#define DT_UI_PANEL_SIDE_DEFAULT_SIZE
dt_ui_panel_t
@ DT_UI_PANEL_TOP
@ DT_UI_PANEL_SIZE
@ DT_UI_PANEL_BOTTOM
@ DT_UI_PANEL_LEFT
@ DT_UI_PANEL_RIGHT
#define DT_UI_PANEL_BOTTOM_DEFAULT_SIZE