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/utility.h"
23#include "common/conf.h"
24#include "control/control.h"
25#include "develop/develop.h"
26#include "develop/imageop.h"
27#include "views/view.h"
28#include "gui/window_manager.h"
29#include "gui/actions/menu.h"
30#include "widgets/sidepanel.h"
31#include "libs/lib.h"
32#include "gui/application.h"
33#include "gui/dtgtk/thumbnail.h"
37#include "control/signal.h"
38
39#define WINDOW_DEBUG 0
40
53
60
61const char *_ui_panel_config_names[] = { "top", "left", "right", "bottom" };
62
63
64gchar * panels_get_view_path(char *suffix)
65{
66
68 if(IS_NULL_PTR(vm)) return NULL;
70 if(IS_NULL_PTR(cv)) return NULL;
71 char lay[32] = "";
72
73 if(!strcmp(cv->module_name, "lighttable"))
74 g_snprintf(lay, sizeof(lay), "%d/", 0);
75 else if(!strcmp(cv->module_name, "darkroom"))
76 g_snprintf(lay, sizeof(lay), "%d/", dt_view_darkroom_get_layout(vm));
77
78 return g_strdup_printf("%s/ui/%s%s", cv->module_name, lay, suffix);
79}
80
81gchar * panels_get_panel_path(dt_ui_panel_t panel, char *suffix)
82{
83 gchar *v = panels_get_view_path("");
84 if(IS_NULL_PTR(v)) return NULL;
85 return dt_util_dstrcat(v, "%s%s", _ui_panel_config_names[panel], suffix);
86}
87
89{
90 gchar *key = NULL;
91
93 {
94 int size = 0;
95
96 key = panels_get_panel_path(p, "_size");
98 {
100 dt_free(key);
101 }
102 else // size hasn't been adjusted, so return default sizes
103 {
104 if(p == DT_UI_PANEL_BOTTOM)
106 else
108
109 if(!IS_NULL_PTR(key)) dt_free(key);
110 }
111 return size;
112 }
113 return -1;
114}
115
117{
118 g_return_val_if_fail(GTK_IS_WIDGET(ui->panels[p]), FALSE);
119 return gtk_widget_is_ancestor(w, ui->panels[p]) || gtk_widget_is_ancestor(ui->panels[p], w);
120}
121
123{
124 return ui->center;
125}
127{
128 return ui->center_base;
129}
130
132{
133 return ui->log_msg;
134}
136{
137 return ui->toast_msg;
138}
139
141{
142 return ui->main_window;
143}
144
146{
147 return GTK_BOX(ui->containers[c]);
148}
149
151{
152 switch(c)
153 {
154 /* These should be flexboxes/flowboxes so line wrapping is turned on when line width is too small to contain everything
155 * but flexboxes don't seem to work here as advertised (everything either goes to new line or same line, no wrapping),
156 * maybe because they will get added to boxes at the end, and Gtk heuristics to decide final width are weird.
157 */
158 /* if box is right lets pack at end for nicer alignment */
159 /* if box is center we want it to fill as much as it can */
161 gtk_box_pack_start(GTK_BOX(ui->containers[c]), w, TRUE, TRUE, 0);
162 break;
163
164 default:
165 gtk_box_pack_start(GTK_BOX(ui->containers[c]), w, FALSE, FALSE, 0);
166 break;
167 }
168 gtk_widget_show_all(w);
169}
170
171static void _ui_init_panel_size(GtkWidget *widget, dt_ui_t *ui)
172{
173 gchar *key = NULL;
174 int s = DT_UI_PANEL_SIDE_DEFAULT_SIZE; // default panel size
175 if(strcmp(gtk_widget_get_name(widget), "right") == 0)
176 {
179 s = MAX(dt_conf_get_int(key), 120);
180 if(key) gtk_widget_set_size_request(widget, s, -1);
181 }
182 else if(strcmp(gtk_widget_get_name(widget), "left") == 0)
183 {
186 s = MAX(dt_conf_get_int(key), 120);
187 if(key) gtk_widget_set_size_request(widget, s, -1);
188 }
189 else if(strcmp(gtk_widget_get_name(widget), "bottom") == 0)
190 {
192 s = DT_UI_PANEL_BOTTOM_DEFAULT_SIZE; // default panel size
194 s = MAX(dt_conf_get_int(key), 48);
195 if(key) gtk_widget_set_size_request(widget, -1, s);
196 }
197
198 dt_free(key);
199}
200
202{
203 /* restore left & right panel size */
207
208 /* restore from a previous collapse all panel state if enabled */
209 gchar *key = panels_get_view_path("panel_collaps_state");
210 const uint32_t state = dt_conf_get_int(key);
211 dt_free(key);
212 if(state)
213 {
214 /* hide all panels (we let saved state as it is, to recover them when pressing TAB)*/
215 for(int k = 0; k < DT_UI_PANEL_SIZE; k++) dt_ui_panel_show(ui, k, FALSE, FALSE);
216 }
217 else
218 {
219 /* restore the visible state of panels */
220 for(int k = 0; k < DT_UI_PANEL_SIZE; k++)
221 {
222 key = panels_get_panel_path(k, "_visible");
225 else
227
228 dt_free(key);
229 }
230 }
231}
232
233/* The main panels share the generic resize-handle primitive (dtgtk_resize_handle_new),
234 * so they get the same grip visual, hover border and cursor as every other resizable area.
235 * These two callbacks let the handle query and apply the panel size; the panel widget is passed
236 * as user_data and identified by its name ("left"/"right"/"bottom"). */
237static int _panel_handle_get_size(gpointer user_data)
238{
239 GtkWidget *widget = GTK_WIDGET(user_data);
240 const gboolean bottom = (strcmp(gtk_widget_get_name(widget), "bottom") == 0);
241 gint w = 0, h = 0;
242 gtk_widget_get_size_request(widget, &w, &h);
243 if(bottom) return (h > 0) ? h : gtk_widget_get_allocated_height(widget);
244 return (w > 0) ? w : gtk_widget_get_allocated_width(widget);
245}
246
247static int _panel_handle_resize(int requested_size, gboolean finished, gpointer user_data)
248{
249 GtkWidget *widget = GTK_WIDGET(user_data);
250 const char *name = gtk_widget_get_name(widget);
252 int win_w = 0, win_h = 0;
253 gtk_window_get_size(GTK_WINDOW(window), &win_w, &win_h);
254
255 gchar *key = NULL;
256 int size = requested_size;
257 if(strcmp(name, "right") == 0)
258 {
259 size = CLAMP(requested_size, 150, win_w / 2);
261 gtk_widget_set_size_request(widget, size, -1);
262 }
263 else if(strcmp(name, "left") == 0)
264 {
265 size = CLAMP(requested_size, 150, win_w / 2);
267 gtk_widget_set_size_request(widget, size, -1);
268 }
269 else if(strcmp(name, "bottom") == 0)
270 {
271 size = CLAMP(requested_size, 48, win_h / 3);
273 gtk_widget_set_size_request(widget, -1, size);
274 }
275
276 // Persist only when the gesture ends, but apply the size live during the drag.
277 if(finished && key) dt_conf_set_int(key, size);
278 dt_free(key);
279
280 return size;
281}
282
283/* initialize the top container of panel */
285{
286 GtkWidget *w = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
287 gtk_box_pack_start(GTK_BOX(container), w, FALSE, FALSE, 0);
288 return w;
289}
290
302static gboolean _ui_scroll_target_is_live_widget(const GtkWidget *target, const int side)
303{
304 if(IS_NULL_PTR(target)) return FALSE;
305 if(side != LEFT_PANNEL && side != RIGHT_PANNEL) return FALSE;
306
308 {
309 // Walk all lib modules and look for the exact expander address queued for scrolling.
310 for(const GList *libs = dt_lib_get_global()->plugins; libs; libs = g_list_next(libs))
311 {
312 const dt_lib_module_t *module = (const dt_lib_module_t *)libs->data;
313 if(!IS_NULL_PTR(module) && module->expander == target) return TRUE;
314 }
315 }
316
317 const dt_develop_t *const dev = dt_dev_get_global();
318 if(side == RIGHT_PANNEL && !IS_NULL_PTR(dev))
319 {
320 // Walk darkroom iop modules and accept either header or expander scroll anchors.
321 for(const GList *iops = dev->iop; iops; iops = g_list_next(iops))
322 {
323 const dt_iop_module_t *module = (const dt_iop_module_t *)iops->data;
324 if(IS_NULL_PTR(module)) continue;
325 if(dt_iop_gui_owns_widget(module, target)) return TRUE;
326 }
327 }
328
329 return FALSE;
330}
331
332// this should work as long as everything happens in the gui thread
333static void _ui_panel_size_changed(GtkAdjustment *adjustment, GParamSpec *pspec, gpointer user_data)
334{
335 GtkAllocation allocation;
336 static float last_height[PANEL_SIDE_COUNT] = { 0 };
337
338 const int side = GPOINTER_TO_INT(user_data);
339 if(side != LEFT_PANNEL && side != RIGHT_PANNEL) return;
340
341 // don't do anything when the size didn't actually change.
342 const float height = gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_lower(adjustment);
343
344 if(height == last_height[side]) return;
345 last_height[side] = height;
346
347 if(IS_NULL_PTR(dt_gui_get_global()->scroll_to[side])) return;
348 if(!_ui_scroll_target_is_live_widget(dt_gui_get_global()->scroll_to[side], side))
349 {
350 dt_gui_get_global()->scroll_to[side] = NULL;
351 return;
352 }
353
354 if(GTK_IS_WIDGET(dt_gui_get_global()->scroll_to[side]))
355 {
356 gtk_widget_get_allocation(dt_gui_get_global()->scroll_to[side], &allocation);
357 gtk_adjustment_set_value(adjustment, allocation.y);
358 }
359
360 dt_gui_get_global()->scroll_to[side] = NULL;
361}
362
363/* initialize the center container of panel */
365{
366 GtkWidget *widget;
367 GtkAdjustment *a[4];
368
369 a[0] = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 100, 1, 10, 10));
370 a[1] = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 100, 1, 10, 10));
371 a[2] = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 100, 1, 10, 10));
372 a[3] = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 100, 1, 10, 10));
373
374 /* create the scrolled window */
375 widget = gtk_scrolled_window_new(a[0], a[1]);
376 // Named so the theme can zero its "scrollbar-spacing" style property (a legacy GtkWidget style
377 // property, not a CSS box property), removing the 3px gutter between the modules and the scrollbar.
378 gtk_widget_set_name(widget, "panel-scroll");
379 gtk_widget_set_can_focus(widget, TRUE);
380 gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(widget),
381 left ? GTK_CORNER_TOP_LEFT : GTK_CORNER_TOP_RIGHT);
382 gtk_box_pack_start(GTK_BOX(container), widget, TRUE, TRUE, 0);
383 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
384
385 g_signal_connect(G_OBJECT(gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(widget))), "notify::lower",
386 G_CALLBACK(_ui_panel_size_changed),
387 GINT_TO_POINTER(left ? RIGHT_PANNEL : LEFT_PANNEL));
388
389 /* create the scrolled viewport */
390 container = widget;
391 widget = gtk_viewport_new(a[2], a[3]);
392 gtk_viewport_set_shadow_type(GTK_VIEWPORT(widget), GTK_SHADOW_NONE);
393 gtk_container_add(GTK_CONTAINER(container), widget);
394
395 /* create the container */
396 container = widget;
397 // WARNING: no spacing between modules in left sidebar
398 widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
399 gtk_widget_set_name(widget, "plugins_box");
400 gtk_container_add(GTK_CONTAINER(container), widget);
401
402 return widget;
403}
404
405/* initialize the bottom container of panel */
407{
408 GtkWidget *w = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
409 gtk_box_pack_start(GTK_BOX(container), w, FALSE, FALSE, 0);
410 return w;
411}
412
413/* initialize the whole left panel */
415{
416 GtkWidget *widget;
417
418 /* create left panel main widget and add it to ui */
419 // Configuration reaches the widget from here, not from inside it. Must precede the first
420 // dtgtk_side_panel_new(): the value is read in the GObject class_init.
422
424 gtk_widget_set_name(widget, "left");
425 _ui_init_panel_size(widget, ui);
426
427 GtkWidget *over = gtk_overlay_new();
428 gtk_container_add(GTK_CONTAINER(over), widget);
429 // resize grip overlaid on the panel's inner (right) edge: drag right to grow
430 GtkWidget *handle = dtgtk_resize_handle_new(GTK_ORIENTATION_HORIZONTAL, FALSE,
431 _("Drag to resize panel"),
433 gtk_overlay_add_overlay(GTK_OVERLAY(over), handle);
434 gtk_widget_show(handle);
435
436 gtk_grid_attach(GTK_GRID(container), over, 1, 1, 1, 1);
437
438 /* add top,center,bottom*/
439 container = widget;
443
444 /* lets show all widgets */
445 gtk_widget_show_all(ui->panels[DT_UI_PANEL_LEFT]);
446}
447
448/* initialize the whole right panel */
450{
451 GtkWidget *widget;
452
453 /* create right panel main widget and add it to ui */
455 gtk_widget_set_name(widget, "right");
456 _ui_init_panel_size(widget, ui);
457
458 GtkWidget *over = gtk_overlay_new();
459 gtk_container_add(GTK_CONTAINER(over), widget);
460 // resize grip overlaid on the panel's inner (left) edge: drag left to grow (inverted)
461 GtkWidget *handle = dtgtk_resize_handle_new(GTK_ORIENTATION_HORIZONTAL, TRUE,
462 _("Drag to resize panel"),
464 gtk_overlay_add_overlay(GTK_OVERLAY(over), handle);
465 gtk_widget_show(handle);
466
467 gtk_grid_attach(GTK_GRID(container), over, 3, 1, 1, 1);
468
469 /* add top,center,bottom*/
470 container = widget;
474
475 /* lets show all widgets */
476 gtk_widget_show_all(ui->panels[DT_UI_PANEL_RIGHT]);
477}
478
479/* initialize the top container of panel */
481{
482 GtkWidget *widget;
483
484 /* create the panel box */
485 // Warning: No spacing between lines !!!
486 ui->panels[DT_UI_PANEL_TOP] = widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
487 gtk_widget_set_name(ui->panels[DT_UI_PANEL_TOP], "top");
488 gtk_widget_set_hexpand(GTK_WIDGET(widget), TRUE);
489 gtk_grid_attach(GTK_GRID(container), widget, 1, 0, 3, 1);
490
491 /* add container for top center */
492 ui->top_panel = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
493 gtk_widget_set_name(ui->top_panel, "top-first-line");
494 gtk_box_pack_start(GTK_BOX(widget), ui->top_panel, FALSE, FALSE,
496
497 ui->containers[DT_UI_CONTAINER_PANEL_TOP_SECOND_ROW] = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
498 gtk_widget_set_name(ui->containers[DT_UI_CONTAINER_PANEL_TOP_SECOND_ROW], "top-second-line");
499 gtk_box_pack_start(GTK_BOX(widget), ui->containers[DT_UI_CONTAINER_PANEL_TOP_SECOND_ROW], FALSE, FALSE,
501}
502
503
504/* initialize the bottom panel */
506{
507 /* create the panel box */
508 GtkWidget *over = gtk_overlay_new();
510 gtk_container_add(GTK_CONTAINER(over), ui->thumbtable_filmstrip->parent_overlay);
511
513 gtk_widget_set_name(ui->thumbtable_filmstrip->parent_overlay, "bottom");
515 gtk_grid_attach(GTK_GRID(container), over, 1, 2, 3, 1);
516
517 // resize grip overlaid on the panel's top edge: drag up to grow (inverted).
518 // We resize the actual bottom panel widget (named "bottom"), not the overlay wrapper.
519 // Otherwise the panel can be grown (outer overlay expands) but not shrunk because the
520 // filmstrip panel keeps its previous size request until the view is recreated.
521 GtkWidget *handle = dtgtk_resize_handle_new(GTK_ORIENTATION_VERTICAL, TRUE,
522 _("Drag to resize panel"),
525 gtk_overlay_add_overlay(GTK_OVERLAY(over), handle);
526 gtk_widget_show(handle);
527}
528
529/* this is called as a signal handler, the signal raising logic asserts the gdk lock. */
530static void _ui_widget_redraw_callback(gpointer instance, GtkWidget *widget)
531{
532 gtk_widget_queue_draw(widget);
533}
534
536{
537 GtkWidget *widget;
538
539 // Creating the table
540 GtkWidget *container = gtk_grid_new();
541 gtk_box_pack_start(GTK_BOX(parent), container, TRUE, TRUE, 0);
542 gtk_widget_show(container);
543
544 /* initialize toolboxes panels */
549
550 /* initialize the main drawing widget (center) */
551 widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
552 gtk_widget_set_name(widget, "main-widget");
553 gtk_widget_set_hexpand(GTK_WIDGET(widget), TRUE);
554 gtk_widget_set_vexpand(GTK_WIDGET(widget), TRUE);
555 gtk_grid_attach(GTK_GRID(container), widget, 2, 1, 1, 1);
556
557 /* initialize the thumb panel */
559
560 /* setup center drawing area */
561 GtkWidget *ocda = gtk_overlay_new();
562 gtk_widget_set_size_request(ocda, DT_PIXEL_APPLY_DPI(200), DT_PIXEL_APPLY_DPI(200));
563 gtk_widget_show(ocda);
564
565 GtkWidget *cda = gtk_drawing_area_new();
566 gtk_widget_set_hexpand(ocda, TRUE);
567 gtk_widget_set_vexpand(ocda, TRUE);
568 gtk_widget_set_app_paintable(cda, TRUE);
569 gtk_widget_set_events(cda, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK
570 | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
572 // The center canvas is the MAIN child of the overlay (gtk_container_add), NOT an overlay child.
573 // GtkOverlay renders overlay children through their own offscreen GdkWindow; on Wayland that
574 // path goes stale until a pointer event invalidates it, and because the canvas is the heavily,
575 // continuously repainted darkroom surface, that stale offscreen subsurface also starves the
576 // toplevel's frame commits, leaving the chrome (global menu, window top border) blank until
577 // hover (issues #877/#888). The main child draws into the overlay's own window instead. Unlike
578 // the thumbnail grids, a GtkDrawingArea requests ~0px so it does not pin the overlay's minimum
579 // size. The thumbtable, log and toast overlays still float above it.
580 gtk_container_add(GTK_CONTAINER(ocda), cda);
581
582 // Add the reserved overlay for the thumbtable in central position
583 // Then we insert into container, instead of dynamically adding/removing a new overlay
584 // because log and toast messages need to go on top too.
585 gtk_overlay_add_overlay(GTK_OVERLAY(ocda), ui->thumbtable_lighttable->parent_overlay);
586
587 gtk_box_pack_start(GTK_BOX(widget), ocda, TRUE, TRUE, 0);
588
589 ui->center = cda;
590 ui->center_base = ocda;
591
592 /* center should redraw when signal redraw center is raised*/
594 G_CALLBACK(_ui_widget_redraw_callback), ui->center);
595
596 gtk_widget_show_all(container);
597}
598
600{
601 // Avoid dangling UI pointers during shutdown: background threads might query
602 // thumbnail info while mipmap cache is being flushed.
603 dt_thumbtable_t *filmstrip = ui->thumbtable_filmstrip;
604 dt_thumbtable_t *lighttable = ui->thumbtable_lighttable;
605
606 ui->thumbtable_filmstrip = NULL;
607 ui->thumbtable_lighttable = NULL;
608
609 if(filmstrip) dt_thumbtable_cleanup(filmstrip);
610 if(lighttable) dt_thumbtable_cleanup(lighttable);
611}
612
613
615{
616 ui->header = g_malloc0(sizeof(dt_header_t));
617
618#ifdef MERGE_MENUBAR
619 // Remove useless desktop environment titlebar. We will handle closing buttons internally
620 ui->header->titlebar = gtk_header_bar_new();
621 gtk_widget_set_size_request(ui->header->titlebar, -1, -1);
622 gtk_window_set_titlebar(GTK_WINDOW(ui->main_window), ui->header->titlebar);
623
624 // Reset header bar properties
625 gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(ui->header->titlebar), FALSE);
626 gtk_header_bar_set_decoration_layout(GTK_HEADER_BAR(ui->header->titlebar), NULL);
627
628 // Gtk mandatorily adds an empty label that is still "visible" for the title.
629 // Since it's centered, it can collide with the hinter width.
630 // Plus it adds mandatory padding. AKA scrap that.
631 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
632 gtk_header_bar_set_custom_title(GTK_HEADER_BAR(ui->header->titlebar), box);
633 gtk_widget_set_no_show_all(box, TRUE);
634#else
635 ui->header->titlebar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
636#endif
637
638 gtk_widget_show(ui->header->titlebar);
639}
640
642{
643#ifdef MERGE_MENUBAR
644 gtk_header_bar_pack_start(GTK_HEADER_BAR(ui->header->titlebar), widget);
645#else
646 gtk_box_pack_start(GTK_BOX(ui->header->titlebar), widget, FALSE, FALSE, 0);
647#endif
648}
649
651{
652#ifdef MERGE_MENUBAR
653 gtk_header_bar_pack_end(GTK_HEADER_BAR(ui->header->titlebar), widget);
654#else
655 gtk_box_pack_end(GTK_BOX(ui->header->titlebar), widget, FALSE, FALSE, 0);
656#endif
657}
658
660{
661 dt_ctl_switch_mode_to("lighttable");
662}
663
664void _close_callback(GtkWidget *w, gpointer data)
665{
666 gtk_window_close(GTK_WINDOW((GtkWidget *)data));
667}
668
669void _iconify_callback(GtkWidget *w, gpointer data)
670{
671 gtk_window_iconify(GTK_WINDOW((GtkWidget *)data));
672}
673
675{
676 dt_accels_search(dt_gui_get_accels(), GTK_WINDOW(dt_gui_get_ui()->main_window), w);
677}
678
680{
681 /* if user_pref != merge menubar in titlebar */
682 GtkWidget *parent = ui->top_panel;
683 gtk_box_pack_start(GTK_BOX(parent), ui->header->titlebar, TRUE, TRUE, 0);
684 /* endif */
685
686 /* Init top-level menus */
687 ui->header->menu_bar = gtk_menu_bar_new();
688 gtk_widget_set_name(ui->header->menu_bar, "menu-bar");
689 gchar *labels [DT_MENU_LAST] = { _("_File"), _("_Edit"), _("_Selection"), _("_Image"), _("_Styles"), _("_Run"), _("_Display"), _("_Ateliers"), _("_Help") };
690 for(int i = 0; i < DT_MENU_LAST; i++)
691 {
692 ui->header->item_lists[i] = NULL;
693 add_top_menu_entry(ui->header->menu_bar, ui->header->menus, &ui->header->item_lists[i], i, labels[i]);
694 }
695
696 gtk_widget_set_halign(ui->header->menu_bar, GTK_ALIGN_START);
697 gtk_widget_set_hexpand(ui->header->menu_bar, FALSE);
698
699 /* Populate sub-menus */
709
711 gtk_widget_show_all(ui->header->menu_bar);
712
713 GtkWidget *search_button = gtk_button_new_from_icon_name("edit-find", GTK_ICON_SIZE_SMALL_TOOLBAR);
714 gtk_button_set_label (GTK_BUTTON(search_button), _("Search actions..."));
715 gtk_widget_set_halign(search_button, GTK_ALIGN_CENTER);
716 gtk_widget_set_valign(search_button, GTK_ALIGN_CENTER);
717 gtk_widget_set_hexpand(search_button, TRUE);
718 gtk_widget_set_name(search_button, "search-button");
719 g_signal_connect(G_OBJECT(search_button), "clicked", G_CALLBACK(_open_accel_search_callback), NULL);
720 dt_ui_titlebar_pack_start(ui, search_button);
721 gtk_widget_show(search_button);
722
723 // From there, we pack_end meaning it should be done in reverse order of appearance
724 ui->header->close = gtk_button_new_from_icon_name("window-close", GTK_ICON_SIZE_LARGE_TOOLBAR);
725 g_signal_connect(G_OBJECT(ui->header->close), "clicked", G_CALLBACK(_close_callback), ui->main_window);
726 gtk_widget_set_size_request(ui->header->close, 24, 24);
727 dt_gui_add_class(ui->header->close, "window-button");
729
730 ui->header->iconify = gtk_button_new_from_icon_name("window-minimize", GTK_ICON_SIZE_LARGE_TOOLBAR);
731 g_signal_connect(G_OBJECT(ui->header->iconify), "clicked", G_CALLBACK(_iconify_callback), ui->main_window);
732 gtk_widget_set_size_request(ui->header->iconify, 24, 24);
733 dt_gui_add_class(ui->header->iconify, "window-button");
735
736 ui->header->home = gtk_button_new_from_icon_name("go-home", GTK_ICON_SIZE_LARGE_TOOLBAR);
737 gtk_widget_set_tooltip_text(ui->header->home, _("Go back to lighttable"));
738 g_signal_connect(G_OBJECT(ui->header->home), "clicked", _home_callback, NULL);
739 gtk_widget_set_size_request(ui->header->home, 24, 24);
740 dt_gui_add_class(ui->header->home, "window-button");
742 gtk_widget_show(ui->header->home);
743
744 GtkWidget *spacer = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
745 dt_ui_titlebar_pack_end(ui, spacer);
746 gtk_widget_show(spacer);
747
748 /* Init hinter */
749 ui->header->hinter = gtk_label_new("");
750 gtk_label_set_ellipsize(GTK_LABEL(ui->header->hinter), PANGO_ELLIPSIZE_END);
751 gtk_widget_set_name(ui->header->hinter, "hinter");
752 gtk_widget_set_halign(ui->header->hinter, GTK_ALIGN_END);
753 gtk_label_set_justify(GTK_LABEL(ui->header->hinter), GTK_JUSTIFY_RIGHT);
754 gtk_label_set_line_wrap(GTK_LABEL(ui->header->hinter), TRUE);
756 gtk_widget_show(ui->header->hinter);
757
758 spacer = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
759 dt_ui_titlebar_pack_end(ui, spacer);
760 gtk_widget_show(spacer);
761
762 /* Image info */
763 ui->header->image_info = gtk_label_new("");
764 gtk_label_set_ellipsize(GTK_LABEL(ui->header->image_info), PANGO_ELLIPSIZE_MIDDLE);
765 gtk_widget_set_name(ui->header->image_info, "image-info");
767 gtk_widget_show(ui->header->image_info);
768}
769
770void dt_ui_set_image_info_label(dt_ui_t *ui, const char *label)
771{
772 if(IS_NULL_PTR(ui) || IS_NULL_PTR(ui->header) || !GTK_IS_LABEL(ui->header->image_info)) return;
773 gtk_label_set_markup(GTK_LABEL(ui->header->image_info), label);
774}
775
776void dt_ui_set_window_buttons_visible(dt_ui_t *ui, gboolean visible)
777{
778 gtk_widget_set_visible(ui->header->close, visible);
779 gtk_widget_set_visible(ui->header->iconify, visible);
780}
781
782void dt_hinter_set_message(dt_ui_t *ui, const char *message)
783{
784 if(IS_NULL_PTR(ui) || IS_NULL_PTR(ui->header) || !GTK_IS_LABEL(ui->header->hinter)) return;
785 // Remove hacky attempts of line wrapping with hardcoded newline :
786 // Line wrap is handled by Gtk at the label scope.
787 char **split = g_strsplit(message, "\n", -1);
788 char *joined = g_strjoinv(", ", split);
789 gtk_label_set_markup(GTK_LABEL(ui->header->hinter), joined);
790 dt_free(joined);
791 g_strfreev(split);
792}
793
794
796{
797 if(IS_NULL_PTR(ui) || IS_NULL_PTR(ui->header)) return;
798
799 if(!IS_NULL_PTR(ui->header->titlebar) && GTK_IS_WIDGET(ui->header->titlebar))
800 {
801 gtk_widget_destroy(ui->header->titlebar);
802 ui->header->titlebar = NULL;
803 }
804
805 for(int i = 0; i < DT_MENU_LAST; i++)
806 {
807 g_list_free(ui->header->item_lists[i]);
808 ui->header->item_lists[i] = NULL;
809 }
810 dt_free(ui->header);
811 ui->header = NULL;
812}
void dt_accels_search(dt_accels_t *accels, GtkWindow *main_window, GtkWidget *anchor)
Handle default and user-set shortcuts (accelerators)
GtkWidget * dt_gui_main_window(void)
struct dt_accels_t * dt_gui_get_accels(void)
struct dt_ui_t * dt_gui_get_ui(void)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
uint32_t container(dt_lib_module_t *self)
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
int dt_conf_get_bool(const char *name)
int dt_conf_key_exists(const char *key)
Does key have a value?
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.
void dt_ctl_switch_mode_to(const char *mode)
Definition control.c:701
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:528
struct dt_lib_t * dt_lib_get_global(void)
Definition darktable.c:621
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
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:129
void append_display(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition display.c:447
GtkWidget * window
void append_edit(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition edit.c:523
void append_file(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition file.c:392
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:245
gboolean dt_iop_gui_owns_widget(const dt_iop_module_t *module, const GtkWidget *target)
float *const restrict const size_t k
#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
void add_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index, gchar *label)
Definition menu.c:509
void append_views(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition views.c:60
void append_select(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition select.c:71
void append_run(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition run.c:215
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
char * key
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
const char * name
Definition pdf.h:90
GtkWidget * dtgtk_resize_handle_new(GtkOrientation orientation, gboolean invert, const char *tooltip, dtgtk_resize_handle_get_size_f get_size, dtgtk_resize_handle_resize_f resize, gpointer user_data)
Create a themed handle widget driving one-dimensional resize gestures.
GtkWidget * dtgtk_side_panel_new()
Definition sidepanel.c:62
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ 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:77
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
const float uint32_t state[4]
GList * iop
Definition develop.h:269
GtkWidget * scroll_to[2]
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
GtkWidget * parent_overlay
Definition thumbtable.h:163
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:155
#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.
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
@ DT_THUMBTABLE_MODE_FILMSTRIP
Definition thumbtable.h:66
@ DT_THUMBTABLE_MODE_FILEMANAGER
Definition thumbtable.h:65
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:99
dt_darkroom_layout_t dt_view_darkroom_get_layout(dt_view_manager_t *vm)
Definition view.c:1335
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:139
GdkEventMask dt_widget_scroll_mask(void)
void dt_widget_set_min_panel_width(gint width)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
static void _ui_panel_size_changed(GtkAdjustment *adjustment, GParamSpec *pspec, gpointer user_data)
GtkWidget * dt_ui_toast_msg(dt_ui_t *ui)
GtkWidget * dt_ui_center(dt_ui_t *ui)
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)
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)
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)
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)
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