Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
bauhaus.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011-2016 johannes hanika.
4 Copyright (C) 2012-2013 Henrik Andersson.
5 Copyright (C) 2012 Jean-Sébastien Pédron.
6 Copyright (C) 2012, 2014, 2017, 2019-2020 parafin.
7 Copyright (C) 2012 Pascal de Bruijn.
8 Copyright (C) 2012 Richard Wonka.
9 Copyright (C) 2012-2014, 2016-2020 Tobias Ellinghaus.
10 Copyright (C) 2012, 2016 Ulrich Pegelow.
11 Copyright (C) 2013, 2019, 2021-2022 Aldric Renaudin.
12 Copyright (C) 2013 Dennis Gnad.
13 Copyright (C) 2013-2016 Roman Lebedev.
14 Copyright (C) 2013 Simon Spannagel.
15 Copyright (C) 2013 Yari Adan.
16 Copyright (C) 2015 Jérémy Rosen.
17 Copyright (C) 2016 Asma.
18 Copyright (C) 2017 Christian Stussak.
19 Copyright (C) 2017-2018, 2020-2021 Dan Torop.
20 Copyright (C) 2017 luzpaz.
21 Copyright (C) 2018 Matthieu Moy.
22 Copyright (C) 2018-2022 Pascal Obry.
23 Copyright (C) 2018 Peter Budai.
24 Copyright (C) 2019-2020, 2022-2026 Aurélien PIERRE.
25 Copyright (C) 2019-2022 Diederik Ter Rahe.
26 Copyright (C) 2019 emeikei.
27 Copyright (C) 2019-2021 Heiko Bauke.
28 Copyright (C) 2019 Jakub Filipowicz.
29 Copyright (C) 2019 jakubfi.
30 Copyright (C) 2020-2022 Chris Elston.
31 Copyright (C) 2020 Hubert Kowalski.
32 Copyright (C) 2020 matt-maguire.
33 Copyright (C) 2020-2021 Philippe Weyland.
34 Copyright (C) 2020-2021 Ralf Brown.
35 Copyright (C) 2020 Érico Rolim.
36 Copyright (C) 2021 lhietal.
37 Copyright (C) 2021 Mark-64.
38 Copyright (C) 2021 Martin Straeten.
39 Copyright (C) 2021 Paolo DePetrillo.
40 Copyright (C) 2022 Martin Bařinka.
41 Copyright (C) 2022 Nicolas Auffray.
42 Copyright (C) 2023 Alynx Zhou.
43 Copyright (C) 2023 Luca Zulberti.
44 Copyright (C) 2025-2026 Guillaume Stutin.
45
46 darktable is free software: you can redistribute it and/or modify
47 it under the terms of the GNU General Public License as published by
48 the Free Software Foundation, either version 3 of the License, or
49 (at your option) any later version.
50
51 darktable is distributed in the hope that it will be useful,
52 but WITHOUT ANY WARRANTY; without even the implied warranty of
53 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
54 GNU General Public License for more details.
55
56 You should have received a copy of the GNU General Public License
57 along with darktable. If not, see <http://www.gnu.org/licenses/>.
58*/
59
60#include "widgets/gdkkeys.h"
61#include "widgets/draw.h"
63#include "widgets/paint.h"
64#include "widgets/bauhaus.h"
65#include "math/calculator.h"
66#include "math/math.h"
67#include "system/macros.h"
68
69
72
73#include <glib/gi18n.h>
74#ifdef GDK_WINDOWING_QUARTZ
75#include "osx/osx.h"
76#endif
77
78#include <math.h>
79
80#include <pango/pangocairo.h>
82#ifdef GDK_WINDOWING_WAYLAND
83#include <gdk/gdkwayland.h>
84#endif
85
86G_DEFINE_TYPE(DtBauhausWidget, dt_bh, GTK_TYPE_DRAWING_AREA)
87
88// WARNING
89// A lot of GUI setters/getters functions used to have type checking on input widgets
90// and silently returned early if the types were not ok (like trying to set a combobox using slider methods).
91// This only hides programmers mistakes in a way that prevents the soft to crash,
92// but it still leads to faulty GUI interactions and inconsistent widgets values that might be hard to spot.
93// In november 2023, they got removed in order to fail explicitly and possibly crash.
94// If that's not enough, we can always add assertions in the code.
95
96#define DEBUG 0
97
98// fwd declare
99static gboolean dt_bauhaus_popup_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data);
100static gboolean dt_bauhaus_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
101static gboolean _widget_draw(GtkWidget *widget, cairo_t *crf);
102static gboolean _widget_scroll(GtkWidget *widget, GdkEventScroll *event);
103static gboolean _widget_key_press(GtkWidget *widget, GdkEventKey *event);
104static void _get_preferred_width(GtkWidget *widget, gint *minimum_size, gint *natural_size);
105static void _get_preferred_height(GtkWidget *widget, gint *minimum_size, gint *natural_size);
106static void _style_updated(GtkWidget *widget);
107static void dt_bauhaus_widget_accept(struct dt_bauhaus_widget_t *w);
108static void dt_bauhaus_widget_reject(struct dt_bauhaus_widget_t *w);
109static void _combobox_set(GtkWidget *widget, const int pos);
110
111// !!! EXECUTIVE NOTE !!!
112// Sizing and spacing need to be declared once only in getters/setters functions below.
113// The rest of the code accesses those values only through the getters.
114// Doxygen docstrings need to be added to explain what is computed, based on what.
115// Code changes that recompute sizing or coordinates outside of getters/setters will be refused.
116
124{
125 if(IS_NULL_PTR(w->margin)) w->margin = gtk_border_new();
126 if(IS_NULL_PTR(w->padding)) w->padding = gtk_border_new();
127 GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(w));
128 const GtkStateFlags state = gtk_widget_get_state_flags(GTK_WIDGET(w));
129 gtk_style_context_get_margin(context, state, w->margin);
130 gtk_style_context_get_padding(context, state, w->padding);
131
132 // Deal with borders by extending margins because we don't care
133 GtkBorder *borders = gtk_border_new();
134 gtk_style_context_get_border(context, state, borders);
135 w->margin->left += borders->left;
136 w->margin->right += borders->right;
137 w->margin->top += borders->top;
138 w->margin->bottom += borders->bottom;
139 gtk_border_free(borders);
140}
141
150{
151 return w->bauhaus->line_height * 1.4;
152}
153
161 const dt_bauhaus_combobox_entry_t *entry)
162{
163 const float row_height_factor = (entry->is_separator) ? MAX(entry->row_height_factor, 0.1f) : 1.f;
164 return _bh_get_row_height(w) * row_height_factor;
165}
166
174{
175 if(w->show_quad)
176 return w->bauhaus->quad_width;
177 else
178 return 0.;
179}
180
189{
190 GtkWidget *box_reference = (widget) ? widget : GTK_WIDGET(w);
191 GtkAllocation allocation;
192 gtk_widget_get_allocation(box_reference, &allocation);
193 return allocation.width - w->margin->left - w->margin->right - w->padding->left - w->padding->right;
194}
195
205static double _widget_get_main_width(struct dt_bauhaus_widget_t *w, GtkWidget *widget, double *total_width)
206{
207 const double tot_width = _widget_get_total_width(w, widget);
208 // Slider cursors are centered on the scale bounds, so the scale starts one
209 // cursor radius after the text origin while keeping its right edge flush.
210 const double slider_cursor_radius = (w->type == DT_BAUHAUS_SLIDER) ? 0.5 * w->bauhaus->marker_size : 0.0;
211 if(total_width) *total_width = tot_width;
212 return tot_width - _widget_get_quad_width(w) - 2. * INNER_PADDING - slider_cursor_radius;
213}
214
223{
224 GtkWidget *box_reference = (widget) ? widget : GTK_WIDGET(w);
225 GtkAllocation allocation;
226 gtk_widget_get_allocation(box_reference, &allocation);
227 return allocation.height - w->margin->top - w->margin->bottom - w->padding->top - w->padding->bottom;
228}
229
248static double _get_combobox_height(GtkWidget *widget)
249{
250 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
251 return w->margin->top + w->padding->top + w->margin->bottom + w->padding->bottom
252 + w->bauhaus->line_height;
253}
254
255static double _get_slider_height(GtkWidget *widget)
256{
257 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
258 return w->margin->top + w->padding->top + w->margin->bottom + w->padding->bottom + INNER_PADDING / 2.
260}
261
263{
264 return w->bauhaus->line_height + INNER_PADDING + w->bauhaus->baseline_size / 2.0f;
265}
266
268{
269 // Total height of the text label + slider baseline, discarding padding
271
272}
273
275{
276 dt_gui_module_t *module = (dt_gui_module_t *)(w->module);
278
279 // Need to run the populating callback first for dynamically-populated ones.
280 if(d->populate) d->populate(GTK_WIDGET(w), module);
281 if(!d->entries->len) return 0.;
282
283 double height = 0.;
284
285 // Add an extra sit for user keyboard input if any
286 if(w->bauhaus->keys_cnt > 0) height += _bh_get_row_height(w);
287
288 for(int i = 0; i < d->entries->len; i++)
289 {
290 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
292 }
293
294 return height;
295}
296
297
306static void _translate_cursor(double *x, double *y, struct dt_bauhaus_widget_t *const w)
307{
308 const double slider_cursor_radius = (w->type == DT_BAUHAUS_SLIDER) ? 0.5 * w->bauhaus->marker_size : 0.0;
309 const double left_offset = w->margin->left + w->padding->left + slider_cursor_radius;
310 *x -= left_offset;
311 *y -= w->margin->top + w->padding->top;
312}
313
314// Convenience state for cursor position over widget
316{
317 BH_REGION_OUT = 0, // we are outside the padding box
318 BH_REGION_MAIN, // we are on the slider scale or combobox label/value, aka out of the quad button
319 BH_REGION_QUAD, // we are on the quad button
321
334static _bh_active_region_t _bh_get_active_region(GtkWidget *widget, double *x, double *y, double *width, GtkWidget *popup)
335{
336 struct dt_bauhaus_widget_t *const w = DT_BAUHAUS_WIDGET(widget);
337
338 // The widget to use as a reference to fetch allocation and compute sizes
339 GtkWidget *box_reference = (popup) ? popup : widget;
340 double total_width;
341 const double main_width = _widget_get_main_width(w, box_reference, &total_width);
342 const double main_height = _widget_get_main_height(w, box_reference);
343
344 if(width) *width = main_width;
345 _translate_cursor(x, y, w);
346
347 // Check if we are within popup frame
348 if(*y < 0. || *y > main_height || *x < 0. || *x > total_width)
349 return BH_REGION_OUT;
350
351 // Check where we are horizontally
352 // The quad now begins exactly 2*INNER_PADDING past the main area for both
353 // sliders and comboboxes (see the quad draw sites), so use the same boundary.
354 if(*x <= main_width + 2. * INNER_PADDING)
355 return BH_REGION_MAIN;
356 else
357 return BH_REGION_QUAD;
358
359 return BH_REGION_OUT;
360}
361
369static float _bh_round_to_n_digits(const struct dt_bauhaus_widget_t *const w, float x)
370{
371 const dt_bauhaus_slider_data_t *const d = &w->data.slider;
372 const float factor = (float)ipow(10, d->digits);
373 return roundf(x * factor) / factor;
374}
375
382static float _bh_slider_get_min_step(const struct dt_bauhaus_widget_t *const w)
383{
385 return d->factor * (float)ipow(10, d->digits);
386}
387
389{
391 return 10.0 / (_bh_slider_get_min_step(w) * (d->max - d->min));
392}
393
395{
397 {
399 double y = w->bauhaus->mouse_y;
400 d->hovered = -1;
401 gtk_widget_set_tooltip_text(w->bauhaus->popup_area, NULL);
402
403 // If the combobox has a user input row, it is always the first one and has the regular row height.
404 if(w->bauhaus->keys_cnt > 0)
405 {
406 y -= _bh_get_row_height(w);
407 if(y < 0.) return;
408 }
409
410 // Separators may use less vertical space than regular entries.
411 // Accumulate actual row heights so mouse hit-testing stays aligned with drawing.
412 for(int i = 0; i < d->entries->len; i++)
413 {
414 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
415 const double entry_height = _bh_get_combobox_entry_height(w, entry);
416 if(y < entry_height)
417 {
418 d->hovered = i;
419 gtk_widget_set_tooltip_text(w->bauhaus->popup_area, entry->tooltip);
420 return;
421 }
422 y -= entry_height;
423 }
424 }
425}
426
427static _bh_active_region_t _popup_coordinates(dt_bauhaus_t *bh, const double x_root, const double y_root, double *event_x, double *event_y)
428{
429 // Because the popup widget is a floating window, it keeps capturing motion events even if they don't
430 // overlap it. In those events, (x, y) coordinates are expressed in the space of the hovered third-party widget,
431 // meaning their coordinates will seem ok from here (right range regarding height/width of widget) but will belong to something else.
432 // We need to grab absolute coordinates in the main window space to ensure we overlay the widget popup.
433 gint wx, wy;
434 GdkWindow *window = gtk_widget_get_window(bh->popup_window);
435 gdk_window_get_origin(window, &wx, &wy);
436 *event_x = x_root - (double)wx;
437 *event_y = y_root - (double)wy;
438 return _bh_get_active_region(GTK_WIDGET(bh->current), event_x, event_y, NULL, bh->popup_window);
439}
440
441// Ensure the programmatically-focused widget is visible,
442// and all its parents containers expose the right page before grabbing focus.
443#define DT_BAUHAUS_FOCUS_IDLE_SOURCE_KEY "dt-bauhaus-focus-idle-source"
444#define DT_BAUHAUS_FOCUS_IDLE_TRIES_KEY "dt-bauhaus-focus-idle-tries"
445#define DT_BAUHAUS_FOCUS_IDLE_MAX_TRIES 20
446static gboolean ensure_focus_idle(gpointer data)
447{
448 GtkWidget *target = GTK_WIDGET(data);
449 if(!GTK_IS_WIDGET(target)) return G_SOURCE_REMOVE;
450
451 GtkWidget *child = target;
452
453 for(GtkWidget *w = child; w; w = gtk_widget_get_parent(w))
454 {
455 if(GTK_IS_NOTEBOOK(w))
456 {
457 GtkWidget *page = child;
458 while(!IS_NULL_PTR(page) && gtk_widget_get_parent(page) != w)
459 page = gtk_widget_get_parent(page);
460
461 GtkNotebook *nb = GTK_NOTEBOOK(w);
462 const gint page_num = !IS_NULL_PTR(page) ? gtk_notebook_page_num(nb, page) : -1;
463 if(page_num >= 0) gtk_notebook_set_current_page(nb, page_num);
464 }
465 else if(GTK_IS_STACK(w))
466 {
467 GtkWidget *page = child;
468 while(!IS_NULL_PTR(page) && gtk_widget_get_parent(page) != w)
469 page = gtk_widget_get_parent(page);
470
471 GtkWidget *visible_child = gtk_stack_get_visible_child(GTK_STACK(w));
472 if(!IS_NULL_PTR(page) && visible_child != page) gtk_stack_set_visible_child(GTK_STACK(w), page);
473 }
474 child = w;
475 }
476
477 if(gtk_widget_is_drawable(target))
478 {
479 gtk_widget_grab_focus(target);
481 // The widget's own toplevel, rather than the application's main window: this is a debug
482 // trace and has no business knowing which window the host considers primary.
483 GtkWidget *toplevel = gtk_widget_get_toplevel(target);
484 GtkWidget *gtk_focus = (toplevel && gtk_widget_is_toplevel(toplevel))
485 ? gtk_window_get_focus(GTK_WINDOW(toplevel)) : NULL;
486 dt_widget_log("[bauhaus] ensure_focus_idle success target=%s(%p) gtk_focus=%s(%p) scroll_focus=%s(%p)\n",
487 gtk_widget_get_name(target), (void *)target,
488 !IS_NULL_PTR(gtk_focus) ? gtk_widget_get_name(gtk_focus) : "<null>", (void *)gtk_focus,
489 !IS_NULL_PTR(dt_widget_scroll_focus()) ? gtk_widget_get_name(dt_widget_scroll_focus()) : "<null>",
490 (void *)dt_widget_scroll_focus());
491 g_object_set_data(G_OBJECT(target), DT_BAUHAUS_FOCUS_IDLE_SOURCE_KEY, NULL);
492 g_object_set_data(G_OBJECT(target), DT_BAUHAUS_FOCUS_IDLE_TRIES_KEY, NULL);
493 return G_SOURCE_REMOVE;
494 }
495
496 const int tries = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(target), DT_BAUHAUS_FOCUS_IDLE_TRIES_KEY)) + 1;
498 {
499 dt_widget_log("[bauhaus] ensure_focus_idle abort target=%s(%p) tries=%d drawable=%d\n",
500 gtk_widget_get_name(target), (void *)target, tries, gtk_widget_is_drawable(target));
501 g_object_set_data(G_OBJECT(target), DT_BAUHAUS_FOCUS_IDLE_SOURCE_KEY, NULL);
502 g_object_set_data(G_OBJECT(target), DT_BAUHAUS_FOCUS_IDLE_TRIES_KEY, NULL);
503 return G_SOURCE_REMOVE;
504 }
505
506 g_object_set_data(G_OBJECT(target), DT_BAUHAUS_FOCUS_IDLE_TRIES_KEY, GINT_TO_POINTER(tries));
507 return G_SOURCE_CONTINUE;
508}
509
510gboolean dt_bauhaus_focus_in_callback(GtkWidget *widget, GdkEventFocus event, gpointer user_data)
511{
512 // Scroll focus needs to be managed separately from Gtk focus
513 // because of Gtk notebooks (tabs): Gtk gives focus automatically to the first
514 // notebook child, which is not what we want for scroll event capture.
516 gtk_widget_queue_draw(widget);
517 return FALSE;
518}
519
520gboolean dt_bauhaus_focus_out_callback(GtkWidget *widget, GdkEventFocus event, gpointer user_data)
521{
522 // Scroll focus is released from leave-notify so wheel capture survives
523 // transient Gtk focus changes while the pointer is still over the widget.
524 gtk_widget_queue_draw(widget);
525 return FALSE;
526}
527
528
529gboolean dt_bauhaus_focus_callback(GtkWidget *widget, GtkDirectionType direction, gpointer data)
530{
531 // Let user focus on the next/previous widget on arrow up/down
532 if(direction == GTK_DIR_UP || direction == GTK_DIR_DOWN) return FALSE;
533
534 // Any other key stroke is captured
535 return TRUE;
536}
537
538gboolean _action_request_focus(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
539 GdkModifierType modifier, gpointer data)
540{
541 if(IS_NULL_PTR(data) || IS_NULL_PTR(accelerable))
542 {
543 dt_widget_message(_("The target widget of the action does not exist anymore"));
544 fprintf(stderr, "The target widget of the action does not exist anymore");
545 return FALSE;
546 }
547
549
550 // Make sure the parent module widget is visible, if we know it,
551 // because we can't grab focus on invisible widgets
552 if(!IS_NULL_PTR(w->module))
553 {
554 // Whatever "become visible" means for this module type is the module's business.
555 if(w->module->ensure_visible) w->module->ensure_visible(w->module);
556 w->module->focus(w->module, FALSE);
557 }
558
559 GtkWidget *target = GTK_WIDGET(data);
560 const guint previous_source = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(target), DT_BAUHAUS_FOCUS_IDLE_SOURCE_KEY));
561 if(previous_source > 0)
562 g_source_remove(previous_source);
563
564 g_object_set_data(G_OBJECT(target), DT_BAUHAUS_FOCUS_IDLE_TRIES_KEY, GINT_TO_POINTER(0));
565 const guint source = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, ensure_focus_idle, g_object_ref(target),
566 (GDestroyNotify)g_object_unref);
567 g_object_set_data(G_OBJECT(target), DT_BAUHAUS_FOCUS_IDLE_SOURCE_KEY, GUINT_TO_POINTER(source));
568 return TRUE;
569}
570
572{
574
575 int new_pos = d->active;
576 int inc = (delta) > 0 ? 1 : -1;
577 int cur = new_pos + inc;
578 while(delta && cur >= 0 && cur < d->entries->len)
579 {
580 dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, cur);
581 if(entry->sensitive)
582 {
583 new_pos = cur;
584 delta -= inc;
585 }
586 cur += inc;
587 }
588 d->hovered = new_pos;
589 _combobox_set(GTK_WIDGET(w), new_pos);
590}
591
593 dt_bauhaus_combobox_alignment_t alignment, gboolean sensitive,
594 void *data, void (*free_func)(void *))
595{
597 entry->label = g_strdup(label);
598 entry->tooltip = g_strdup(tooltip);
599 entry->alignment = alignment;
600 entry->sensitive = sensitive;
601 entry->row_height_factor = 1.f;
602 entry->data = data;
603 entry->free_func = free_func;
604 return entry;
605}
606
613static dt_bauhaus_combobox_entry_t *new_combobox_separator(const float row_height_factor)
614{
616 entry->label = g_strdup("");
617 entry->tooltip = NULL;
618 entry->sensitive = FALSE;
619 entry->is_separator = TRUE;
620 entry->row_height_factor = CLAMPF(row_height_factor, 0.1f, 1.f);
621 return entry;
622}
623
624static void free_combobox_entry(gpointer data)
625{
627 dt_free(entry->label);
628 dt_free(entry->tooltip);
629 if(entry->free_func)
630 entry->free_func(entry->data);
631 dt_free(entry);
632}
633
634static GdkRGBA *default_color_assign()
635{
636 // helper to initialize a color pointer with red color as a default
637 GdkRGBA color = {.red = 1.0f, .green = 0.0f, .blue = 0.0f, .alpha = 1.0f};
638 return gdk_rgba_copy(&color);
639}
640
641// Vertical alignment of text in its bounding box
648
649// Horizontal alignment of text in its bounding box
656
674 GtkStyleContext *context,
675 cairo_t *cr,
676 GdkRectangle *bounding_box,
677 const char *text,
678 _bh_halign_t halign,
679 _bh_valign_t valign,
680 PangoEllipsizeMode ellipsize,
681 GdkRGBA *bg_color,
682 float *width,
683 float *height,
684 GtkStateFlags state)
685{
686 if(IS_NULL_PTR(text)) return;
687
688 // Prepare context and font properties
689 PangoFontDescription *css_font = NULL;
690 gtk_style_context_get(context, state, GTK_STYLE_PROPERTY_FONT, &css_font, NULL);
691 PangoFontDescription *resolved = pango_font_description_copy(w->bauhaus->pango_font_desc);
692
693 if(css_font)
694 {
695 // Apply only style-related fields, NOT size
696 pango_font_description_set_weight(resolved, pango_font_description_get_weight(css_font));
697 pango_font_description_set_style(resolved, pango_font_description_get_style(css_font));
698 pango_font_description_set_variant(resolved, pango_font_description_get_variant(css_font));
699 pango_font_description_set_stretch(resolved, pango_font_description_get_stretch(css_font));
700 pango_font_description_set_family(resolved, pango_font_description_get_family(css_font));
701 pango_font_description_free(css_font);
702 }
703
704 // --- Propagate GTK's font rendering settings to the Cairo context ---
705 //
706 // gtk_widget_update_pango_context() (called by GTK on realise / screen
707 // change) already populated this context with the system's hinting,
708 // anti-aliasing, subpixel order and hint-metrics settings, sourced from
709 // GtkSettings / Xft / fontconfig. This is the identical source used by
710 // every native GTK widget for text rendering.
711 //
712 // pango_cairo_update_layout() copies font options FROM cr TO the layout's
713 // Pango context. When cr is an intermediate image surface (as used by the
714 // bauhaus off-screen compositing path) Cairo initialises it with its own
715 // defaults — anti-aliasing on, regardless of system settings. Calling
716 // pango_cairo_update_layout() without correcting cr first silently
717 // overwrites everything GTK configured.
718 //
719 // Solution: push the widget Pango context's options onto cr before
720 // pango_cairo_update_layout() runs. The round-trip then becomes a no-op
721 // on font options; only the CTM/DPI are synchronised.
722 //
723 // cairo_set_font_options() makes an internal copy, so the const pointer
724 // lifetime is not a concern.
725 PangoContext *pc = gtk_widget_get_pango_context(GTK_WIDGET(w));
726 dt_gui_cairo_set_font_options(cr, GTK_WIDGET(w));
727
728 // --- Build the layout ---
729 PangoLayout *layout = pango_layout_new(pc);
730 pango_layout_set_font_description(layout, resolved);
731 pango_font_description_free(resolved);
732
733 // Set the actual text
734 pango_layout_set_text(layout, text, -1);
735
736 // Sync layout with Cairo context (CTM, DPI).
737 // Font options are now consistent between cr and pc, so no overwrite occurs.
738 pango_cairo_update_layout(cr, layout);
739
740 // --- Measure ---
741 int pango_width, pango_height;
742 pango_layout_get_size(layout, &pango_width, &pango_height);
743 double text_width = (double)pango_width / PANGO_SCALE;
744 double text_height = fmax((double)pango_height / PANGO_SCALE, w->bauhaus->line_height);
745 if(width) *width = (float)text_width;
746 if(height) *height = (float)text_height;
747
748 // --- Ellipsise on overflow ---
749 if(text_width > bounding_box->width)
750 {
751 pango_layout_set_ellipsize(layout, ellipsize);
752 pango_layout_set_width(layout, (int)(PANGO_SCALE * bounding_box->width));
753 text_width = bounding_box->width;
754 if(width) *width = (float)text_width;
755 pango_cairo_update_layout(cr, layout); // re-sync after constraint change
756 }
757
758 // Paint background color if any - useful to highlight elements in popup list
759 if(!IS_NULL_PTR(bg_color))
760 {
761 cairo_save(cr);
762 cairo_rectangle(cr, bounding_box->x, bounding_box->y,
763 bounding_box->width, bounding_box->height);
764 cairo_set_source_rgba(cr, bg_color->red, bg_color->green,
765 bg_color->blue, bg_color->alpha);
766 cairo_fill(cr);
767 cairo_restore(cr);
768 }
769
770 // Compute the coordinates of the top-left corner as to ensure proper alignment
771 // in bounding box given the dimensions of the label.
772 double x = 0.;
773 switch(halign)
774 {
775 case BH_ALIGN_CENTER:
776 x = bounding_box->x + bounding_box->width / 2. - text_width / 2.;
777 break;
778 case BH_ALIGN_RIGHT:
779 x = bounding_box->x + bounding_box->width - text_width;
780 break;
781 case BH_ALIGN_LEFT:
782 default:
783 x = bounding_box->x;
784 break;
785 }
786
787 double y = 0.;
788 switch(valign)
789 {
790 case BH_ALIGN_MIDDLE:
791 y = bounding_box->y + bounding_box->height / 2. - text_height / 2.;
792 break;
793 case BH_ALIGN_BOTTOM:
794 y = bounding_box->y + bounding_box->height - text_height;
795 break;
796 case BH_ALIGN_TOP:
797 default:
798 y = bounding_box->y;
799 break;
800 }
801
802 // Actually (finally) draw everything in place
803 cairo_move_to(cr, x, y);
804 pango_cairo_show_layout(cr, layout);
805
806 // Cleanup
807 g_object_unref(layout);
808}
809
810static void dt_bauhaus_slider_set_normalized(struct dt_bauhaus_widget_t *w, float pos, gboolean raise);
811static void _dt_bauhaus_slider_set_with_raise(GtkWidget *widget, float pos, gboolean raise);
812
813static double get_slider_line_offset(const double pos, const double scale, const double x, double y, const double line_height)
814{
815 // handle linear startup and rescale y to fit the whole range again
816 // Note : all inputs are in relative coordinates, except pos
817 float offset = 0.f;
818 if(y < line_height)
819 {
820 offset = x - pos;
821 }
822 else
823 {
824 // Renormalize y coordinates below the baseline
825 y = (y - line_height) / (1.0 - line_height);
826 offset = (x - sqf(y) * .5 - (1.0 - sqf(y)) * pos)
827 / (.5 * sqf(y) / scale + (1.0 - sqf(y)));
828 }
829 // clamp to result in a [0,1] range:
830 if(pos + offset > 1.0) offset = 1.0 - pos;
831 if(pos + offset < 0.0) offset = -pos;
832 return offset;
833}
834
835// draw a loupe guideline for the quadratic zoom in in the slider interface:
836static void draw_slider_line(cairo_t *cr, const double pos, const double off, const double scale, const double width, const double height,
837 const double line_height, double line_width)
838{
839 // pos is normalized position [0,1], offset is on that scale.
840 // ht is in pixels here
841 const int steps = 128;
842 const double corrected_height = (height - line_height);
843
844 cairo_set_line_width(cr, line_width);
845 cairo_move_to(cr, width * (pos + off), line_height);
846 const double half_line_width = line_width / 2.;
847 for(int j = 1; j < steps; j++)
848 {
849 const double y = (double)j / (double)(steps - 1);
850 const double x = sqf(y) * .5f * (1.f + off / scale) + (1.0f - sqf(y)) * (pos + off);
851 cairo_line_to(cr, x * width - half_line_width, line_height + y * corrected_height);
852 }
853}
854// -------------------------------
855
856static void _slider_zoom_range(struct dt_bauhaus_widget_t *w, float zoom)
857{
859
860 const float value = dt_bauhaus_slider_get(GTK_WIDGET(w));
861
862 if(roundf(zoom) == 0.f)
863 {
864 d->min = d->soft_min;
865 d->max = d->soft_max;
866 dt_bauhaus_slider_set(GTK_WIDGET(w), value); // restore value (and move min/max again if needed)
867 return;
868 }
869
870 // make sure current value still in zoomed range
871 const float min_visible = _bh_slider_get_min_step(w);
872 const float multiplier = exp2f(zoom / 2.f);
873 const float new_min = value - multiplier * (value - d->min);
874 const float new_max = value + multiplier * (d->max - value);
875 if(new_min >= d->hard_min
876 && new_max <= d->hard_max
877 && new_max - new_min >= min_visible * 10)
878 {
879 d->min = new_min;
880 d->max = new_max;
881 }
882
883 gtk_widget_queue_draw(GTK_WIDGET(w));
884}
885
886static gboolean dt_bauhaus_popup_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
887{
888 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
890 dt_widget_set_scroll_focus(GTK_WIDGET(w));
891 return _widget_scroll(GTK_WIDGET(w), event);
892}
893
894static gboolean dt_bauhaus_popup_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
895{
896 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
898
899 double event_x;
900 double event_y;
901 const _bh_active_region_t active = _popup_coordinates(bh, event->x_root, event->y_root, &event_x, &event_y);
902
903#if DEBUG
904 fprintf(stdout, "x: %i, y: %i, active: %i\n", (int)event_x, (int)event_y, active);
905#endif
906
907 if(active == BH_REGION_OUT) return FALSE;
908
909 // Pass-on new cursor coordinates corrected for padding and margin
910 // and start a redraw. Nothing else.
911 bh->mouse_x = event_x;
912 bh->mouse_y = event_y;
913
914 if(w->type == DT_BAUHAUS_COMBOBOX)
915 {
917 gtk_widget_queue_draw(bh->popup_area);
918 }
919 else
920 {
922 const double main_height = _widget_get_main_height(w, widget);
923 const float mouse_off = get_slider_line_offset(
924 d->oldpos, _bh_slider_get_scale(w), bh->mouse_x / _widget_get_main_width(w, NULL, NULL),
925 bh->mouse_y / main_height, _get_slider_bar_height(w) / main_height);
926
927 if(d->is_dragging)
928 {
929 // On dragging (when holding a click), we commit intermediate values to pipeline for "realtime" preview
930 dt_bauhaus_slider_set_normalized(w, d->oldpos + mouse_off, TRUE);
931 }
932 else
933 {
934 // If not dragging, assume user just wants to take his time to fine-tune the value.
935 d->pos = d->oldpos + mouse_off;
936 gtk_widget_queue_draw(bh->popup_area);
937 }
938 }
939
940 return TRUE;
941}
942
943static gboolean dt_bauhaus_popup_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
944{
945 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_NORMAL, TRUE);
946 return TRUE;
947}
948
949static gboolean dt_bauhaus_popup_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
950{
951 int delay = 0;
952 g_object_get(gtk_settings_get_default(), "gtk-double-click-time", &delay, NULL);
953 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
955
956 if(w && (w->type == DT_BAUHAUS_COMBOBOX) && (event->button == 1)
957 && (event->time >= bh->opentime + delay) && !bh->hiding)
958 {
959 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_ACTIVE, TRUE);
960
962 }
963 else if(bh->hiding)
964 {
966 }
967 return TRUE;
968}
969
970static gboolean dt_bauhaus_popup_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
971{
972 int delay = 0;
973 g_object_get(gtk_settings_get_default(), "gtk-double-click-time", &delay, NULL);
974
975 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
977
978 if(event->button == 1)
979 {
981 && event->time < bh->opentime + delay)
982 {
983 // counts as double click, reset:
985 dt_bauhaus_combobox_set(GTK_WIDGET(w), d->defpos);
987 }
988 else
989 {
990 // only accept left mouse click.
991 // coordinates are set in motion_notify, which also makes sure they are within the valid range.
992 // problems appear with the cornercase where user didn't move the cursor since opening the popup.
993 // aka we need to re-read coordinates here.
994 double event_x;
995 double event_y;
996 const _bh_active_region_t active = _popup_coordinates(bh, event->x_root, event->y_root, &event_x, &event_y);
997
998 if(active == BH_REGION_OUT)
999 {
1002 return TRUE;
1003 }
1004
1005 bh->end_mouse_x = bh->mouse_x = event_x;
1006 bh->end_mouse_y = bh->mouse_y = event_y;
1007
1008 if(w->type == DT_BAUHAUS_SLIDER)
1009 {
1011 d->is_dragging = TRUE;
1012
1013 // Trick to ensure new value ≠ d->pos (so we commit to pipeline), since
1014 // d->pos is used for uncommitted drawings.
1015 const float value = d->pos;
1016 d->pos = d->oldpos;
1018 }
1019 else
1020 {
1023 }
1024 }
1025 bh->hiding = TRUE;
1026 }
1027 else if(event->button == 2 && w->type == DT_BAUHAUS_SLIDER)
1028 {
1029 _slider_zoom_range(w, 0);
1030 gtk_widget_queue_draw(widget);
1031 }
1032 else
1033 {
1035 bh->hiding = TRUE;
1036 }
1037 return TRUE;
1038}
1039
1040static void dt_bauhaus_window_show(GtkWidget *w, gpointer user_data)
1041{
1042 gtk_grab_add(GTK_WIDGET(user_data));
1043}
1044
1045static void dt_bh_init(DtBauhausWidget *class)
1046{
1047 // not sure if we want to use this instead of our code in *_new()
1048 // TODO: the common code from bauhaus_widget_init() could go here.
1049}
1050
1051static gboolean _enter_leave(GtkWidget *widget, GdkEventCrossing *event)
1052{
1053 if(event->type == GDK_ENTER_NOTIFY)
1054 {
1055 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_PRELIGHT, FALSE);
1056 }
1057 else
1058 {
1059 gtk_widget_unset_state_flags(widget, GTK_STATE_FLAG_PRELIGHT);
1060
1061 // GUI refreshes can emit synthetic crossing events while the pointer never
1062 // physically leaves the widget. Only drop wheel capture on a real pointer
1063 // leave from the widget itself.
1064 const gboolean real_leave = event->mode == GDK_CROSSING_NORMAL
1065 && event->detail != GDK_NOTIFY_INFERIOR
1067 if(real_leave && dt_widget_scroll_focus() == widget)
1069 }
1070
1071 gtk_widget_queue_draw(widget);
1072
1073 return FALSE;
1074}
1075
1076static void _widget_finalize(GObject *widget)
1077{
1078 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1079
1080 const guint focus_source = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), DT_BAUHAUS_FOCUS_IDLE_SOURCE_KEY));
1081 if(focus_source > 0)
1082 {
1083 g_source_remove(focus_source);
1084 g_object_set_data(G_OBJECT(widget), DT_BAUHAUS_FOCUS_IDLE_SOURCE_KEY, NULL);
1085 g_object_set_data(G_OBJECT(widget), DT_BAUHAUS_FOCUS_IDLE_TRIES_KEY, NULL);
1086 }
1087
1088 const char *accel_path = g_object_get_data(G_OBJECT(widget), "accel-path");
1089 if(!IS_NULL_PTR(accel_path) && !IS_NULL_PTR(dt_accels_get_global()))
1090 dt_accels_remove_accel(dt_accels_get_global(), accel_path, widget);
1091
1092 if(dt_widget_scroll_focus() == GTK_WIDGET(w))
1094 if(w->type == DT_BAUHAUS_SLIDER)
1095 {
1097 dt_free(d->grad_col);
1098 dt_free(d->grad_pos);
1099 }
1100 else
1101 {
1103 g_ptr_array_free(d->entries, TRUE);
1104 dt_free(d->text);
1105 }
1106 gtk_border_free(w->margin);
1107 gtk_border_free(w->padding);
1108
1109 G_OBJECT_CLASS(dt_bh_parent_class)->finalize(widget);
1110}
1111
1113{
1114 class->signals[DT_BAUHAUS_VALUE_CHANGED_SIGNAL]
1115 = g_signal_new("value-changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
1116 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1117 class->signals[DT_BAUHAUS_QUAD_PRESSED_SIGNAL]
1118 = g_signal_new("quad-pressed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
1119 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1120
1121 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
1122 widget_class->draw = _widget_draw;
1123 widget_class->scroll_event = _widget_scroll;
1124 widget_class->key_press_event = _widget_key_press;
1125 widget_class->get_preferred_width = _get_preferred_width;
1126 widget_class->get_preferred_height = _get_preferred_height;
1127 widget_class->enter_notify_event = _enter_leave;
1128 widget_class->leave_notify_event = _enter_leave;
1129 widget_class->style_updated = _style_updated;
1130 G_OBJECT_CLASS(class)->finalize = _widget_finalize;
1131}
1132
1134{
1135 bauhaus->line_height = 16;
1136 bauhaus->marker_size = 0.25f;
1137
1138 GtkWidget *root_window = dt_widget_root_window();
1139 if(IS_NULL_PTR(root_window)) return;
1140 GtkStyleContext *ctx = gtk_widget_get_style_context(root_window);
1141 gtk_style_context_set_screen(ctx, gtk_widget_get_screen(root_window));
1142
1143 gtk_style_context_lookup_color(ctx, "bauhaus_fg", &bauhaus->color_fg);
1144 gtk_style_context_lookup_color(ctx, "bauhaus_fg_insensitive", &bauhaus->color_fg_insensitive);
1145 gtk_style_context_lookup_color(ctx, "bauhaus_bg", &bauhaus->color_bg);
1146 gtk_style_context_lookup_color(ctx, "bauhaus_border", &bauhaus->color_border);
1147 gtk_style_context_lookup_color(ctx, "bauhaus_fill", &bauhaus->color_fill);
1148 gtk_style_context_lookup_color(ctx, "bauhaus_indicator_border", &bauhaus->indicator_border);
1149 if(!gtk_style_context_lookup_color(ctx, "bauhaus_value", &bauhaus->color_value))
1151 if(!gtk_style_context_lookup_color(ctx, "bauhaus_value_insensitive", &bauhaus->color_value_insensitive))
1153 if(!gtk_style_context_lookup_color(ctx, "bauhaus_value_text", &bauhaus->color_value_text))
1155 if(!gtk_style_context_lookup_color(ctx, "bauhaus_value_text_insensitive", &bauhaus->color_value_text_insensitive))
1157
1158 gtk_style_context_lookup_color(ctx, "graph_bg", &bauhaus->graph_bg);
1159 gtk_style_context_lookup_color(ctx, "graph_exterior", &bauhaus->graph_exterior);
1160 gtk_style_context_lookup_color(ctx, "graph_border", &bauhaus->graph_border);
1161 gtk_style_context_lookup_color(ctx, "graph_grid", &bauhaus->graph_grid);
1162 gtk_style_context_lookup_color(ctx, "graph_fg", &bauhaus->graph_fg);
1163 gtk_style_context_lookup_color(ctx, "graph_fg_active", &bauhaus->graph_fg_active);
1164 gtk_style_context_lookup_color(ctx, "graph_overlay", &bauhaus->graph_overlay);
1165 if(!gtk_style_context_lookup_color(ctx, "graph_scope_restricted", &bauhaus->graph_scope_restricted))
1166 bauhaus->graph_scope_restricted = bauhaus->graph_fg; // For the "restricted" text in the scope
1167 gtk_style_context_lookup_color(ctx, "inset_histogram", &bauhaus->inset_histogram);
1168 gtk_style_context_lookup_color(ctx, "graph_red", &bauhaus->graph_colors[0]);
1169 gtk_style_context_lookup_color(ctx, "graph_green", &bauhaus->graph_colors[1]);
1170 gtk_style_context_lookup_color(ctx, "graph_blue", &bauhaus->graph_colors[2]);
1171 gtk_style_context_lookup_color(ctx, "colorlabel_red",
1173 gtk_style_context_lookup_color(ctx, "colorlabel_yellow",
1175 gtk_style_context_lookup_color(ctx, "colorlabel_green",
1177 gtk_style_context_lookup_color(ctx, "colorlabel_blue",
1179 gtk_style_context_lookup_color(ctx, "colorlabel_purple",
1181
1182 // Publish the resolved palette to the widget layer: paint.c draws colour labels and must
1183 // not reach back into bauhaus for them. Re-published on every theme reload.
1185
1186
1187 // make sure we release previously loaded font
1189 pango_font_description_free(bauhaus->pango_font_desc);
1190
1191 // Create a scratch Cairo surface to draw on
1192 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 128, 128);
1193 cairo_t *cr = cairo_create(cst);
1194
1195 // Let Gtk create a fake label and grab its font description
1196 GtkWidget *ref = gtk_label_new(NULL);
1197 ctx = gtk_widget_get_style_context(ref);
1198 gtk_style_context_get(ctx, GTK_STATE_FLAG_NORMAL, "font", &bauhaus->pango_font_desc, NULL);
1199
1200 PangoContext *context = gtk_widget_get_pango_context(ref);
1201 PangoLayout *layout = pango_layout_new(context);
1202 pango_layout_set_text(layout, "em", -1);
1203 pango_layout_set_font_description(layout, bauhaus->pango_font_desc);
1204 //double dpi = pango_cairo_context_get_resolution(context);
1205 //double scale = gtk_widget_get_scale_factor(ref);
1206 //pango_cairo_context_set_resolution(context, dpi * scale);
1207
1208 // Get advanced font options (system anti-aliasing/hinting/subpixel/kerning)
1210
1211 // Render
1212 pango_cairo_update_layout(cr, layout);
1213 pango_cairo_show_layout(cr, layout);
1214
1215 // Measure the text line height
1216 int pango_width;
1217 int pango_height;
1218 pango_layout_get_size(layout, &pango_width, &pango_height);
1219 g_object_unref(layout);
1220 cairo_destroy(cr);
1221 cairo_surface_destroy(cst);
1222
1223 bauhaus->line_height = pango_height / PANGO_SCALE;
1225
1226 bauhaus->baseline_size = 5; // absolute size in Cairo unit
1227 bauhaus->border_width = 1; // absolute size in Cairo unit
1228 bauhaus->marker_size = pango_height / PANGO_SCALE * 0.6;
1229}
1230
1232{
1233 dt_bauhaus_t * bauhaus = (dt_bauhaus_t *)calloc(1, sizeof(dt_bauhaus_t));
1234 bauhaus->keys_cnt = 0;
1235 bauhaus->current = NULL;
1236 bauhaus->popup_area = gtk_drawing_area_new();
1237 bauhaus->pango_font_desc = NULL;
1238
1240
1241 // this easily gets keyboard input:
1242 // bauhaus->popup_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1243 // but this doesn't flicker, and the above hack with key input seems to work well.
1244 bauhaus->popup_window = gtk_window_new(GTK_WINDOW_POPUP);
1245#ifdef GDK_WINDOWING_QUARTZ
1247#endif
1248 // this is needed for popup, not for toplevel.
1249 // since popup_area gets the focus if we show the window, this is all
1250 // we need.
1251
1252 gtk_window_set_resizable(GTK_WINDOW(bauhaus->popup_window), FALSE);
1253 gtk_window_set_default_size(GTK_WINDOW(bauhaus->popup_window), 30, 30);
1254 gtk_window_set_modal(GTK_WINDOW(bauhaus->popup_window), TRUE);
1255
1256 // Needed for Wayland and Sway :
1258 if(root) gtk_window_set_transient_for(GTK_WINDOW(bauhaus->popup_window), GTK_WINDOW(root));
1259
1260 gtk_window_set_decorated(GTK_WINDOW(bauhaus->popup_window), FALSE);
1261 gtk_window_set_attached_to(GTK_WINDOW(bauhaus->popup_window), NULL);
1262
1263 // needed on macOS to avoid fullscreening the popup with newer GTK
1264 gtk_window_set_type_hint(GTK_WINDOW(bauhaus->popup_window), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
1265
1266 gtk_container_add(GTK_CONTAINER(bauhaus->popup_window), bauhaus->popup_area);
1267 gtk_widget_set_hexpand(bauhaus->popup_area, TRUE);
1268 gtk_widget_set_vexpand(bauhaus->popup_area, TRUE);
1269 gtk_window_set_keep_above(GTK_WINDOW(bauhaus->popup_window), TRUE);
1270 gtk_window_set_gravity(GTK_WINDOW(bauhaus->popup_window), GDK_GRAVITY_STATIC);
1271
1272 gtk_widget_set_can_focus(bauhaus->popup_area, TRUE);
1273 gtk_widget_add_events(bauhaus->popup_area, GDK_POINTER_MOTION_MASK
1274 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1275 | GDK_KEY_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK
1277
1278 GObject *window = G_OBJECT(bauhaus->popup_window);
1279 GObject *area = G_OBJECT(bauhaus->popup_area);
1280 g_object_set_data(area, "bauhaus", bauhaus);
1281
1282 g_signal_connect(window, "show", G_CALLBACK(dt_bauhaus_window_show), area);
1283 g_signal_connect(area, "draw", G_CALLBACK(dt_bauhaus_popup_draw), NULL);
1284 g_signal_connect(area, "motion-notify-event", G_CALLBACK(dt_bauhaus_popup_motion_notify), NULL);
1285 g_signal_connect(area, "leave-notify-event", G_CALLBACK(dt_bauhaus_popup_leave_notify), NULL);
1286 g_signal_connect(area, "button-press-event", G_CALLBACK(dt_bauhaus_popup_button_press), NULL);
1287 g_signal_connect(area, "button-release-event", G_CALLBACK (dt_bauhaus_popup_button_release), NULL);
1288 g_signal_connect(area, "key-press-event", G_CALLBACK(dt_bauhaus_popup_key_press), NULL);
1289 g_signal_connect(area, "scroll-event", G_CALLBACK(dt_bauhaus_popup_scroll), NULL);
1290
1291 // Keys used by key-pressed event handler when the Bauhaus widget has the focus
1292 gchar *path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Increase value (normal step)"));
1294 path, NULL, GDK_KEY_Right, 0);
1295 dt_free(path);
1296 path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Decrease value (normal step)"));
1298 path, NULL, GDK_KEY_Left, 0);
1299 dt_free(path);
1300 path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Increase value (fine step)"));
1302 path, NULL, GDK_KEY_Right, DT_PRIMARY_MASK);
1303 dt_free(path);
1304 path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Decrease value (fine step)"));
1306 path, NULL, GDK_KEY_Left, DT_PRIMARY_MASK);
1307 dt_free(path);
1308 path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Increase value (coarse step)"));
1310 path, NULL, GDK_KEY_Right, GDK_SHIFT_MASK);
1311 dt_free(path);
1312 path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Decrease value (coarse step)"));
1314 path, NULL, GDK_KEY_Left, GDK_SHIFT_MASK);
1315 dt_free(path);
1316 path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Toggle color-picker"));
1318 path, NULL, GDK_KEY_Insert, 0);
1319 dt_free(path);
1320
1321 path = dt_accels_build_path(_("Darkroom/Controls/Comboboxes"), _("Open editing mode"));
1323 path, NULL, GDK_KEY_Return, 0);
1324 dt_free(path);
1325 path = dt_accels_build_path(_("Darkroom/Controls/Comboboxes"), _("Exit editing mode"));
1327 path, NULL, GDK_KEY_Escape, 0);
1328 dt_free(path);
1329 path = dt_accels_build_path(_("Darkroom/Controls/Comboboxes"), _("Select previous (in editing mode)"));
1331 path, NULL, GDK_KEY_Up, 0);
1332 dt_free(path);
1333 path = dt_accels_build_path(_("Darkroom/Controls/Comboboxes"), _("Select next (in editing mode)"));
1335 path, NULL, GDK_KEY_Down, 0);
1336 dt_free(path);
1337 path = dt_accels_build_path(_("Darkroom/Controls/Comboboxes"), _("Validate result (in editing mode)"));
1339 path, NULL, GDK_KEY_Return, 0);
1340 dt_free(path);
1341 path = dt_accels_build_path(_("Darkroom/Controls/Comboboxes"), _("Toggle color-picker"));
1343 path, NULL, GDK_KEY_Insert, 0);
1344 dt_free(path);
1345
1346 return bauhaus;
1347}
1348
1352
1353// fwd declare a few callbacks
1354static gboolean dt_bauhaus_slider_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
1355static gboolean dt_bauhaus_slider_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
1356static gboolean dt_bauhaus_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data);
1357
1358
1359static gboolean dt_bauhaus_combobox_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
1360
1361// end static init/cleanup
1362// =================================================
1363
1364
1365
1366// common initialization
1368{
1369 w->module = self;
1370 w->field = NULL;
1371
1372 w->no_accels = FALSE;
1373 w->no_module_list = FALSE;
1374 w->bauhaus = bauhaus;
1376
1377 // no quad icon and no toggle button:
1378 w->quad_paint = 0;
1379 w->quad_paint_data = NULL;
1380 w->quad_toggle = 0;
1381 w->show_quad = TRUE;
1382 w->expand = TRUE;
1383
1384 gtk_widget_add_events(GTK_WIDGET(w), GDK_POINTER_MOTION_MASK
1385 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1386 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
1387 | GDK_FOCUS_CHANGE_MASK
1389
1390 gtk_widget_set_can_focus(GTK_WIDGET(w), TRUE);
1391 gtk_widget_set_halign(GTK_WIDGET(w), GTK_ALIGN_FILL);
1392 gtk_widget_set_hexpand(GTK_WIDGET(w), TRUE);
1393 g_signal_connect(G_OBJECT(w), "focus-in-event", G_CALLBACK(dt_bauhaus_focus_in_callback), NULL);
1394 g_signal_connect(G_OBJECT(w), "focus-out-event", G_CALLBACK(dt_bauhaus_focus_out_callback), NULL);
1395 g_signal_connect(G_OBJECT(w), "focus", G_CALLBACK(dt_bauhaus_focus_callback), NULL);
1396
1397 dt_gui_add_class(GTK_WIDGET(w), "dt_bauhaus");
1398}
1399
1401{
1402 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1404 d->defpos = def;
1405}
1406
1407
1409{
1410 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1412 float current_position = dt_bauhaus_slider_get(widget);
1413 float desired_position = _bh_round_to_n_digits(w, val);
1414 d->min = MAX(d->min, d->hard_min);
1415 d->soft_min = MAX(d->soft_min, d->hard_min);
1416
1417 if(desired_position > d->hard_max)
1419
1420 if(current_position < desired_position)
1421 dt_bauhaus_slider_set(widget, desired_position);
1422 // else nothing : old position is the new position, just the bound changes
1423}
1424
1426{
1427 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1429 return d->hard_min;
1430}
1431
1433{
1434 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1436 float current_position = dt_bauhaus_slider_get(widget);
1437 float desired_position = _bh_round_to_n_digits(w, val);
1438 d->hard_max = desired_position;
1439 d->max = MIN(d->max, d->hard_max);
1440 d->soft_max = MIN(d->soft_max, d->hard_max);
1441
1442 if(desired_position < d->hard_min)
1443 dt_bauhaus_slider_set_hard_min(widget, desired_position);
1444
1445 if(current_position > desired_position)
1446 dt_bauhaus_slider_set(widget, desired_position);
1447 // else nothing : old position is the new position, just the bound changes
1448}
1449
1451{
1452 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1454 return d->hard_max;
1455}
1456
1458{
1459 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1461 float oldval = dt_bauhaus_slider_get(widget);
1462 d->min = d->soft_min = CLAMP(val, d->hard_min, d->hard_max);
1463 // a bound change is not a user edit: re-apply the value without raising
1465}
1466
1468{
1469 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1471 return d->soft_min;
1472}
1473
1475{
1476 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1478 float oldval = dt_bauhaus_slider_get(widget);
1479 d->max = d->soft_max = CLAMP(val, d->hard_min, d->hard_max);
1480 // a bound change is not a user edit: re-apply the value without raising
1482}
1483
1485{
1486 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1488 return d->soft_max;
1489}
1490
1492{
1493 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1495 d->defpos = def;
1496}
1497
1498void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
1499{
1500 dt_bauhaus_slider_set_soft_min(widget, soft_min);
1501 dt_bauhaus_slider_set_soft_max(widget, soft_max);
1502}
1503
1505{
1506 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1507 if(label)
1508 {
1509 g_strlcpy(w->label, label, sizeof(w->label));
1511 }
1512
1513 if(w->module)
1514 {
1515 // Widgets auto-set by params introspection need to be added to the list of stuff to auto-update
1516 dt_gui_module_t *m = w->module;
1517 if(m && !w->no_module_list)
1518 m->widget_list = g_list_append(m->widget_list, w);
1519
1520 if(m && w->field && !w->no_module_list)
1521 m->widget_list_bh = g_list_append(m->widget_list_bh, w);
1522
1523 // Wire the focusing action
1524 // Note: once the focus is grabbed, interaction with the widget happens through arrow keys or mouse wheel.
1525 // No need to wire all possible events/interactions.
1526 if(m && !w->no_accels && !w->module->deprecated && label)
1527 {
1528 // The module name and the control label are two levels of the path: each one goes
1529 // through dt_accels_build_path() on its own, so that neither can split into more.
1530 gchar *module_name = g_strdup(m->name);
1531 dt_capitalize_label(module_name);
1532
1533 gchar *scope = g_strdup_printf("%s/Modules", m->view);
1534 gchar *module_path = dt_accels_build_path(scope, module_name);
1535 dt_accels_new_darkroom_action(_action_request_focus, widget, widget, module_path, w->label, 0, 0,
1536 _("Focuses the control"));
1537 gchar *darkroom_path = dt_accels_build_path("Darkroom/Modules", module_name);
1538 g_object_set_data_full(G_OBJECT(widget), "accel-path",
1539 dt_accels_build_path(darkroom_path, w->label), dt_free_gpointer);
1540 gtk_widget_set_has_tooltip(widget, TRUE);
1541 dt_free(darkroom_path);
1542 dt_free(module_path);
1543 dt_free(scope);
1544 dt_free(module_name);
1545 }
1546
1547 gtk_widget_queue_draw(GTK_WIDGET(w));
1548 }
1549}
1550
1552{
1553 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1554 return w->label;
1555}
1556
1557void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
1558{
1559 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1560 w->quad_paint = f;
1561 w->quad_paint_flags = paint_flags;
1562 w->quad_paint_data = paint_data;
1563}
1564
1566{
1567 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1568 if(*w->label)
1569 fprintf(stderr, "[dt_bauhaus_widget_set_field] bauhaus label '%s' set before field (needs to be after)\n", w->label);
1570 w->field = field;
1572}
1573
1574// make this quad a toggle button:
1576{
1577 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1578 w->quad_toggle = toggle;
1579}
1580
1582{
1583 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1584 if (active)
1586 else
1587 w->quad_paint_flags &= ~CPF_ACTIVE;
1588 gtk_widget_queue_draw(GTK_WIDGET(w));
1589}
1590
1591void dt_bauhaus_widget_set_quad_visibility(GtkWidget *widget, const gboolean visible)
1592{
1593 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1594 w->show_quad = visible;
1595 gtk_widget_queue_draw(GTK_WIDGET(w));
1596}
1597
1599{
1600 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1601 return (w->quad_paint_flags & CPF_ACTIVE) == CPF_ACTIVE;
1602}
1603
1605{
1606 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1607 if(w->quad_toggle)
1608 {
1610 }
1611 else
1613
1614 g_signal_emit_by_name(G_OBJECT(w), "quad-pressed");
1615}
1616
1618{
1619 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1620 gtk_widget_grab_focus(widget);
1621
1622 if(!w->quad_toggle)
1623 {
1625 w->quad_paint_flags &= ~CPF_ACTIVE;
1626 gtk_widget_queue_draw(GTK_WIDGET(w));
1627 }
1628}
1629
1631{
1632 return dt_bauhaus_slider_new_with_range(bh, self, 0.0, 1.0, 0.1, 0.5, 3);
1633}
1634
1636 float defval, int digits)
1637{
1638 return dt_bauhaus_slider_new_with_range_and_feedback(bh, self, min, max, step, defval, digits, 1);
1639}
1640
1642 float step, float defval, int digits, int feedback)
1643{
1644 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(g_object_new(DT_BAUHAUS_WIDGET_TYPE, NULL));
1645 return dt_bauhaus_slider_from_widget(bh, w,self, min, max, step, defval, digits, feedback);
1646}
1647
1648static void _style_updated(GtkWidget *widget)
1649{
1650 // GTK's own handler applies the CSS opacity (the theme dims *:disabled) and queues the redraw;
1651 // without it, a widget keeps the opacity of whichever state its style was first computed in.
1652 GTK_WIDGET_CLASS(dt_bh_parent_class)->style_updated(widget);
1653
1654 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
1656
1657 // gtk_widget_set_size_request is the minimal preferred, size.
1658 // it NEEDS to be defined and will be contextually adapted, possibly overriden by CSS.
1659 // Thing is Gtk CSS min-width in combination with hexpand is wonky so this is how it should be done.
1660 if(w->type == DT_BAUHAUS_COMBOBOX)
1661 gtk_widget_set_size_request(widget, -1, _get_combobox_height(widget));
1662 else if(w->type == DT_BAUHAUS_SLIDER)
1663 gtk_widget_set_size_request(widget, -1, _get_slider_height(widget));
1664}
1665
1667 float step, float defval, int digits, int feedback)
1668{
1670 _bauhaus_widget_init(bh, w, self);
1672 d->min = d->soft_min = d->hard_min = min;
1673 d->max = d->soft_max = d->hard_max = max;
1674 d->step = step;
1675 // normalize default:
1676 d->defpos = defval;
1677 d->pos = (defval - min) / (max - min);
1678 d->oldpos = d->pos;
1679 d->digits = digits;
1680 d->format = "";
1681 d->factor = 1.0f;
1682 d->offset = 0.0f;
1683
1684 d->grad_cnt = 0;
1685 d->grad_col = NULL;
1686 d->grad_pos = NULL;
1687
1688 d->fill_feedback = feedback;
1689
1690 d->is_dragging = 0;
1691
1692 dt_gui_add_class(GTK_WIDGET(w), "bauhaus_slider");
1693
1694 g_signal_connect(G_OBJECT(w), "button-press-event", G_CALLBACK(dt_bauhaus_slider_button_press), NULL);
1695 g_signal_connect(G_OBJECT(w), "button-release-event", G_CALLBACK(dt_bauhaus_slider_button_release), NULL);
1696 g_signal_connect(G_OBJECT(w), "motion-notify-event", G_CALLBACK(dt_bauhaus_slider_motion_notify), NULL);
1697
1698 return GTK_WIDGET(w);
1699}
1700
1702{
1703 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(g_object_new(DT_BAUHAUS_WIDGET_TYPE, NULL));
1705 return GTK_WIDGET(w);
1706}
1707
1709 int pos, GtkCallback callback, gpointer data, const char **texts)
1710{
1711 GtkWidget *combo = dt_bauhaus_combobox_new(bh, self);
1713 dt_bauhaus_combobox_add_list(combo, texts);
1714 dt_bauhaus_combobox_set(combo, pos);
1715 gtk_widget_set_tooltip_text(combo, tip ? tip : _(label));
1716 if(callback) g_signal_connect(G_OBJECT(combo), "value-changed", G_CALLBACK(callback), data);
1717
1718 return combo;
1719}
1720
1721
1722
1724{
1726 _bauhaus_widget_init(bh, w, self);
1728 d->entries = g_ptr_array_new_full(4, free_combobox_entry);
1729 d->defpos = 0;
1730 d->active = -1;
1731 d->editable = 0;
1732 d->text_align = DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT;
1733 d->entries_ellipsis = PANGO_ELLIPSIZE_END;
1734 d->populate = NULL;
1735 d->text = NULL;
1736
1737 dt_gui_add_class(GTK_WIDGET(w), "bauhaus_combobox");
1738
1739 g_signal_connect(G_OBJECT(w), "button-press-event", G_CALLBACK(dt_bauhaus_combobox_button_press), NULL);
1740}
1741
1743{
1744 if(IS_NULL_PTR(widget)) return NULL;
1745 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1746 if(w->type != DT_BAUHAUS_COMBOBOX) return NULL;
1748
1749 if(d->active >= d->entries->len) d->active = -1;
1750
1751 return d;
1752}
1753
1761{
1762 int count = 0;
1763 for(int i = 0; !IS_NULL_PTR(d) && i < d->entries->len; i++)
1764 {
1765 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
1766 if(!entry->is_separator) count++;
1767 }
1768 return count;
1769}
1770
1778 const gboolean allow_end)
1779{
1780 if(IS_NULL_PTR(d) || pos < 0) return -1;
1781
1782 int selectable_pos = 0;
1783 for(int i = 0; i < d->entries->len; i++)
1784 {
1785 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
1786 if(entry->is_separator) continue;
1787 if(selectable_pos == pos) return i;
1788 selectable_pos++;
1789 }
1790
1791 return (allow_end && selectable_pos == pos) ? (int)d->entries->len : -1;
1792}
1793
1797static int _combobox_entry_pos_to_public(const dt_bauhaus_combobox_data_t *d, const int entry_pos)
1798{
1799 if(IS_NULL_PTR(d) || entry_pos < 0 || entry_pos >= d->entries->len) return -1;
1800
1801 int selectable_pos = 0;
1802 for(int i = 0; i <= entry_pos; i++)
1803 {
1804 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
1805 if(entry->is_separator) continue;
1806 if(i == entry_pos) return selectable_pos;
1807 selectable_pos++;
1808 }
1809
1810 return -1;
1811}
1812
1813void dt_bauhaus_combobox_add_populate_fct(GtkWidget *widget, void (*fct)(GtkWidget *w, void *module))
1814{
1815 if(IS_NULL_PTR(widget)) return;
1816 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1817 if(w->type == DT_BAUHAUS_COMBOBOX)
1818 w->data.combobox.populate = fct;
1819}
1820
1821void dt_bauhaus_combobox_add_list(GtkWidget *widget, const char **texts)
1822{
1823 while(texts && *texts)
1824 dt_bauhaus_combobox_add_full(widget, _(*(texts++)), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, NULL, NULL, TRUE);
1825}
1826
1827void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
1828{
1830}
1831
1832void dt_bauhaus_combobox_add_with_tooltip(GtkWidget *widget, const char *text, const char *tooltip)
1833{
1834 if(IS_NULL_PTR(widget)) return;
1835 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1836 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1840 g_ptr_array_add(d->entries, entry);
1841 if(d->active < 0) d->active = (int)d->entries->len - 1;
1842}
1843
1845{
1846 dt_bauhaus_combobox_add_full(widget, text, align, NULL, NULL, TRUE);
1847}
1848
1850 gpointer data, void (free_func)(void *data), gboolean sensitive)
1851{
1852 if(IS_NULL_PTR(widget)) return;
1853 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1854 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1856 dt_bauhaus_combobox_entry_t *entry = new_combobox_entry(text, NULL, align, sensitive, data, free_func);
1857 g_ptr_array_add(d->entries, entry);
1858 if(d->active < 0) d->active = (int)d->entries->len - 1;
1859}
1860
1865
1866void dt_bauhaus_combobox_add_separator_with_height(GtkWidget *widget, const float row_height_factor)
1867{
1868 if(IS_NULL_PTR(widget)) return;
1869 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1870 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1872 g_ptr_array_add(d->entries, new_combobox_separator(row_height_factor));
1873}
1874
1875void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
1876{
1877 if(IS_NULL_PTR(widget)) return;
1878 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1879 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1881 d->entries_ellipsis = ellipis;
1882}
1883
1884
1886{
1887 if(IS_NULL_PTR(widget)) return;
1888 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1889 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1891 d->editable = editable ? 1 : 0;
1892 if(d->editable && IS_NULL_PTR(d->text))
1893 d->text = calloc(1, DT_BAUHAUS_COMBO_MAX_TEXT);
1894}
1895
1897{
1899
1900 return d ? d->editable : 0;
1901}
1902
1904{
1905 if(IS_NULL_PTR(widget)) return;
1906 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1907 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1909 d->text_align = text_align;
1910}
1911
1913{
1915 if(IS_NULL_PTR(d)) return;
1916 const int entry_pos = _combobox_public_to_entry_pos(d, pos, FALSE);
1917
1918 if(entry_pos < 0) return;
1919
1920 // move active position up if removing anything before it
1921 // or when removing last position that is currently active.
1922 // this also sets active to -1 when removing the last remaining entry in a combobox.
1923 if(d->active > entry_pos || d->active == d->entries->len-1)
1924 d->active--;
1925
1926 g_ptr_array_remove_index(d->entries, entry_pos);
1927}
1928
1929void dt_bauhaus_combobox_insert(GtkWidget *widget, const char *text,int pos)
1930{
1932}
1933
1935 gpointer data, void (*free_func)(void *), int pos)
1936{
1937 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1938 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1940 const int entry_pos = _combobox_public_to_entry_pos(d, pos, TRUE);
1941 if(entry_pos < 0) return;
1942 g_ptr_array_insert(d->entries, entry_pos, new_combobox_entry(text, NULL, align, TRUE, data, free_func));
1943 if(d->active < 0)
1944 d->active = entry_pos;
1945 else if(entry_pos <= d->active)
1946 d->active++;
1947}
1948
1953
1954void dt_bauhaus_combobox_insert_separator_with_height(GtkWidget *widget, int pos, const float row_height_factor)
1955{
1956 if(IS_NULL_PTR(widget)) return;
1957 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1958 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1960 const int entry_pos = _combobox_public_to_entry_pos(d, pos, TRUE);
1961 if(entry_pos < 0) return;
1962 g_ptr_array_insert(d->entries, entry_pos, new_combobox_separator(row_height_factor));
1963 if(entry_pos <= d->active) d->active++;
1964}
1965
1967{
1969
1971}
1972
1974{
1976 if(IS_NULL_PTR(d)) return NULL;
1977
1978 if(d->active < 0)
1979 {
1980 return d->editable ? d->text : NULL;
1981 }
1982 else
1983 {
1984 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
1985 if(entry->is_separator) return NULL;
1986 return entry->label;
1987 }
1988}
1989
1991{
1993 if(IS_NULL_PTR(d) || d->active < 0) return NULL;
1994
1995 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
1996 if(entry->is_separator) return NULL;
1997 return entry->data;
1998}
1999
2001{
2002 if(IS_NULL_PTR(widget)) return;
2003 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2004 if(w->type != DT_BAUHAUS_COMBOBOX) return;
2006 d->active = -1;
2007 g_ptr_array_set_size(d->entries, 0);
2008}
2009
2010const char *dt_bauhaus_combobox_get_entry(GtkWidget *widget, int pos)
2011{
2013 const int entry_pos = _combobox_public_to_entry_pos(d, pos, FALSE);
2014 if(entry_pos < 0) return NULL;
2015
2016 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, entry_pos);
2017 return entry->label;
2018}
2019
2020void dt_bauhaus_combobox_set_text(GtkWidget *widget, const char *text)
2021{
2023 if(IS_NULL_PTR(d) || !d->editable) return;
2024
2025 g_strlcpy(d->text, text, DT_BAUHAUS_COMBO_MAX_TEXT);
2026}
2027
2028
2030{
2031 // Announce the new value. Whoever listens decides what it costs: the history commit
2032 // batches the pipeline recompute it triggers, so emitting on every scroll step is fine
2033 // and this widget has no reason to hold anything back.
2034
2035 // Don't commit anything while a reset is in progress (reloading history, applying a
2036 // style, ...): those set widget values programmatically and must not reach history.
2037 if(dt_gui_widgets_suppressed()) return;
2038
2040 {
2042 w->bauhaus->default_value_changed_callback(GTK_WIDGET(w));
2043 else
2044 fprintf(stderr, "ERROR: %s - %s is set to use default callback but none is provided\n", w->module->name,
2045 w->label);
2046 }
2047 else
2048 {
2049 if(w->module)
2050 fprintf(stderr, "WARNING: %s - %s has an IOP module but doesn't use default callback\n", w->module->name,
2051 w->label);
2052 }
2053
2054 // We need te emit this signal inconditionnaly
2055 g_signal_emit_by_name(G_OBJECT(w), "value-changed");
2056}
2057
2064void _combobox_set(GtkWidget *widget, const int pos)
2065{
2066 if(IS_NULL_PTR(widget)) return;
2067 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2069 const int old_pos = d->active;
2070 const int new_pos = (d->entries) ? CLAMP(pos, -1, (int)d->entries->len - 1)
2071 : -1;
2072 if(new_pos >= 0)
2073 {
2074 // if the new position is a separator, don't change the active position but still redraw to update the visual selection
2075 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, new_pos);
2076 if(entry->is_separator) return;
2077 }
2078
2079 if(old_pos != new_pos)
2080 {
2081 d->active = new_pos;
2082
2083 if(w->bauhaus->current == w)
2084 gtk_widget_queue_draw(w->bauhaus->popup_area);
2085
2086 gtk_widget_queue_draw(GTK_WIDGET(w));
2087
2089 }
2090}
2091
2092// Public API function, called from GUI init and update
2093void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
2094{
2096 const int selectable_count = _combobox_selectable_count(d);
2097 const int public_pos = (selectable_count > 0) ? CLAMP(pos, -1, selectable_count - 1) : -1;
2098 const int entry_pos = _combobox_public_to_entry_pos(d, public_pos, FALSE);
2099 _combobox_set(widget, entry_pos);
2100}
2101
2102
2103gboolean dt_bauhaus_combobox_set_from_text(GtkWidget *widget, const char *text)
2104{
2105 if(IS_NULL_PTR(text)) return FALSE;
2106
2108
2109 for(int i = 0; d && i < d->entries->len; i++)
2110 {
2111 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
2112 if(entry->is_separator) continue;
2113 if(!g_strcmp0(entry->label, text))
2114 {
2115 _combobox_set(widget, i);
2116 return TRUE;
2117 }
2118 }
2119 return FALSE;
2120}
2121
2123{
2125
2126 for(int i = 0; d && i < d->entries->len; i++)
2127 {
2128 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
2129 if(entry->is_separator) continue;
2130 if(GPOINTER_TO_INT(entry->data) == value)
2131 {
2132 _combobox_set(widget, i);
2133 return TRUE;
2134 }
2135 }
2136 return FALSE;
2137}
2138
2140{
2142
2143 return _combobox_entry_pos_to_public(d, d ? d->active : -1);
2144}
2145
2146void dt_bauhaus_combobox_entry_set_sensitive(GtkWidget *widget, int pos, gboolean sensitive)
2147{
2149 const int entry_pos = _combobox_public_to_entry_pos(d, pos, FALSE);
2150 if(entry_pos < 0) return;
2151
2152 dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)g_ptr_array_index(d->entries, entry_pos);
2153 entry->sensitive = sensitive;
2154}
2155
2157{
2158 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2159 if(w->type != DT_BAUHAUS_SLIDER) return;
2161 d->grad_cnt = 0;
2162}
2163
2164void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
2165{
2166 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2167 if(w->type != DT_BAUHAUS_SLIDER) return;
2169
2170 if(IS_NULL_PTR(d->grad_col))
2171 {
2172 d->grad_col = malloc(DT_BAUHAUS_SLIDER_MAX_STOPS * sizeof(*d->grad_col));
2173 d->grad_pos = malloc(DT_BAUHAUS_SLIDER_MAX_STOPS * sizeof(*d->grad_pos));
2174 }
2175 // need to replace stop?
2176 for(int k = 0; k < d->grad_cnt; k++)
2177 {
2178 if(d->grad_pos[k] == stop)
2179 {
2180 d->grad_col[k][0] = r;
2181 d->grad_col[k][1] = g;
2182 d->grad_col[k][2] = b;
2183 return;
2184 }
2185 }
2186 // new stop:
2187 if(d->grad_cnt < DT_BAUHAUS_SLIDER_MAX_STOPS)
2188 {
2189 int k = d->grad_cnt++;
2190 d->grad_pos[k] = stop;
2191 d->grad_col[k][0] = r;
2192 d->grad_col[k][1] = g;
2193 d->grad_col[k][2] = b;
2194 }
2195 else
2196 {
2197 fprintf(stderr, "[bauhaus_slider_set_stop] only %d stops allowed.\n", DT_BAUHAUS_SLIDER_MAX_STOPS);
2198 }
2199}
2200
2201static void dt_bauhaus_draw_indicator(struct dt_bauhaus_widget_t *w, float pos, cairo_t *cr, float wd)
2202{
2203 // draw scale indicator (the tiny triangle)
2204 const float size = w->bauhaus->marker_size;
2205 const float vertical_position = _get_indicator_y_position(w);
2206 const float horizontal_position = pos * wd;
2207
2208 cairo_save(cr);
2209 cairo_translate(cr, horizontal_position, vertical_position);
2210
2212 const gboolean has_colored_background = d->grad_cnt > 0;
2213
2214 if(!has_colored_background && d->fill_feedback)
2215 {
2216 // Plain indicator (regular sliders)
2217 cairo_arc(cr, 0, 0, size / 2., 0, M_PI * 2);
2218 set_color(cr, w->bauhaus->color_fg);
2219 cairo_set_line_width(cr, 0);
2220 cairo_fill(cr);
2221 }
2222 else
2223 {
2224 // Hollow indicator to keep the slider background visible through the marker.
2225 const float border = (size - w->bauhaus->baseline_size) / 2.;
2226 set_color(cr, w->bauhaus->color_fg);
2227 cairo_set_line_width(cr, border);
2228 const float radius = size / 2. - border / 2.;
2229 cairo_arc(cr, 0, 0, radius, 0, M_PI * 2);
2230 cairo_stroke(cr);
2231 }
2232 cairo_restore(cr);
2233}
2234
2235static void dt_bauhaus_draw_quad(struct dt_bauhaus_widget_t *w, cairo_t *cr, const double x, const double y)
2236{
2237 if(!w->show_quad) return;
2238
2239 cairo_save(cr);
2240 if(w->quad_paint)
2241 {
2242 // draw color picker
2243 w->quad_paint(cr, x, y,
2244 w->bauhaus->quad_width, // width
2245 w->bauhaus->quad_width, // height
2247 }
2248 else if(w->type == DT_BAUHAUS_COMBOBOX)
2249 {
2250 // draw combobox chevron
2251 // Inset the chevron from the right edge of the (now flush) quad box so it
2252 // doesn't crowd or overflow the widget border. The quad box itself is placed
2253 // identically to the slider's, so this keeps the chevron in line with slider
2254 // quad icons when widgets are stacked.
2255 // FIXME: Why do we need an extra half-pixel shift to the left for pixel-correctness ?
2256 cairo_translate(cr, x + w->bauhaus->quad_width / 2. - 0.5, y + w->bauhaus->line_height / 2.);
2257 const float r = w->bauhaus->quad_width * .2f;
2258 cairo_move_to(cr, -r, -r * .5f);
2259 cairo_line_to(cr, 0, r * .5f);
2260 cairo_line_to(cr, r, -r * .5f);
2261 cairo_stroke(cr);
2262 }
2263 cairo_restore(cr);
2264}
2265
2273static void dt_bauhaus_draw_baseline(struct dt_bauhaus_widget_t *w, cairo_t *cr, float width)
2274{
2275 // draw line for orientation in slider
2276 cairo_save(cr);
2278 const gboolean has_colored_background = d->grad_cnt > 0;
2279 const float baseline_top = w->bauhaus->line_height + INNER_PADDING;
2280 const float baseline_height = w->bauhaus->baseline_size;
2281 const gboolean is_sensitive = gtk_widget_is_sensitive(GTK_WIDGET(w));
2282
2283 // the background of the line
2284 // Note: to ensure the indicator is not clipped on the left side,
2285 // we push the whole slider of an indicator radius to the right.
2286 // Issue is that leaves a dent on the baseline, so the trick
2287 // is to make it overlap back by a radius on the left.
2288 const double x_origin = -w->bauhaus->marker_size / 3.;
2289 // The left edge was pushed by x_origin to overlap the indicator's leftmost
2290 // resting position. Extend the width by the same amount so the right edge
2291 // stays at `width` (flush with the right-aligned numeric value) instead of
2292 // retreating by |x_origin| and letting the value overhang the baseline.
2293 const double baseline_width = width - x_origin;
2294 // Make sure we use integer coordinates to limit anti-aliasing blur
2295 cairo_rectangle(cr, round(x_origin), round(baseline_top),
2296 round(baseline_width), round(baseline_height));
2297
2298 cairo_pattern_t *gradient = NULL;
2299 if(has_colored_background && is_sensitive)
2300 {
2301 // gradient line as used in some modules for hue, saturation, lightness
2302 const double zoom = (d->max - d->min) / (d->hard_max - d->hard_min);
2303 const double offset = (d->min - d->hard_min) / (d->hard_max - d->hard_min);
2304 gradient = cairo_pattern_create_linear(x_origin, 0, width, baseline_height);
2305 for(int k = 0; k < d->grad_cnt; k++)
2306 cairo_pattern_add_color_stop_rgba(gradient, (d->grad_pos[k] - offset) / zoom,
2307 d->grad_col[k][0], d->grad_col[k][1], d->grad_col[k][2], 0.4f);
2308 cairo_set_source(cr, gradient);
2309 }
2310 else
2311 {
2312 // regular baseline
2313 set_color(cr, w->bauhaus->color_bg);
2314 }
2315
2316 cairo_fill(cr);
2317
2318
2319 if(w->bauhaus->border_width > 0)
2320 {
2321 // Draw the border on top
2322 // We need to recess the coordinates by half a line-width for perfect border matching
2323 const double line_width = 1.;
2324 cairo_rectangle(cr, round(x_origin) + line_width / 2., round(baseline_top) + line_width / 2.,
2325 round(baseline_width) - line_width, round(baseline_height) - line_width);
2326 cairo_set_line_width(cr, line_width);
2328 cairo_stroke(cr);
2329 }
2330
2331 if(gradient) cairo_pattern_destroy(gradient);
2332
2333 // get the reference of the slider aka the position of the 0 value
2334 float origin = fmaxf(fminf((d->factor > 0 ? -d->min - d->offset/d->factor
2335 : d->max + d->offset/d->factor)
2336 / (d->max - d->min),
2337 1.0f) * width,
2338 0.f);
2339
2340 // have a `fill ratio feel' from zero to current position
2341 if(!has_colored_background && d->fill_feedback && is_sensitive)
2342 {
2343 set_color(cr, w->bauhaus->color_value);
2344
2345 const float position = d->pos * width;
2346 const float delta = position - origin;
2347
2348 // origin = 0 means start on left, but our left is shifted to the right,
2349 // so offset it. (see x_origin)
2350 if(origin == 0.f)
2351 cairo_rectangle(cr, x_origin, baseline_top, delta - x_origin, baseline_height);
2352 else
2353 cairo_rectangle(cr, origin, baseline_top, delta, baseline_height);
2354
2355 cairo_fill(cr);
2356 }
2357
2358 // draw the 0 reference graduation if it's different than the bounds of the slider
2359 // If the max of the slider is 360, it is likely an absolute hue slider in degrees
2360 // a zero in periodic stuff has not much meaning so we skip it.
2361 if(d->hard_max != 360.0f && is_sensitive)
2362 {
2363 set_color(cr, w->bauhaus->color_fg);
2364
2365 // If we are to put the 0 reference on the "left", this time we need to account for
2366 // actual leftmost resting position of the indicator, not the starting point of the baseline.
2367 cairo_arc(cr, origin, baseline_top + w->bauhaus->marker_size, w->bauhaus->border_width / 2., 0, 2 * M_PI);
2368 cairo_fill(cr);
2369 }
2370
2371 cairo_restore(cr);
2372}
2373
2375{
2376 switch(w->type)
2377 {
2379 break;
2380 case DT_BAUHAUS_SLIDER:
2381 {
2384 }
2385 break;
2386 default:
2387 break;
2388 }
2389}
2390
2392{
2393 GtkWidget *widget = GTK_WIDGET(w);
2394
2395 switch(w->type)
2396 {
2398 {
2400
2401 if(d->editable && w->bauhaus->keys_cnt > 0)
2402 {
2403 // combobox is editable and we have text, assume it is a custom input
2404 memset(d->text, 0, DT_BAUHAUS_COMBO_MAX_TEXT);
2405 g_strlcpy(d->text, w->bauhaus->keys, DT_BAUHAUS_COMBO_MAX_TEXT);
2406 _combobox_set(widget, -1); // select custom entry
2407
2408#if DEBUG
2409 fprintf(stdout, "combobox went the custom path\n");
2410#endif
2411 }
2412 else
2413 {
2414 if(w->bauhaus->keys_cnt > 0)
2415 {
2416 // combobox is not editable, but we have text. Assume user wanted to init a selection from keyboard.
2417 // find the closest match by looking for the entry having the maximum number
2418 // of characters in common with the user input.
2419 gchar *keys = g_utf8_casefold(w->bauhaus->keys, -1);
2420 int match = -1;
2421 int matches = 0;
2422
2423 for(int j = 0; j < d->entries->len; j++)
2424 {
2425 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, j);
2426 gchar *text_cmp = g_utf8_casefold(entry->label, -1);
2427 if(entry->sensitive && !strncmp(text_cmp, keys, w->bauhaus->keys_cnt))
2428 {
2429 matches++;
2430 match = j;
2431 }
2432 dt_free(text_cmp);
2433 }
2434 dt_free(keys);
2435
2436 // Accept result only if exactly one match was found. Anything else is ambiguous
2437 if(matches == 1)
2438 _combobox_set(widget, match);
2439 }
2440 else {
2441 // Active entry (below cursor or scrolled)
2442 _combobox_set(widget, d->hovered);
2443 }
2444 }
2445
2446 break;
2447 }
2448 case DT_BAUHAUS_SLIDER:
2449 {
2450 // The slider popup uses the quadratic magnifier for accurate setting.
2451 // We need extra conversions from cursor coordinates to set it right.
2452 // This needs to be kept in sync with popup_draw()
2454
2455 // This is needed to accept the change.
2456 // d->pos is soft-updated with corrected coordinates for drawing purposes only in popup_redraw().
2457 // We need to reset it to the original value temporarily, and request a proper setting
2458 // with value-changed signal through dt_bauhaus_slider_set_normalized()
2459 const float value = d->pos;
2460 d->pos = d->oldpos;
2462 break;
2463 }
2464 default:
2465 break;
2466 }
2467}
2468
2469static gchar *_build_label(const struct dt_bauhaus_widget_t *w)
2470{
2471 return g_strdup(w->label);
2472}
2473
2474static gboolean dt_bauhaus_popup_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
2475{
2476 // Popups belong to the app, not to the bauhaus widget. That's confusing.
2477 // Also the *widget param here is the popup container, still not the bauhaus widget. Even more confusing.
2478 // This is the actual parent bauhaus widget :
2479 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
2481
2482 // get area properties
2483 GtkAllocation allocation;
2484 gtk_widget_get_allocation(widget, &allocation);
2485 cairo_surface_t *cst = dt_cairo_surface_create_at_scale(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height, dt_widget_ppd());
2486 cairo_t *cr = cairo_create(cst);
2487 GtkStyleContext *context = gtk_widget_get_style_context(widget);
2488
2489 // look up some colors once
2490 GdkRGBA text_color, text_color_selected, text_color_hover, text_color_insensitive, text_color_focused;
2491 gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &text_color);
2492 gtk_style_context_get_color(context, GTK_STATE_FLAG_SELECTED, &text_color_selected);
2493 gtk_style_context_get_color(context, GTK_STATE_FLAG_PRELIGHT, &text_color_hover);
2494 gtk_style_context_get_color(context, GTK_STATE_FLAG_INSENSITIVE, &text_color_insensitive);
2495 gtk_style_context_get_color(context, GTK_STATE_FLAG_FOCUSED, &text_color_focused);
2496
2497 GdkRGBA *fg_color = default_color_assign();
2498 GdkRGBA *bg_color;
2499 GtkStateFlags state = gtk_widget_get_state_flags(widget);
2500
2501 gtk_style_context_get(context, state, "background-color", &bg_color, NULL);
2502 gtk_style_context_get_color(context, state, fg_color);
2503
2504 // draw background
2505 gtk_render_background(context, cr, 0, 0, allocation.width, allocation.height);
2506 gtk_render_frame(context, cr, 0, 0, allocation.width, allocation.height);
2507
2508 const double main_height = _widget_get_main_height(w, widget);
2509 double total_width = 0.f;
2510 const double main_width = _widget_get_main_width(w, NULL, &total_width);
2511
2512 // translate to account for the widget spacing
2513 cairo_translate(cr, w->padding->left, w->padding->top);
2514
2515 // switch on bauhaus widget type (so we only need one static window)
2516 switch(w->type)
2517 {
2518 case DT_BAUHAUS_SLIDER:
2519 {
2521 const double slider_cursor_radius = 0.5 * w->bauhaus->marker_size;
2522 const double text_width = main_width + slider_cursor_radius;
2523 cairo_save(cr);
2524 cairo_translate(cr, slider_cursor_radius, 0.0);
2525 set_color(cr, *fg_color);
2526
2527 float scale = _bh_slider_get_scale(w);
2528 const int num_scales = 1.f / scale;
2529 const float bottom_baseline = _get_slider_bar_height(w);
2530
2531 for(int k = 0; k < num_scales; k++)
2532 {
2533 const float off = k * scale - d->oldpos;
2534 GdkRGBA fg_copy = *fg_color;
2535 fg_copy.alpha = scale / fabsf(off);
2536 set_color(cr, fg_copy);
2537 draw_slider_line(cr, d->oldpos, off, scale, main_width, main_height, bottom_baseline, 1);
2538 cairo_stroke(cr);
2539 }
2540 cairo_restore(cr);
2541
2542 // Get the x offset compared to d->oldpos accounting for vertical position magnification
2543 const double mouse_off = d->pos - d->oldpos;
2544
2545 cairo_save(cr);
2546 cairo_translate(cr, slider_cursor_radius, 0.0);
2547
2548 // Draw the baseline with fill feedback if any (needs the new d->pos set before)
2549 dt_bauhaus_draw_baseline(w, cr, main_width);
2550
2551 // draw mouse over indicator line
2553 draw_slider_line(cr, d->oldpos, mouse_off, scale, main_width, main_height, bottom_baseline, 2);
2554 cairo_stroke(cr);
2555
2556 // draw indicator
2557 dt_bauhaus_draw_indicator(w, d->pos, cr, main_width);
2558
2559 cairo_restore(cr);
2560
2561 // draw numerical value:
2562 cairo_save(cr);
2564
2565 float value_width = 0.f;
2566 char *text = dt_bauhaus_slider_get_text(GTK_WIDGET(w), dt_bauhaus_slider_get(GTK_WIDGET(w)));
2567 GdkRectangle bounding_value = { .x = 0.,
2568 .y = 0.,
2569 .width = text_width,
2570 .height = w->bauhaus->line_height };
2571 // Display user keyboard input if any, otherwise the current value
2572 show_pango_text(w, context, cr, &bounding_value,
2573 (w->bauhaus->keys_cnt) ? w->bauhaus->keys : text, BH_ALIGN_RIGHT,
2574 BH_ALIGN_MIDDLE, PANGO_ELLIPSIZE_NONE, NULL, &value_width, NULL, GTK_STATE_FLAG_NORMAL);
2575 dt_free(text);
2576
2577 // label on top of marker:
2578 gchar *label_text = _build_label(w);
2579 const float label_width = text_width - value_width - INNER_PADDING;
2580 GdkRectangle bounding_label = { .x = 0.,
2581 .y = 0.,
2582 .width = label_width,
2583 .height = w->bauhaus->line_height };
2584 set_color(cr, *fg_color);
2585 show_pango_text(w, context, cr, &bounding_label, label_text, BH_ALIGN_LEFT, BH_ALIGN_MIDDLE,
2586 PANGO_ELLIPSIZE_END, NULL, NULL, NULL, GTK_STATE_FLAG_NORMAL);
2587 dt_free(label_text);
2588
2589 cairo_restore(cr);
2590 }
2591 break;
2593 {
2595
2596 // User keyboard input goes first
2597 double popup_y = 0.;
2598 if(w->bauhaus->keys_cnt > 0)
2599 {
2600 cairo_save(cr);
2601 set_color(cr, text_color_focused);
2602 const double query_height = _bh_get_row_height(w);
2603 GdkRectangle query_label = { .x = 0.,
2604 .y = popup_y,
2605 .width = main_width,
2606 .height = query_height };
2607 show_pango_text(w, context, cr, &query_label, w->bauhaus->keys, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE,
2608 PANGO_ELLIPSIZE_NONE, NULL, NULL, NULL, GTK_STATE_FLAG_NORMAL);
2609 popup_y += query_height;
2610 cairo_restore(cr);
2611 }
2612
2613 cairo_save(cr);
2614 gchar *keys = g_utf8_casefold(w->bauhaus->keys, -1);
2615 for(int j = 0; j < d->entries->len; j++)
2616 {
2617 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, j);
2618 const double entry_height = _bh_get_combobox_entry_height(w, entry);
2619 if(entry->is_separator)
2620 {
2621 if(w->bauhaus->keys_cnt == 0)
2622 {
2623 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.);
2624 cairo_move_to(cr, 0., popup_y + 0.5 * entry_height);
2625 cairo_line_to(cr, main_width, popup_y + 0.5 * entry_height);
2626 cairo_set_line_width(cr, 1.);
2627 cairo_stroke(cr);
2628 }
2629 popup_y += entry_height;
2630 continue;
2631 }
2632
2633 gchar *text_cmp = g_utf8_casefold(entry->label, -1);
2634 if(!strncmp(text_cmp, keys, w->bauhaus->keys_cnt))
2635 {
2636 // If user typed some keys, display matching entries only
2637
2638 // The GTK state flag is applied to the whole widget,
2639 // we need to dispatch it individually to each entry
2640 if(!entry->sensitive)
2641 {
2642 set_color(cr, text_color_insensitive);
2643 state = GTK_STATE_FLAG_INSENSITIVE;
2644 }
2645 else if(j == d->active)
2646 {
2647 set_color(cr, text_color_selected);
2648 state = GTK_STATE_FLAG_SELECTED;
2649 }
2650 else if(j == d->hovered)
2651 {
2652 set_color(cr, text_color_hover);
2653 state = GTK_STATE_FLAG_PRELIGHT;
2654 }
2655 else
2656 {
2657 set_color(cr, text_color);
2658 state = GTK_STATE_FLAG_NORMAL;
2659 }
2660
2661 GdkRectangle bounding_label = { .x = 0.,
2662 .y = popup_y,
2663 .width = main_width,
2664 .height = entry_height };
2665#if DEBUG
2666 cairo_rectangle(cr, bounding_label.x, bounding_label.y, bounding_label.width, bounding_label.height);
2667 cairo_set_line_width(cr, 2);
2668 cairo_stroke(cr);
2669#endif
2670 show_pango_text(w, context, cr, &bounding_label, entry->label, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE,
2671 d->entries_ellipsis, bg_color, NULL, NULL, state);
2672 }
2673
2674 dt_free(text_cmp);
2675 popup_y += entry_height;
2676 }
2677 cairo_restore(cr);
2678 dt_free(keys);
2679 }
2680 break;
2681 default:
2682 // yell
2683 break;
2684 }
2685
2686 cairo_destroy(cr);
2687 cairo_set_source_surface(crf, cst, 0, 0);
2688 cairo_paint(crf);
2689 cairo_surface_destroy(cst);
2690
2691 gdk_rgba_free(bg_color);
2692 gdk_rgba_free(fg_color);
2693
2694 return TRUE;
2695}
2696
2697
2698// Get the maximum width of a full combobox without ellipsization
2700{
2701 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2703 GtkStyleContext *context = gtk_widget_get_style_context(widget);
2704 const GtkStateFlags state = gtk_widget_get_state_flags(widget);
2705
2706 cairo_surface_t *cst = dt_cairo_surface_create_at_scale(CAIRO_FORMAT_ARGB32, 999, 999, dt_widget_ppd());
2707 cairo_t *cr = cairo_create(cst);
2708
2709 float width = 0.f;
2710
2711 // Get chevron width + padding if any
2712 if(w->show_quad)
2714
2715 float label_width = 0.f;
2716 GdkRectangle bounding_label = { .x = 0.,
2717 .y = 0.,
2718 .width = 999,
2719 .height = 999 };
2720
2721 show_pango_text(w, context, cr, &bounding_label, w->label, BH_ALIGN_LEFT, BH_ALIGN_MIDDLE,
2722 PANGO_ELLIPSIZE_NONE, NULL, &label_width, NULL, state);
2723
2724 if(label_width > 0.f) width += label_width + INNER_PADDING;
2725
2726 // Get width of the longest entry
2727 float max_entry = 0.f;
2728 for(int i = 0; i < d->entries->len; i++)
2729 {
2730 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
2731
2732 // The value is shown right-aligned, ellipsized if needed.
2733 GdkRectangle bounding_value = { .x = 0,
2734 .y = 0.,
2735 .width = 999,
2736 .height = 999 };
2737 float entry_label_width = 0.f;
2738
2739 show_pango_text(w, context, cr, &bounding_value, entry->label, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE, PANGO_ELLIPSIZE_NONE, NULL,
2740 &entry_label_width, NULL, state);
2741
2742 if(entry_label_width + INNER_PADDING > max_entry) max_entry = entry_label_width + INNER_PADDING;
2743 }
2744
2745 width += max_entry;
2746 width += w->margin->left + w->margin->right + w->padding->left + w->padding->right;
2747
2748 cairo_destroy(cr);
2749 cairo_surface_destroy(cst);
2750
2751 return width;
2752}
2753
2754static gboolean _widget_draw(GtkWidget *widget, cairo_t *crf)
2755{
2756 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2757 GtkStyleContext *context = gtk_widget_get_style_context(widget);
2758
2759 // Get current Gtk allocation
2760 GtkAllocation allocation;
2761 gtk_widget_get_allocation(widget, &allocation);
2762
2763 if(w->type == DT_BAUHAUS_COMBOBOX)
2764 allocation.height = _get_combobox_height(widget);
2765 else if(w->type == DT_BAUHAUS_SLIDER)
2766 allocation.height = _get_slider_height(widget);
2767
2768 if(w->type == DT_BAUHAUS_COMBOBOX && !w->expand)
2769 {
2770 // For comboboxes that are not set to hexpand, limit the width span to what's needed
2771 // to display the internal text, aka prevent them to grow out of proportions
2772 float max_width = _get_combobox_max_width(widget);
2773 if(max_width < allocation.width)
2774 allocation.width = ceilf(max_width);
2775 }
2776
2777 // Force allocate to our requirements. Yes, it's ugly.
2778 gtk_widget_size_allocate(widget, &allocation);
2779
2780 cairo_surface_t *cst = dt_cairo_surface_create_at_scale(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height, dt_widget_ppd());
2781 cairo_t *cr = cairo_create(cst);
2782
2783 GdkRGBA *text_color = default_color_assign();
2784 GdkRGBA *value_color = default_color_assign();
2785 GdkRGBA *value_text_color = default_color_assign();
2786 GtkStateFlags state = gtk_widget_get_state_flags(widget);
2787 if(gtk_widget_has_focus(widget))
2788 state |= GTK_STATE_FLAG_FOCUSED;
2789
2790 gtk_style_context_save(context);
2791 gtk_style_context_set_state(context, state);
2792 gtk_style_context_get_color(context, state, text_color);
2793
2794 *value_color = gtk_widget_is_sensitive(widget) ? w->bauhaus->color_value : w->bauhaus->color_value_insensitive;
2795 *value_text_color = gtk_widget_is_sensitive(widget) ? w->bauhaus->color_value_text : w->bauhaus->color_value_text_insensitive;
2796
2797 // Compute internal widget sizes, that is allocation minus margins
2799 // Note : box is the border box in CSS conventions
2800 const float box_width = allocation.width - w->margin->left - w->margin->right;
2801 const float box_height = allocation.height - w->margin->top - w->margin->bottom;
2802
2803 // Paint background and borders first
2804 gtk_render_background(context, crf, w->margin->left, w->margin->top, box_width, box_height);
2805 gtk_render_frame(context, crf, w->margin->left, w->margin->top, box_width, box_height);
2806
2807 // Translate Cairo coordinates to account for the widget spacing
2808 cairo_translate(cr,
2809 w->margin->left + w->padding->left,
2810 w->margin->top + w->padding->top);
2811
2812 const float available_width = _widget_get_main_width(w, NULL, NULL);
2813 const float inner_height = _widget_get_main_height(w, NULL);
2814
2815 // draw type specific content:
2816 cairo_save(cr);
2817 set_color(cr, *text_color);
2818 cairo_set_line_width(cr, 1.0);
2819 switch(w->type)
2820 {
2822 {
2823 // draw label and quad area at right end
2824 // Gap must match the one reserved by _widget_get_main_width() (2*INNER_PADDING)
2825 // and used by the slider, so combobox quads stay flush and vertically aligned
2826 // with slider quad icons when stacked.
2827 if(w->show_quad)
2828 dt_bauhaus_draw_quad(w, cr, available_width + 2 * INNER_PADDING, 0.);
2829
2831 const PangoEllipsizeMode combo_ellipsis = d->entries_ellipsis;
2832
2833 float label_width = 0.f;
2834 float label_height = 0.f;
2835
2836 GdkRectangle bounding_label = { .x = 0.,
2837 .y = 0.,
2838 .width = available_width,
2839 .height = inner_height };
2840 set_color(cr, *text_color);
2841 show_pango_text(w, context, cr, &bounding_label, w->label, BH_ALIGN_LEFT, BH_ALIGN_MIDDLE,
2842 combo_ellipsis, NULL, &label_width, &label_height, state);
2843
2844 // The value is shown right-aligned, ellipsized if needed.
2845 gchar *text = d->text;
2846 if(d->active >= 0 && d->active < d->entries->len)
2847 {
2848 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
2849 text = entry->label;
2850 }
2851 GdkRectangle bounding_value = { .x = label_width + INNER_PADDING,
2852 .y = 0.,
2853 .width = available_width - label_width - INNER_PADDING,
2854 .height = inner_height };
2855 set_color(cr, *value_text_color);
2856 show_pango_text(w, context, cr, &bounding_value, text, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE, combo_ellipsis, NULL,
2857 NULL, NULL, state);
2858
2859 break;
2860 }
2861 case DT_BAUHAUS_SLIDER:
2862 {
2863 const double slider_cursor_radius = 0.5 * w->bauhaus->marker_size;
2864 const double text_width = available_width + slider_cursor_radius;
2865
2866 // line for orientation
2867 cairo_save(cr);
2868 cairo_translate(cr, slider_cursor_radius, 0.0);
2869 dt_bauhaus_draw_baseline(w, cr, available_width);
2870 cairo_restore(cr);
2871
2872 // Paint the non-active quad icon with some transparency, because
2873 // icons are bolder than the neighbouring text and appear brighter.
2874 cairo_save(cr);
2875 if(!(w->quad_paint_flags & CPF_ACTIVE))
2876 cairo_set_source_rgba(cr, text_color->red, text_color->green, text_color->blue, text_color->alpha * 0.7);
2877
2878 dt_bauhaus_draw_quad(w, cr, text_width + 2. * INNER_PADDING, 0.);
2879 cairo_restore(cr);
2880
2881 float value_width = 0;
2882 if(gtk_widget_is_sensitive(widget))
2883 {
2884 cairo_save(cr);
2885 cairo_translate(cr, slider_cursor_radius, 0.0);
2886 dt_bauhaus_draw_indicator(w, w->data.slider.pos, cr, available_width);
2887 cairo_restore(cr);
2888
2889 char *text = dt_bauhaus_slider_get_text(widget, dt_bauhaus_slider_get(widget));
2890 GdkRectangle bounding_value = { .x = 0.,
2891 .y = 0.,
2892 .width = text_width,
2893 .height = w->bauhaus->line_height };
2894 set_color(cr, *value_text_color);
2895 show_pango_text(w, context, cr, &bounding_value, text, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE,
2896 PANGO_ELLIPSIZE_NONE, NULL, &value_width, NULL, state);
2897 dt_free(text);
2898 }
2899
2900 // label on top of marker:
2901 gchar *label_text = _build_label(w);
2902 const float label_width = text_width - value_width - INNER_PADDING;
2903 GdkRectangle bounding_label = { .x = 0.,
2904 .y = 0.,
2905 .width = label_width,
2906 .height = w->bauhaus->line_height };
2907 set_color(cr, *text_color);
2908 show_pango_text(w, context, cr, &bounding_label, label_text, BH_ALIGN_LEFT, BH_ALIGN_MIDDLE,
2909 PANGO_ELLIPSIZE_END, NULL, NULL, NULL, state);
2910 dt_free(label_text);
2911 }
2912 break;
2913 default:
2914 break;
2915 }
2916 cairo_restore(cr);
2917 cairo_destroy(cr);
2918 cairo_set_source_surface(crf, cst, 0, 0);
2919 cairo_paint(crf);
2920 cairo_surface_destroy(cst);
2921 gtk_style_context_restore(context);
2922
2923 gdk_rgba_free(text_color);
2924 gdk_rgba_free(value_color);
2925 gdk_rgba_free(value_text_color);
2926
2927 return TRUE;
2928}
2929
2930static void _get_preferred_width(GtkWidget *widget, gint *minimum_size, gint *natural_size)
2931{
2932 // Nothing clever here : preferred size is the size of the container.
2933 // If user is not happy with that, it's his responsibility to resize sidebars.
2934 // Minimum size is left at 0 so the widget can shrink with its parent;
2935 // leaving it unset would read as uninitialized stack garbage and could
2936 // clamp natural_size up to a stale value, preventing the widget from
2937 // growing again when the parent grows.
2938 *minimum_size = 0;
2939
2940 // The host knows which panel this sits in and how wide it is; -1 means it has no opinion.
2941 const gint host_width = dt_widget_natural_width(widget);
2942 *natural_size = (host_width >= 0) ? host_width : DT_PIXEL_APPLY_DPI(300);
2943}
2944
2945static void _get_preferred_height(GtkWidget *widget, gint *minimum_size, gint *natural_size)
2946{
2947 // Resolve the height from the same getters used everywhere else so it is
2948 // always correct at measure time, regardless of when "style-updated" fires.
2949 // Relying solely on the size-request set in _style_updated is not enough:
2950 // when the widget lives inside a freshly popped-up GtkMenu, the menu measures
2951 // its children before their style context is resolved, so it would size
2952 // itself from a stale/zero height and end up showing scroll arrows.
2953 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
2955 const gint height = (w->type == DT_BAUHAUS_COMBOBOX) ? (gint)ceil(_get_combobox_height(widget))
2956 : (gint)ceil(_get_slider_height(widget));
2957 *minimum_size = height;
2958 *natural_size = height;
2959}
2960
2962{
2963 if(bh->current)
2964 {
2965 gtk_grab_remove(bh->popup_area);
2966 gtk_widget_hide(bh->popup_window);
2967 gtk_window_set_attached_to(GTK_WINDOW(bh->popup_window), NULL);
2968
2969 // Give back focus to the attached widget
2970 gtk_widget_grab_focus(GTK_WIDGET(bh->current));
2971 dt_widget_set_scroll_focus(GTK_WIDGET(bh->current));
2972
2973 bh->current = NULL;
2974 }
2975}
2976
2978{
2979 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2981
2982 // Re-parent onto the window this widget actually lives in, which is not necessarily the
2983 // application's main window: modules shown in a modal (the export dialog) have their own
2984 // toplevel. The popup's position is computed relative to that same window below, so the two
2985 // must agree or the popup lands outside its parent.
2986 GtkWidget *parent_toplevel = gtk_widget_get_toplevel(widget);
2987 if(parent_toplevel && gtk_widget_is_toplevel(parent_toplevel))
2988 gtk_window_set_transient_for(GTK_WINDOW(w->bauhaus->popup_window), GTK_WINDOW(parent_toplevel));
2989 w->bauhaus->current = w;
2990 w->bauhaus->keys_cnt = 0;
2991 memset(w->bauhaus->keys, 0, sizeof(w->bauhaus->keys));
2992 w->bauhaus->change_active = 0;
2993 w->bauhaus->mouse_line_distance = 0.0f;
2994 w->bauhaus->hiding = FALSE;
2995
2996 // Make sure all relevant widgets exist
2997 gtk_widget_realize(w->bauhaus->popup_window);
2998 gtk_widget_realize(widget);
3000
3001 GtkAllocation tmp;
3002 gtk_widget_get_allocation(widget, &tmp);
3003 int width = MAX(tmp.width, 1);
3004 int height = MAX(tmp.height, 1);
3005
3006 switch(w->bauhaus->current->type)
3007 {
3008 case DT_BAUHAUS_SLIDER:
3009 {
3010 // Slider popup: make it square
3012 d->oldpos = d->pos;
3013 d->is_dragging = FALSE;
3014 height = tmp.width;
3015 break;
3016 }
3018 {
3020 break;
3021 }
3022 default:
3023 {
3024 fprintf(stderr, "[dt_bauhaus_show_popup] The bauhaus widget has an unknown type\n");
3025 break;
3026 }
3027 }
3028
3029 /* Bind to CSS rules from parent widget */
3030 GtkStyleContext *context = gtk_widget_get_style_context(w->bauhaus->popup_area);
3031 gtk_style_context_add_class(context, "dt_bauhaus_popup");
3032 gtk_window_set_attached_to(GTK_WINDOW(w->bauhaus->popup_window), widget);
3033 #ifdef GDK_WINDOWING_WAYLAND
3034 if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) {
3035 GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
3036 gtk_window_set_transient_for(GTK_WINDOW(w->bauhaus->popup_window), GTK_WINDOW(toplevel));
3037 }
3038 #endif
3039
3040 // The popup window stays transient for the main application window, so the anchor
3041 // rectangle needs to remain expressed in that coordinate space even when the popup is
3042 // logically attached to an enclosing popover on Wayland.
3043 gint wx = 0, wy = 0;
3044 GdkWindow *widget_window = gtk_widget_get_window(widget);
3045 if(widget_window) gdk_window_get_origin(widget_window, &wx, &wy);
3046 wx += w->margin->left;
3047 wy += w->margin->top;
3048
3049 gint wwx = 0, wwy = 0;
3050 // The widget's OWN toplevel, not the host's main window: correct for popups raised from a
3051 // secondary window, and it removes the last reason to ask the host which window is primary.
3052 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(w));
3053 GdkWindow *main_window = (toplevel && gtk_widget_is_toplevel(toplevel))
3054 ? gtk_widget_get_window(toplevel) : NULL;
3055 if(main_window) gdk_window_get_origin(main_window, &wwx, &wwy);
3056
3057 GdkRectangle anchor = {
3058 .x = wx - wwx,
3059 .y = wy - wwy,
3060 .width = MAX(tmp.width, 1),
3061 .height = MAX(tmp.height, 1)
3062 };
3063
3064 // Set desired size, but it's more a guide than a rule.
3065 gtk_widget_set_size_request(w->bauhaus->popup_area, width, height);
3066 gtk_widget_set_size_request(w->bauhaus->popup_window, width, height);
3067
3068 // Need to call resize to actually change something
3069 gtk_window_resize(GTK_WINDOW(w->bauhaus->popup_window), width, height);
3070
3071 GdkWindow *window = gtk_widget_get_window(w->bauhaus->popup_window);
3072 if(IS_NULL_PTR(window))
3073 {
3074 gtk_widget_show_all(w->bauhaus->popup_window);
3075 gtk_widget_grab_focus(w->bauhaus->popup_area);
3076 return;
3077 }
3078
3079 // For Wayland (and supposed to work on X11 too) and Gtk 3.24 this is how you do it
3080 gdk_window_move_to_rect(GDK_WINDOW(window), &anchor, GDK_GRAVITY_STATIC, GDK_GRAVITY_STATIC,
3081 GDK_ANCHOR_SLIDE, 0, 0);
3082
3083 gtk_widget_show_all(w->bauhaus->popup_window);
3084 gtk_widget_grab_focus(w->bauhaus->popup_area);
3085}
3086
3087static void _slider_add_step(GtkWidget *widget, float delta, guint state)
3088{
3089 if(delta == 0.f) return;
3090 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3092
3095 else if(dt_modifier_is(state, GDK_SHIFT_MASK)) delta *= 5.f;
3096
3097 // Ensure the requested delta is at least visible given current number of digits in display
3098 const float min_visible = 1.f / (fabsf(d->factor) * ipow(10, d->digits));
3099 if(fabsf(delta) < min_visible)
3100 delta = copysignf(min_visible, delta);
3101
3102 const float value = dt_bauhaus_slider_get(widget);
3104}
3105
3106static gboolean _widget_scroll(GtkWidget *widget, GdkEventScroll *event)
3107{
3108 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3109 const gboolean popup_captured = (w->bauhaus->current == w);
3110 const gboolean scroll_captured = (dt_widget_scroll_focus() == widget);
3111 const gboolean key_captured = gtk_widget_has_focus(widget);
3112 const gboolean smooth = (event->direction == GDK_SCROLL_SMOOTH);
3113
3114 // - native Gtk focus (keyboard), that takes precedence and records all keyboard events,
3115 // - custom scroll focus (mouse wheel), that should not overlap with vertical scrolling.
3116 // Scroll focus is a subset of Gtk focus, aka we can lose scroll focus while we have Gtk focus,
3117 // but delayed history commits may transiently steal Gtk focus while the
3118 // pointer never leaves the widget. In that case, keep trusting the explicit
3119 // scroll focus until leave-notify releases it.
3120 // We also extend widget focus with the popup window focus if it is captured
3121 // by the current widget.
3122 if(!key_captured && !popup_captured && !scroll_captured)
3123 return FALSE;
3124
3125 // Keep ownership of the whole touchpad gesture sequence.
3127
3128 int delta_x = 0;
3129 int delta_y = 0;
3130
3131 if(!dt_gui_get_scroll_unit_deltas(event, &delta_x, &delta_y))
3132 {
3133 // Important: for touchpad scroll, sub-unit deltas and stop events must not
3134 // propagate to the parent scrolled window, otherwise the same gesture gets
3135 // re-accumulated there.
3136 return smooth;
3137 }
3138
3139 const gboolean vscroll = delta_y != 0 && abs(delta_y) > abs(delta_x);
3140 const gboolean hscroll = delta_x != 0 && abs(delta_x) > abs(delta_y);
3141
3142 if(w->type == DT_BAUHAUS_SLIDER)
3143 {
3144 if(hscroll)
3145 {
3146 // inconditionnaly record horizontal scroll on slider
3147 _slider_add_step(widget, delta_x, event->state);
3148 return TRUE;
3149 }
3150 else if(vscroll && dt_widget_scroll_focus())
3151 {
3152 // convert vertical scrolling to horizontal only if we have the scroll focus
3153 _slider_add_step(widget, -delta_y, event->state);
3154 return TRUE;
3155 }
3156
3157 // Diagonal smooth gestures: consume them if captured, so they do not leak.
3158 return smooth;
3159 }
3160 else
3161 {
3162 if(vscroll && dt_widget_scroll_focus())
3163 {
3164 _combobox_next_sensitive(w, delta_y);
3165 return TRUE;
3166 }
3167
3168 return smooth;
3169 }
3170}
3171
3172static gboolean _widget_key_press(GtkWidget *widget, GdkEventKey *event)
3173{
3174 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3175 guint key = dt_keys_mainpad_alternatives(event->keyval);
3176
3177 if(w->type == DT_BAUHAUS_SLIDER)
3178 {
3179 switch(key)
3180 {
3181 case GDK_KEY_Right:
3182 _slider_add_step(widget, 1, event->state);
3183 return TRUE;
3184
3185 case GDK_KEY_Left:
3186 _slider_add_step(widget, -1, event->state);
3187 return TRUE;
3188
3189 case GDK_KEY_Insert:
3190 if(w->quad_toggle)
3191 {
3194 return TRUE;
3195 }
3196
3197 default:
3198 return FALSE;
3199 }
3200 }
3201 else if(w->type == DT_BAUHAUS_COMBOBOX)
3202 {
3203 switch(key)
3204 {
3205 case GDK_KEY_Return:
3206 dt_bauhaus_show_popup(widget);
3207 return TRUE;
3208
3209 case GDK_KEY_Insert:
3210 if(w->quad_toggle)
3211 {
3214 return TRUE;
3215 }
3216
3217 default:
3218 return FALSE;
3219 }
3220 }
3221 return FALSE;
3222}
3223
3224static gboolean dt_bauhaus_combobox_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3225{
3226 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3227
3228 double event_x = event->x;
3229 double event_y = event->y;
3230 double width;
3231 _bh_active_region_t activated = _bh_get_active_region(widget, &event_x, &event_y, &width, NULL);
3232
3234
3235 if(activated == BH_REGION_OUT)
3236 {
3238 return FALSE;
3239 }
3240
3241 gtk_widget_grab_focus(widget);
3243
3244 if(activated == BH_REGION_QUAD && w->quad_toggle)
3245 {
3247 return TRUE;
3248 }
3249 else
3250 {
3251 // If no quad toggle, treat the whole widget as unit pack.
3252 if(event->button == 3)
3253 {
3254 w->bauhaus->mouse_x = event_x;
3255 w->bauhaus->mouse_y = event_y;
3256 dt_bauhaus_show_popup(widget);
3257 return TRUE;
3258 }
3259 else if(event->button == 1)
3260 {
3261 // reset to default.
3262 if(event->type == GDK_2BUTTON_PRESS)
3263 {
3264 // never called, as we popup the other window under your cursor before.
3265 // (except in weird corner cases where the popup is under the -1st entry
3266 dt_bauhaus_combobox_set(widget, d->defpos);
3268 }
3269 else
3270 {
3271 // single click, show options
3272 w->bauhaus->opentime = event->time;
3273 w->bauhaus->mouse_x = event_x;
3274 w->bauhaus->mouse_y = event_y;
3275 dt_bauhaus_show_popup(widget);
3276 }
3277 return TRUE;
3278 }
3279 }
3280 return FALSE;
3281}
3282
3284{
3285 // first cast to bh widget, to check that type:
3286 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3288 return d->min + d->pos * (d->max - d->min);
3289}
3290
3292{
3293 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(widget)->data.slider;
3294 return dt_bauhaus_slider_get(widget) * d->factor + d->offset;
3295}
3296
3298{
3299 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(w)->data.slider;
3300 if((d->hard_max * d->factor + d->offset)*(d->hard_min * d->factor + d->offset) < 0)
3301 return g_strdup_printf("%+.*f%s", d->digits, val * d->factor + d->offset, d->format);
3302 else
3303 return g_strdup_printf( "%.*f%s", d->digits, val * d->factor + d->offset, d->format);
3304}
3305
3306static void _dt_bauhaus_slider_set_with_raise(GtkWidget *widget, float pos, gboolean raise)
3307{
3308 // translate by bounds and call set_normalized.
3309 // raise == FALSE re-applies the value (re-clamped to the current bounds) and
3310 // redraws without committing to history or emitting "value-changed". This is
3311 // what bound changes (soft min/max) need: re-clamping the current value to a
3312 // new display range is not a user edit, and emitting would re-enter the
3313 // module's gui_changed handler -- which during gui_init runs before all
3314 // widgets exist (NULL deref), and at runtime would spuriously commit.
3315 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3317 const float rpos = CLAMP(pos, d->hard_min, d->hard_max);
3318
3319 // Restore soft min/max if we are in its range
3320 float rrpos = (rpos - d->soft_min) / (d->soft_max - d->soft_min);
3321 if(rrpos > 0.f)
3322 d->min = d->soft_min;
3323 else
3324 d->min = rpos;
3325
3326 if(rrpos < 1.f)
3327 d->max = d->soft_max;
3328 else
3329 d->max = rpos;
3330
3331 dt_bauhaus_slider_set_normalized(w, (rpos - d->min) / (d->max - d->min), raise);
3332}
3333
3334void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
3335{
3336 // this is the public interface function, it commits the value and emits "value-changed"
3338}
3339
3340void dt_bauhaus_slider_set_val(GtkWidget *widget, float val)
3341{
3342 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(widget)->data.slider;
3343 dt_bauhaus_slider_set(widget, (val - d->offset) / d->factor);
3344}
3345
3347{
3348 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3350 d->digits = val;
3351}
3352
3354{
3355 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3357 return d->digits;
3358}
3359
3361{
3362 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3364 d->step = val;
3365}
3366
3368{
3369 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3371 float step = d->step;
3372
3373 if(step == 0.f)
3374 {
3375 const float min = d->soft_min;
3376 const float max = d->soft_max;
3377
3378 // Derive the automatic keyboard/wheel step from the internal slider domain.
3379 // Display formatting may rescale that domain (for example "%" multiplies by 100),
3380 // but interaction needs to stay in the unformatted value space to avoid jumping
3381 // across the whole range in a single step for small-percent sliders.
3382 const float top = fminf(max-min, fmaxf(fabsf(min), fabsf(max)));
3383 if(top >= 100.f)
3384 step = 1.f;
3385 else
3386 step = top / 100.f;
3387 }
3388
3389 return copysignf(step, d->factor);
3390}
3391
3393{
3394 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3396 d->fill_feedback = feedback;
3397 gtk_widget_queue_draw(widget);
3398}
3399
3401{
3402 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3404 d->min = d->soft_min;
3405 d->max = d->soft_max;
3406 dt_bauhaus_slider_set(widget, d->defpos);
3407 return;
3408}
3409
3410void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
3411{
3412 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3414 d->format = g_intern_string(format);
3415
3416 if(strstr(format,"%") && fabsf(d->hard_max) <= 10)
3417 {
3418 if(d->factor == 1.0f) d->factor = 100;
3419 // Scaling by 100 costs two decimals, but a slider declared with fewer than two must not end
3420 // up with a negative digit count : it is a decimal exponent, fed to ipow() on every value
3421 // change and every redraw, and there is no such thing as fewer than zero decimals.
3422 d->digits = MAX(d->digits - 2, 0);
3423 }
3424}
3425
3427{
3428 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3430 d->factor = factor;
3431}
3432
3433void dt_bauhaus_slider_set_offset(GtkWidget *widget, float offset)
3434{
3435 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3437 d->offset = offset;
3438}
3439
3441{
3442 // Announce the new value. Whoever listens decides what it costs: the history commit
3443 // batches the pipeline recompute it triggers, so emitting on every scroll step is fine
3444 // and this widget has no reason to hold anything back.
3445
3446 // Don't commit anything while a reset is in progress (reloading history, applying a
3447 // style, ...): those set widget values programmatically and must not reach history.
3448 if(dt_gui_widgets_suppressed()) return;
3449
3451 {
3453 w->bauhaus->default_value_changed_callback(GTK_WIDGET(w));
3454 else
3455 fprintf(stderr, "ERROR: %s - %s is set to use default callback but none is provided\n", w->module->name,
3456 w->label);
3457 }
3458 else
3459 {
3460 if(w->module)
3461 fprintf(stderr, "WARNING: %s - %s has an IOP module but doesn't use default callback\n", w->module->name,
3462 w->label);
3463 }
3464
3465 // We need te emit this signal inconditionnaly
3466 g_signal_emit_by_name(G_OBJECT(w), "value-changed");
3467}
3468
3478static void dt_bauhaus_slider_set_normalized(struct dt_bauhaus_widget_t *w, float pos, gboolean raise)
3479{
3481 const float old_pos = d->pos;
3482 const float new_pos = CLAMP(pos, 0.0f, 1.0f);
3483
3484 if(old_pos != new_pos || raise)
3485 {
3486 const float new_value = new_pos * (d->max - d->min) + d->min;
3487 const float precision = (float)ipow(10, d->digits) * d->factor;
3488 const float rounded_value = roundf(new_value * precision) / precision;
3489 d->pos = (rounded_value - d->min) / (d->max - d->min);
3490
3491 if(w->bauhaus->current == w)
3492 gtk_widget_queue_draw(w->bauhaus->popup_area);
3493
3494 gtk_widget_queue_draw(GTK_WIDGET(w));
3495
3496 if(raise) _commit_slider_value(w);
3497 }
3498}
3499
3500static gboolean dt_bauhaus_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
3501{
3502 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
3504
3505 guint key = dt_keys_mainpad_alternatives(event->keyval);
3506
3507 switch(w->type)
3508 {
3509 case DT_BAUHAUS_SLIDER:
3510 {
3511 if(bh->keys_cnt + 2 < 64
3512 && (key == GDK_KEY_space || // SPACE
3513 key == GDK_KEY_percent || // %
3514 (event->string[0] >= 40 && event->string[0] <= 57) || // ()+-*/.,0-9
3515 key == GDK_KEY_asciicircum || key == GDK_KEY_dead_circumflex || // ^
3516 key == GDK_KEY_X || key == GDK_KEY_x)) // Xx
3517 {
3518 if(key == GDK_KEY_dead_circumflex)
3519 bh->keys[bh->keys_cnt++] = '^';
3520 else
3521 bh->keys[bh->keys_cnt++] = event->string[0];
3522 gtk_widget_queue_draw(bh->popup_area);
3523 }
3524 else if(bh->keys_cnt > 0
3525 && (key == GDK_KEY_BackSpace || key == GDK_KEY_Delete))
3526 {
3527 bh->keys[--bh->keys_cnt] = 0;
3528 gtk_widget_queue_draw(bh->popup_area);
3529 }
3530 else if(bh->keys_cnt > 0 && bh->keys_cnt + 1 < 64
3531 && (key == GDK_KEY_Return))
3532 {
3533 // accept input
3534 bh->keys[bh->keys_cnt] = 0;
3535 // unnormalized input, user was typing this:
3536 const float old_value = dt_bauhaus_slider_get_val(GTK_WIDGET(w));
3537 const float new_value = dt_calculator_solve(old_value, bh->keys);
3538 if(isfinite(new_value)) dt_bauhaus_slider_set_val(GTK_WIDGET(w), new_value);
3539 bh->keys_cnt = 0;
3540 memset(bh->keys, 0, sizeof(bh->keys));
3542 }
3543 else if(key == GDK_KEY_Escape)
3544 {
3545 // discard input ands close popup
3546 bh->keys_cnt = 0;
3547 memset(bh->keys, 0, sizeof(bh->keys));
3549 }
3550 else
3551 return FALSE;
3552
3553 return TRUE;
3554 }
3556 {
3557 if(!g_utf8_validate(event->string, -1, NULL)) return FALSE;
3558 const gunichar c = g_utf8_get_char(event->string);
3559 const long int char_width = g_utf8_next_char(event->string) - event->string;
3560 if(bh->keys_cnt + 1 + char_width < 64 && g_unichar_isprint(c))
3561 {
3562 // only accept key input if still valid or editable?
3563 g_utf8_strncpy(bh->keys + bh->keys_cnt, event->string, 1);
3564 bh->keys_cnt += char_width;
3565 gtk_widget_queue_draw(bh->popup_area);
3566 }
3567 else if(bh->keys_cnt > 0
3568 && (key == GDK_KEY_BackSpace || key == GDK_KEY_Delete))
3569 {
3570 bh->keys_cnt
3571 -= (bh->keys + bh->keys_cnt)
3572 - g_utf8_prev_char(bh->keys + bh->keys_cnt);
3573 bh->keys[bh->keys_cnt] = 0;
3574 gtk_widget_queue_draw(bh->popup_area);
3575 }
3576 else if(bh->keys_cnt > 0 && bh->keys_cnt + 1 < 64
3577 && (key == GDK_KEY_Return))
3578 {
3579 // accept unique matches only for editable:
3580 if(w->data.combobox.editable)
3581 bh->end_mouse_y = FLT_MAX;
3582 else
3583 bh->end_mouse_y = 0;
3584 bh->keys[bh->keys_cnt] = 0;
3586 bh->keys_cnt = 0;
3587 memset(bh->keys, 0, sizeof(bh->keys));
3589 }
3590 else if(key == GDK_KEY_Escape)
3591 {
3592 // discard input and close popup
3593 bh->keys_cnt = 0;
3594 memset(bh->keys, 0, sizeof(bh->keys));
3596 }
3597 else if(key == GDK_KEY_Up)
3598 {
3600 }
3601 else if(key == GDK_KEY_Down)
3602 {
3604 }
3605 else if(key == GDK_KEY_Return)
3606 {
3607 // return pressed, but didn't type anything
3608 bh->end_mouse_y = -1; // negative will use currently highlighted instead.
3609 bh->keys[bh->keys_cnt] = 0;
3610 bh->keys_cnt = 0;
3611 memset(bh->keys, 0, sizeof(bh->keys));
3614 }
3615 else
3616 return FALSE;
3617 return TRUE;
3618 }
3619 default:
3620 return FALSE;
3621 }
3622}
3623
3624static gboolean dt_bauhaus_slider_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3625{
3626 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3628
3629 double event_x = event->x;
3630 double event_y = event->y;
3631 double main_width = 0.;
3632 _bh_active_region_t activated = _bh_get_active_region(widget, &event_x, &event_y, &main_width, NULL);
3633 w->bauhaus->mouse_x = event_x;
3634 w->bauhaus->mouse_y = event_y;
3635
3636 if(activated == BH_REGION_OUT)
3637 {
3639 return FALSE;
3640 }
3641
3642 gtk_widget_grab_focus(widget);
3644
3645 if(activated == BH_REGION_QUAD && w->quad_toggle)
3646 {
3648 return TRUE;
3649 }
3650 else if(activated == BH_REGION_MAIN)
3651 {
3652 if(event->button == 1)
3653 {
3654 if(event->type == GDK_2BUTTON_PRESS)
3655 {
3656 // double left click on the main region : reset value to default
3658 d->is_dragging = 0;
3659 }
3660 else
3661 {
3662 // single left click on main region : redraw the slider immediately
3663 // but without committing results to pipeline yet.
3664 if(event_y < w->bauhaus->line_height)
3665 {
3666 // single left click on the header name : do nothing (only give focus)
3667 d->is_dragging = 0;
3668 }
3669 else
3670 {
3671 // single left click on slider bar : set new value
3672 d->is_dragging = 1;
3673 dt_bauhaus_slider_set_normalized(w, event_x / main_width, FALSE);
3674 }
3675 }
3676 }
3677 else if(event->button == 3)
3678 {
3679 // right click : show accurate slider setting popup
3680 d->oldpos = d->pos;
3681 dt_bauhaus_show_popup(widget);
3682 }
3683 else if(event->button == 2)
3684 {
3685 // middle click : reset zoom range to soft min/max
3686 _slider_zoom_range(w, 0);
3687 }
3688 return TRUE;
3689 }
3690 return FALSE;
3691}
3692
3693static gboolean dt_bauhaus_slider_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3694{
3695 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3697
3699
3700 // is_dragging is set TRUE no matter what on button_press, except if it's a double click.
3701 // double click is the only event handled in button_press, otherwise we assume every event
3702 // is drag and drop, and handle the final drag coordinate here.
3703 if(d->is_dragging)
3704 {
3705 d->is_dragging = 0;
3706
3707 if(event->button == 1)
3708 {
3710 return TRUE;
3711 }
3712 }
3713 return FALSE;
3714}
3715
3716static gboolean dt_bauhaus_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
3717{
3718 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3720 _bh_active_region_t activated = BH_REGION_OUT; // = 0
3721
3722 if(d->is_dragging && event->state & GDK_BUTTON1_MASK)
3723 {
3724 double event_x = event->x;
3725 double event_y = event->y;
3726 double main_width;
3727 activated = _bh_get_active_region(widget, &event_x, &event_y, &main_width, NULL);
3728
3729 w->bauhaus->mouse_x = event_x;
3730 w->bauhaus->mouse_y = event_y;
3731 dt_bauhaus_slider_set_normalized(w, event_x / main_width, TRUE);
3732 }
3733
3734 return activated;
3735}
3736
3738{
3739 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3740 w->no_accels = TRUE;
3741}
3742
3744{
3745 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3746 w->no_module_list = TRUE;
3747}
3748
3750{
3751 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3753}
3754
3755
3756// clang-format off
3757// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3758// vim: shiftwidth=2 expandtab tabstop=2 cindent
3759// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3760// clang-format on
dt_accels_t * dt_accels_get_global(void)
void dt_accels_new_virtual_shortcut(dt_accels_t *accels, GtkAccelGroup *accel_group, const gchar *accel_path, GtkWidget *widget, guint key_val, GdkModifierType accel_mods)
Add a new virtual shortcut. Virtual shortcuts are immutable, read-only and don't trigger any action....
gchar * dt_accels_build_path(const gchar *scope, const gchar *feature)
Build the accel path of feature under scope.
void dt_accels_remove_accel(dt_accels_t *accels, const char *path, gpointer data)
Recursively remove all accels for all shortcuts containing path. This is unneeded for accels attached...
Handle default and user-set shortcuts (accelerators)
#define dt_accels_new_darkroom_action(a, b, w, c, d, e, f, g)
#define DT_PRIMARY_MASK
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int position()
#define m
Definition basecurve.c:283
GtkWidget * dt_bauhaus_slider_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1630
static void dt_bauhaus_widget_accept(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2391
static gboolean dt_bauhaus_popup_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition bauhaus.c:894
int dt_bauhaus_slider_get_digits(GtkWidget *widget)
Definition bauhaus.c:3353
GtkWidget * dt_bauhaus_combobox_new_full(dt_bauhaus_t *bh, dt_gui_module_t *self, const char *label, const char *tip, int pos, GtkCallback callback, gpointer data, const char **texts)
Definition bauhaus.c:1708
static float _bh_get_row_height(struct dt_bauhaus_widget_t *w)
Get the total height of a GUI row containing a line of text + top and bottom padding.
Definition bauhaus.c:149
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1498
_bh_halign_t
Definition bauhaus.c:651
@ BH_ALIGN_RIGHT
Definition bauhaus.c:653
@ BH_ALIGN_CENTER
Definition bauhaus.c:654
@ BH_ALIGN_LEFT
Definition bauhaus.c:652
static gboolean ensure_focus_idle(gpointer data)
Definition bauhaus.c:446
static gboolean dt_bauhaus_popup_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition bauhaus.c:2474
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3346
GtkWidget * dt_bauhaus_slider_from_widget(dt_bauhaus_t *bh, dt_bauhaus_widget_t *w, dt_gui_module_t *self, float min, float max, float step, float defval, int digits, int feedback)
Definition bauhaus.c:1666
void dt_bauhaus_combobox_insert(GtkWidget *widget, const char *text, int pos)
Definition bauhaus.c:1929
static gboolean _widget_key_press(GtkWidget *widget, GdkEventKey *event)
Definition bauhaus.c:3172
void dt_bauhaus_slider_set_hard_max(GtkWidget *widget, float val)
Definition bauhaus.c:1432
static _bh_active_region_t _bh_get_active_region(GtkWidget *widget, double *x, double *y, double *width, GtkWidget *popup)
Check if we have user cursor over quad area or over the slider/main area, then correct cursor coordin...
Definition bauhaus.c:334
void dt_bauhaus_combobox_clear(GtkWidget *widget)
Definition bauhaus.c:2000
static gboolean dt_bauhaus_popup_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
Definition bauhaus.c:886
static void dt_bauhaus_slider_set_normalized(struct dt_bauhaus_widget_t *w, float pos, gboolean raise)
Set the value of a slider as a ratio of the GUI slider width.
Definition bauhaus.c:3478
static gboolean _widget_scroll(GtkWidget *widget, GdkEventScroll *event)
Definition bauhaus.c:3106
void dt_bauhaus_slider_clear_stops(GtkWidget *widget)
Definition bauhaus.c:2156
static gboolean dt_bauhaus_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition bauhaus.c:3716
static gboolean dt_bauhaus_popup_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition bauhaus.c:943
static void dt_bauhaus_widget_reject(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2374
char * dt_bauhaus_slider_get_text(GtkWidget *w, float val)
Definition bauhaus.c:3297
void dt_bauhaus_combobox_set_selected_text_align(GtkWidget *widget, const dt_bauhaus_combobox_alignment_t text_align)
Definition bauhaus.c:1903
void dt_bauhaus_combobox_set_editable(GtkWidget *widget, int editable)
Definition bauhaus.c:1885
static double _widget_get_main_width(struct dt_bauhaus_widget_t *w, GtkWidget *widget, double *total_width)
Get the width of the main Bauhaus widget area (slider scale or combobox), accounting for quad space,...
Definition bauhaus.c:205
gboolean dt_bauhaus_focus_in_callback(GtkWidget *widget, GdkEventFocus event, gpointer user_data)
Definition bauhaus.c:510
void dt_bauhaus_hide_popup(dt_bauhaus_t *bh)
Definition bauhaus.c:2961
gboolean dt_bauhaus_focus_callback(GtkWidget *widget, GtkDirectionType direction, gpointer data)
Definition bauhaus.c:529
static double _get_slider_height(GtkWidget *widget)
Definition bauhaus.c:255
void dt_bauhaus_slider_reset(GtkWidget *widget)
Definition bauhaus.c:3400
static void dt_bauhaus_window_show(GtkWidget *w, gpointer user_data)
Definition bauhaus.c:1040
static void _style_updated(GtkWidget *widget)
Definition bauhaus.c:1648
void dt_bauhaus_cleanup(dt_bauhaus_t *bauhaus)
Definition bauhaus.c:1349
static float _bh_slider_get_min_step(const struct dt_bauhaus_widget_t *const w)
Return the minimum representable value step, for current UI scaling factor and number of digits.
Definition bauhaus.c:382
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1491
const char * dt_bauhaus_combobox_get_entry(GtkWidget *widget, int pos)
Definition bauhaus.c:2010
gboolean dt_bauhaus_focus_out_callback(GtkWidget *widget, GdkEventFocus event, gpointer user_data)
Definition bauhaus.c:520
static void dt_bauhaus_draw_baseline(struct dt_bauhaus_widget_t *w, cairo_t *cr, float width)
Draw the slider baseline, aka the backgronud bar.
Definition bauhaus.c:2273
void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
Definition bauhaus.c:2164
#define DT_BAUHAUS_FOCUS_IDLE_MAX_TRIES
Definition bauhaus.c:445
void dt_bauhaus_load_theme(dt_bauhaus_t *bauhaus)
Definition bauhaus.c:1133
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3283
void dt_bauhaus_combobox_set_text(GtkWidget *widget, const char *text)
Definition bauhaus.c:2020
static void dt_bauhaus_draw_indicator(struct dt_bauhaus_widget_t *w, float pos, cairo_t *cr, float wd)
Definition bauhaus.c:2201
void dt_bauhaus_combobox_entry_set_sensitive(GtkWidget *widget, int pos, gboolean sensitive)
Definition bauhaus.c:2146
gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
Definition bauhaus.c:2122
_bh_valign_t
Definition bauhaus.c:643
@ BH_ALIGN_TOP
Definition bauhaus.c:644
@ BH_ALIGN_MIDDLE
Definition bauhaus.c:646
@ BH_ALIGN_BOTTOM
Definition bauhaus.c:645
void dt_bauhaus_widget_release_quad(GtkWidget *widget)
Definition bauhaus.c:1617
GtkWidget * dt_bauhaus_slider_new_with_range_and_feedback(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits, int feedback)
Definition bauhaus.c:1641
void dt_bauhaus_combobox_from_widget(dt_bauhaus_t *bh, dt_bauhaus_widget_t *w, dt_gui_module_t *self)
Definition bauhaus.c:1723
static void _get_preferred_width(GtkWidget *widget, gint *minimum_size, gint *natural_size)
Definition bauhaus.c:2930
void dt_bauhaus_combobox_insert_separator_with_height(GtkWidget *widget, int pos, const float row_height_factor)
Definition bauhaus.c:1954
static gboolean dt_bauhaus_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
Definition bauhaus.c:3500
float dt_bauhaus_slider_get_soft_max(GtkWidget *widget)
Definition bauhaus.c:1484
static void free_combobox_entry(gpointer data)
Definition bauhaus.c:624
static double _get_combobox_popup_height(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:274
void dt_bauhaus_widget_set_field(GtkWidget *widget, gpointer field, dt_introspection_type_t field_type)
Definition bauhaus.c:1565
static void _bh_combobox_get_hovered_entry(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:394
float dt_bauhaus_slider_get_soft_min(GtkWidget *widget)
Definition bauhaus.c:1467
void dt_bauhaus_slider_set_feedback(GtkWidget *widget, int feedback)
Definition bauhaus.c:3392
static double get_slider_line_offset(const double pos, const double scale, const double x, double y, const double line_height)
Definition bauhaus.c:813
void dt_bauhaus_disable_module_list(GtkWidget *widget)
Definition bauhaus.c:3743
static void _widget_finalize(GObject *widget)
Definition bauhaus.c:1076
static float _bh_get_combobox_entry_height(struct dt_bauhaus_widget_t *w, const dt_bauhaus_combobox_entry_t *entry)
Get the vertical space used by a combobox popup entry.
Definition bauhaus.c:160
_bh_active_region_t
Definition bauhaus.c:316
@ BH_REGION_QUAD
Definition bauhaus.c:319
@ BH_REGION_OUT
Definition bauhaus.c:317
@ BH_REGION_MAIN
Definition bauhaus.c:318
float dt_bauhaus_slider_get_val(GtkWidget *widget)
Definition bauhaus.c:3291
static dt_bauhaus_combobox_data_t * _combobox_data(GtkWidget *widget)
Definition bauhaus.c:1742
void dt_bauhaus_combobox_insert_full(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align, gpointer data, void(*free_func)(void *), int pos)
Definition bauhaus.c:1934
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2139
static void _commit_combobox_value(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2029
void dt_bauhaus_widget_set_quad_toggle(GtkWidget *widget, int toggle)
Definition bauhaus.c:1575
static int _combobox_selectable_count(const dt_bauhaus_combobox_data_t *d)
Count selectable combobox entries.
Definition bauhaus.c:1760
static gboolean dt_bauhaus_popup_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition bauhaus.c:970
static void show_pango_text(struct dt_bauhaus_widget_t *w, GtkStyleContext *context, cairo_t *cr, GdkRectangle *bounding_box, const char *text, _bh_halign_t halign, _bh_valign_t valign, PangoEllipsizeMode ellipsize, GdkRGBA *bg_color, float *width, float *height, GtkStateFlags state)
Display text aligned in a bounding box, with pseudo-classes properties handled, and optional backgrou...
Definition bauhaus.c:673
void dt_bauhaus_slider_set_offset(GtkWidget *widget, float offset)
Definition bauhaus.c:3433
static void _dt_bauhaus_slider_set_with_raise(GtkWidget *widget, float pos, gboolean raise)
Definition bauhaus.c:3306
#define DT_BAUHAUS_FOCUS_IDLE_SOURCE_KEY
Definition bauhaus.c:443
static void _combobox_next_sensitive(struct dt_bauhaus_widget_t *w, int delta)
Definition bauhaus.c:571
static gboolean dt_bauhaus_slider_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition bauhaus.c:3624
static void _combobox_set(GtkWidget *widget, const int pos)
Set a combobox to a given integer position. Private API function, called from user events.
Definition bauhaus.c:2064
void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
Definition bauhaus.c:1875
void dt_bauhaus_slider_set_step(GtkWidget *widget, float val)
Definition bauhaus.c:3360
static dt_bauhaus_combobox_entry_t * new_combobox_entry(const char *label, const char *tooltip, dt_bauhaus_combobox_alignment_t alignment, gboolean sensitive, void *data, void(*free_func)(void *))
Definition bauhaus.c:592
static void _slider_zoom_range(struct dt_bauhaus_widget_t *w, float zoom)
Definition bauhaus.c:856
static GdkRGBA * default_color_assign()
Definition bauhaus.c:634
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1474
void dt_bauhaus_combobox_insert_separator(GtkWidget *widget, int pos)
Definition bauhaus.c:1949
void dt_bauhaus_show_popup(GtkWidget *widget)
Definition bauhaus.c:2977
void dt_bauhaus_set_use_default_callback(GtkWidget *widget)
Tell the widget to use the globally-defined default callback in the bauhaus structure This callback n...
Definition bauhaus.c:3749
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:1966
void dt_bauhaus_widget_set_quad_active(GtkWidget *widget, int active)
Definition bauhaus.c:1581
static _bh_active_region_t _popup_coordinates(dt_bauhaus_t *bh, const double x_root, const double y_root, double *event_x, double *event_y)
Definition bauhaus.c:427
void dt_bauhaus_combobox_set_default(GtkWidget *widget, int def)
Definition bauhaus.c:1400
static double _widget_get_quad_width(struct dt_bauhaus_widget_t *w)
Get the width of the quad without padding.
Definition bauhaus.c:173
float dt_bauhaus_slider_get_hard_min(GtkWidget *widget)
Definition bauhaus.c:1425
static double _widget_get_total_width(struct dt_bauhaus_widget_t *w, GtkWidget *widget)
Get the total width of the main Bauhaus widget area, accounting for padding and margins.
Definition bauhaus.c:188
static float _bh_round_to_n_digits(const struct dt_bauhaus_widget_t *const w, float x)
Round a slider numeric value to the number of digits specified in the widget w.
Definition bauhaus.c:369
void dt_bauhaus_combobox_add_full(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align, gpointer data, void(free_func)(void *data), gboolean sensitive)
Definition bauhaus.c:1849
gpointer dt_bauhaus_combobox_get_data(GtkWidget *widget)
Definition bauhaus.c:1990
static void _commit_slider_value(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:3440
void dt_bauhaus_combobox_add_populate_fct(GtkWidget *widget, void(*fct)(GtkWidget *w, void *module))
Definition bauhaus.c:1813
static void _bauhaus_widget_init(dt_bauhaus_t *bauhaus, dt_bauhaus_widget_t *w, dt_gui_module_t *self)
Definition bauhaus.c:1367
void dt_bauhaus_combobox_add_separator(GtkWidget *widget)
Definition bauhaus.c:1861
static float _get_combobox_max_width(GtkWidget *widget)
Definition bauhaus.c:2699
static dt_bauhaus_combobox_entry_t * new_combobox_separator(const float row_height_factor)
Create a new combobox separator entry.
Definition bauhaus.c:613
void dt_bauhaus_combobox_add_list(GtkWidget *widget, const char **texts)
Definition bauhaus.c:1821
gboolean _action_request_focus(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
Definition bauhaus.c:538
static double _get_indicator_y_position(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:262
int dt_bauhaus_combobox_get_editable(GtkWidget *widget)
Definition bauhaus.c:1896
static void draw_slider_line(cairo_t *cr, const double pos, const double off, const double scale, const double width, const double height, const double line_height, double line_width)
Definition bauhaus.c:836
const char * dt_bauhaus_widget_get_label(GtkWidget *widget)
Definition bauhaus.c:1551
int dt_bauhaus_widget_get_quad_active(GtkWidget *widget)
Definition bauhaus.c:1598
void dt_bauhaus_slider_set_soft_min(GtkWidget *widget, float val)
Definition bauhaus.c:1457
void dt_bauhaus_disable_accels(GtkWidget *widget)
Definition bauhaus.c:3737
static void _margins_retrieve(struct dt_bauhaus_widget_t *w)
Update the box margin and padding properties of the widget w by reading CSS context.
Definition bauhaus.c:123
static gboolean dt_bauhaus_slider_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition bauhaus.c:3693
static gchar * _build_label(const struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2469
const char * dt_bauhaus_combobox_get_text(GtkWidget *widget)
Definition bauhaus.c:1973
void dt_bauhaus_slider_set_val(GtkWidget *widget, float val)
Definition bauhaus.c:3340
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3334
static double _widget_get_main_height(struct dt_bauhaus_widget_t *w, GtkWidget *widget)
Get the height of the main Bauhaus widget area (slider scale or combobox), that is the box allocation...
Definition bauhaus.c:222
float dt_bauhaus_slider_get_step(GtkWidget *widget)
Definition bauhaus.c:3367
static gboolean _widget_draw(GtkWidget *widget, cairo_t *crf)
Definition bauhaus.c:2754
static gboolean _enter_leave(GtkWidget *widget, GdkEventCrossing *event)
Definition bauhaus.c:1051
static void _slider_add_step(GtkWidget *widget, float delta, guint state)
Definition bauhaus.c:3087
void dt_bauhaus_widget_press_quad(GtkWidget *widget)
Definition bauhaus.c:1604
static int _combobox_entry_pos_to_public(const dt_bauhaus_combobox_data_t *d, const int entry_pos)
Convert an internal entry row to the public value index.
Definition bauhaus.c:1797
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2093
#define DT_BAUHAUS_FOCUS_IDLE_TRIES_KEY
Definition bauhaus.c:444
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
void dt_bauhaus_slider_set_hard_min(GtkWidget *widget, float val)
Definition bauhaus.c:1408
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1635
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:1912
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1701
void dt_bauhaus_widget_set_quad_visibility(GtkWidget *widget, const gboolean visible)
Definition bauhaus.c:1591
static double _get_slider_bar_height(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:267
static void _get_preferred_height(GtkWidget *widget, gint *minimum_size, gint *natural_size)
Definition bauhaus.c:2945
gboolean dt_bauhaus_combobox_set_from_text(GtkWidget *widget, const char *text)
Definition bauhaus.c:2103
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3410
float dt_bauhaus_slider_get_hard_max(GtkWidget *widget)
Definition bauhaus.c:1450
static void dt_bauhaus_draw_quad(struct dt_bauhaus_widget_t *w, cairo_t *cr, const double x, const double y)
Definition bauhaus.c:2235
static int _combobox_public_to_entry_pos(const dt_bauhaus_combobox_data_t *d, const int pos, const gboolean allow_end)
Convert a public value index to the internal entry row.
Definition bauhaus.c:1777
static void _translate_cursor(double *x, double *y, struct dt_bauhaus_widget_t *const w)
Translate in-place the cursor coordinates within the widget or popup according to padding and margin,...
Definition bauhaus.c:306
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1827
void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
Definition bauhaus.c:1557
static gboolean dt_bauhaus_popup_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition bauhaus.c:949
void dt_bauhaus_combobox_add_with_tooltip(GtkWidget *widget, const char *text, const char *tooltip)
Definition bauhaus.c:1832
void dt_bauhaus_combobox_add_separator_with_height(GtkWidget *widget, const float row_height_factor)
Definition bauhaus.c:1866
dt_bauhaus_t * dt_bauhaus_init()
Definition bauhaus.c:1231
static void dt_bh_init(DtBauhausWidget *class)
Definition bauhaus.c:1045
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3426
static gboolean dt_bauhaus_combobox_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition bauhaus.c:3224
void dt_bauhaus_combobox_add_aligned(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align)
Definition bauhaus.c:1844
static double _bh_slider_get_scale(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:388
static void dt_bh_class_init(DtBauhausWidgetClass *class)
Definition bauhaus.c:1112
static double _get_combobox_height(GtkWidget *widget)
Definition bauhaus.c:248
dt_bauhaus_combobox_alignment_t
Definition bauhaus.h:125
@ DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT
Definition bauhaus.h:127
#define DT_BAUHAUS_COMBO_MAX_TEXT
Definition bauhaus.h:74
@ DT_BAUHAUS_COMBOBOX
Definition bauhaus.h:89
@ DT_BAUHAUS_SLIDER
Definition bauhaus.h:88
#define DT_BAUHAUS_WIDGET(obj)
Definition bauhaus.h:62
#define DT_BAUHAUS_WIDGET_TYPE
Definition bauhaus.h:61
@ DT_BAUHAUS_QUAD_PRESSED_SIGNAL
Definition bauhaus.h:227
@ DT_BAUHAUS_VALUE_CHANGED_SIGNAL
Definition bauhaus.h:226
#define DT_BAUHAUS_COMBO_SEPARATOR_DEFAULT_HEIGHT_FACTOR
Definition bauhaus.h:75
void(* dt_bauhaus_quad_paint_f)(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Definition bauhaus.h:169
#define DT_BAUHAUS_SLIDER_MAX_STOPS
Definition bauhaus.h:73
#define INNER_PADDING
Definition bauhaus.h:81
float dt_calculator_solve(const float x, const char *formula)
Definition calculator.c:323
@ DT_COLORLABELS_PURPLE
Definition colorlabels.h:46
@ DT_COLORLABELS_GREEN
Definition colorlabels.h:44
@ DT_COLORLABELS_YELLOW
Definition colorlabels.h:43
@ DT_COLORLABELS_BLUE
Definition colorlabels.h:45
@ DT_COLORLABELS_LAST
Definition colorlabels.h:47
@ DT_COLORLABELS_RED
Definition colorlabels.h:42
static const float x
const float f
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const float const float const float min
const float inc
const float max
const float top
const float delta
GtkWidget * window
static void set_color(cairo_t *cr, GdkRGBA color)
Definition draw.h:125
G_DEFINE_TYPE(GtkDarktableDrawingArea, dtgtk_drawing_area, GTK_TYPE_DRAWING_AREA)
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
GdkRGBA color[]
Definition geotagging.c:541
const char * tooltip
Definition image.h:319
dt_introspection_type_t
static int precision(double x, double adj)
Definition lens.c:2507
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
static int ipow(int base, int exp)
Fast integer power, computing base^exp.
Definition math.h:415
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define M_PI
Definition math.h:47
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:105
const float factor
Definition pdf.h:91
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
const float r
PangoEllipsizeMode entries_ellipsis
Definition bauhaus.h:151
void(* populate)(GtkWidget *w, void *module)
Definition bauhaus.h:153
dt_bauhaus_combobox_alignment_t text_align
Definition bauhaus.h:149
Definition bauhaus.h:132
gboolean sensitive
Definition bauhaus.h:136
char * tooltip
Definition bauhaus.h:134
char * label
Definition bauhaus.h:133
float row_height_factor
Definition bauhaus.h:138
void(* free_func)(gpointer)
Definition bauhaus.h:140
dt_bauhaus_combobox_alignment_t alignment
Definition bauhaus.h:135
gboolean is_separator
Definition bauhaus.h:137
gpointer data
Definition bauhaus.h:139
const char * format
Definition bauhaus.h:117
GdkRGBA graph_bg
Definition bauhaus.h:281
guint32 opentime
Definition bauhaus.h:254
GdkRGBA color_value_text_insensitive
Definition bauhaus.h:278
float end_mouse_x
Definition bauhaus.h:256
float baseline_size
Definition bauhaus.h:271
GdkRGBA graph_scope_restricted
Definition bauhaus.h:282
GdkRGBA colorlabels[DT_COLORLABELS_LAST]
Definition bauhaus.h:284
void(* default_value_changed_callback)(GtkWidget *widget)
Definition bauhaus.h:288
GdkRGBA graph_overlay
Definition bauhaus.h:281
float mouse_line_distance
Definition bauhaus.h:259
GdkRGBA color_border
Definition bauhaus.h:277
GdkRGBA indicator_border
Definition bauhaus.h:277
GdkRGBA color_value_insensitive
Definition bauhaus.h:278
GdkRGBA graph_border
Definition bauhaus.h:281
GdkRGBA graph_fg_active
Definition bauhaus.h:281
GdkRGBA graph_grid
Definition bauhaus.h:281
PangoFontDescription * pango_font_desc
Definition bauhaus.h:274
GdkRGBA graph_exterior
Definition bauhaus.h:281
GdkRGBA color_fill
Definition bauhaus.h:277
gboolean hiding
Definition bauhaus.h:265
GtkWidget * popup_window
Definition bauhaus.h:246
GdkRGBA inset_histogram
Definition bauhaus.h:282
float quad_width
Definition bauhaus.h:273
float line_height
Definition bauhaus.h:269
float end_mouse_y
Definition bauhaus.h:256
GdkRGBA color_value_text
Definition bauhaus.h:278
float mouse_x
Definition bauhaus.h:250
GdkRGBA color_value
Definition bauhaus.h:278
char keys[64]
Definition bauhaus.h:261
float border_width
Definition bauhaus.h:272
GdkRGBA graph_fg
Definition bauhaus.h:281
GtkWidget * popup_area
Definition bauhaus.h:247
struct dt_bauhaus_widget_t * current
Definition bauhaus.h:245
GdkRGBA graph_colors[3]
Definition bauhaus.h:283
GdkRGBA color_fg
Definition bauhaus.h:277
int change_active
Definition bauhaus.h:258
GdkRGBA color_fg_insensitive
Definition bauhaus.h:277
float marker_size
Definition bauhaus.h:270
float mouse_y
Definition bauhaus.h:250
GdkRGBA color_bg
Definition bauhaus.h:277
char label[256]
Definition bauhaus.h:186
gboolean show_quad
Definition bauhaus.h:196
GtkBorder * padding
Definition bauhaus.h:203
dt_bauhaus_quad_paint_f quad_paint
Definition bauhaus.h:188
dt_bauhaus_data_t data
Definition bauhaus.h:218
gboolean no_module_list
Definition bauhaus.h:208
gboolean use_default_callback
Definition bauhaus.h:215
gboolean no_accels
Definition bauhaus.h:207
void * quad_paint_data
Definition bauhaus.h:192
GtkBorder * margin
Definition bauhaus.h:203
dt_gui_module_t *gpointer field
Definition bauhaus.h:181
dt_bauhaus_t * bauhaus
Definition bauhaus.h:211
dt_introspection_type_t field_type
Definition bauhaus.h:183
dt_bauhaus_type_t type
Definition bauhaus.h:177
The dt_gui_module_t type is the intersection between a dt_lib_module_t and a dt_iop_module_t structur...
GHashTable * entries
Definition supervisor.c:120
static G_BEGIN_DECLS cairo_surface_t * dt_cairo_surface_create_at_scale(cairo_format_t format, int width, int height, double scale)
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
dt_bauhaus_combobox_data_t combobox
Definition bauhaus.h:162
dt_bauhaus_slider_data_t slider
Definition bauhaus.h:161
GtkWidget * dt_widget_root_window(void)
double dt_widget_ppd(void)
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
void dt_widget_set_colorlabels(const GdkRGBA *labels, int count)
GtkWidget * dt_widget_scroll_focus(void)
GdkEventMask dt_widget_scroll_mask(void)
void dt_widget_set_scroll_focus(GtkWidget *widget)
gboolean dt_gui_widgets_suppressed(void)
gint dt_widget_natural_width(GtkWidget *widget)
void dt_widget_message(const char *message)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_widget_log(...)
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_cairo_set_font_options(cairo_t *cr, GtkWidget *widget)
void dt_capitalize_label(gchar *text)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
@ CPF_ACTIVE