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 // slash is not allowed in control names because that makes accel pathes fail
1529 assert(g_strrstr(label, "/") == NULL);
1530
1531 gchar *plugin_name = g_strdup_printf("%s/%s", m->name, w->label);
1532 dt_capitalize_label(plugin_name);
1533
1534 gchar *scope = g_strdup_printf("%s/Modules", m->view);
1535 dt_accels_new_darkroom_action(_action_request_focus, widget, widget, scope, plugin_name, 0, 0,
1536 _("Focuses the control"));
1537 g_object_set_data_full(G_OBJECT(widget), "accel-path",
1538 dt_accels_build_path("Darkroom/Modules", plugin_name), dt_free_gpointer);
1539 gtk_widget_set_has_tooltip(widget, TRUE);
1540 dt_free(scope);
1541 dt_free(plugin_name);
1542 }
1543
1544 gtk_widget_queue_draw(GTK_WIDGET(w));
1545 }
1546}
1547
1549{
1550 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1551 return w->label;
1552}
1553
1554void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
1555{
1556 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1557 w->quad_paint = f;
1558 w->quad_paint_flags = paint_flags;
1559 w->quad_paint_data = paint_data;
1560}
1561
1563{
1564 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1565 if(*w->label)
1566 fprintf(stderr, "[dt_bauhaus_widget_set_field] bauhaus label '%s' set before field (needs to be after)\n", w->label);
1567 w->field = field;
1569}
1570
1571// make this quad a toggle button:
1573{
1574 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1575 w->quad_toggle = toggle;
1576}
1577
1579{
1580 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1581 if (active)
1583 else
1584 w->quad_paint_flags &= ~CPF_ACTIVE;
1585 gtk_widget_queue_draw(GTK_WIDGET(w));
1586}
1587
1588void dt_bauhaus_widget_set_quad_visibility(GtkWidget *widget, const gboolean visible)
1589{
1590 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1591 w->show_quad = visible;
1592 gtk_widget_queue_draw(GTK_WIDGET(w));
1593}
1594
1596{
1597 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1598 return (w->quad_paint_flags & CPF_ACTIVE) == CPF_ACTIVE;
1599}
1600
1602{
1603 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1604 if(w->quad_toggle)
1605 {
1607 }
1608 else
1610
1611 g_signal_emit_by_name(G_OBJECT(w), "quad-pressed");
1612}
1613
1615{
1616 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1617 gtk_widget_grab_focus(widget);
1618
1619 if(!w->quad_toggle)
1620 {
1622 w->quad_paint_flags &= ~CPF_ACTIVE;
1623 gtk_widget_queue_draw(GTK_WIDGET(w));
1624 }
1625}
1626
1628{
1629 return dt_bauhaus_slider_new_with_range(bh, self, 0.0, 1.0, 0.1, 0.5, 3);
1630}
1631
1633 float defval, int digits)
1634{
1635 return dt_bauhaus_slider_new_with_range_and_feedback(bh, self, min, max, step, defval, digits, 1);
1636}
1637
1639 float step, float defval, int digits, int feedback)
1640{
1641 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(g_object_new(DT_BAUHAUS_WIDGET_TYPE, NULL));
1642 return dt_bauhaus_slider_from_widget(bh, w,self, min, max, step, defval, digits, feedback);
1643}
1644
1645static void _style_updated(GtkWidget *widget)
1646{
1647 // GTK's own handler applies the CSS opacity (the theme dims *:disabled) and queues the redraw;
1648 // without it, a widget keeps the opacity of whichever state its style was first computed in.
1649 GTK_WIDGET_CLASS(dt_bh_parent_class)->style_updated(widget);
1650
1651 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
1653
1654 // gtk_widget_set_size_request is the minimal preferred, size.
1655 // it NEEDS to be defined and will be contextually adapted, possibly overriden by CSS.
1656 // Thing is Gtk CSS min-width in combination with hexpand is wonky so this is how it should be done.
1657 if(w->type == DT_BAUHAUS_COMBOBOX)
1658 gtk_widget_set_size_request(widget, -1, _get_combobox_height(widget));
1659 else if(w->type == DT_BAUHAUS_SLIDER)
1660 gtk_widget_set_size_request(widget, -1, _get_slider_height(widget));
1661}
1662
1664 float step, float defval, int digits, int feedback)
1665{
1667 _bauhaus_widget_init(bh, w, self);
1669 d->min = d->soft_min = d->hard_min = min;
1670 d->max = d->soft_max = d->hard_max = max;
1671 d->step = step;
1672 // normalize default:
1673 d->defpos = defval;
1674 d->pos = (defval - min) / (max - min);
1675 d->oldpos = d->pos;
1676 d->digits = digits;
1677 d->format = "";
1678 d->factor = 1.0f;
1679 d->offset = 0.0f;
1680
1681 d->grad_cnt = 0;
1682 d->grad_col = NULL;
1683 d->grad_pos = NULL;
1684
1685 d->fill_feedback = feedback;
1686
1687 d->is_dragging = 0;
1688
1689 dt_gui_add_class(GTK_WIDGET(w), "bauhaus_slider");
1690
1691 g_signal_connect(G_OBJECT(w), "button-press-event", G_CALLBACK(dt_bauhaus_slider_button_press), NULL);
1692 g_signal_connect(G_OBJECT(w), "button-release-event", G_CALLBACK(dt_bauhaus_slider_button_release), NULL);
1693 g_signal_connect(G_OBJECT(w), "motion-notify-event", G_CALLBACK(dt_bauhaus_slider_motion_notify), NULL);
1694
1695 return GTK_WIDGET(w);
1696}
1697
1699{
1700 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(g_object_new(DT_BAUHAUS_WIDGET_TYPE, NULL));
1702 return GTK_WIDGET(w);
1703}
1704
1706 int pos, GtkCallback callback, gpointer data, const char **texts)
1707{
1708 GtkWidget *combo = dt_bauhaus_combobox_new(bh, self);
1710 dt_bauhaus_combobox_add_list(combo, texts);
1711 dt_bauhaus_combobox_set(combo, pos);
1712 gtk_widget_set_tooltip_text(combo, tip ? tip : _(label));
1713 if(callback) g_signal_connect(G_OBJECT(combo), "value-changed", G_CALLBACK(callback), data);
1714
1715 return combo;
1716}
1717
1718
1719
1721{
1723 _bauhaus_widget_init(bh, w, self);
1725 d->entries = g_ptr_array_new_full(4, free_combobox_entry);
1726 d->defpos = 0;
1727 d->active = -1;
1728 d->editable = 0;
1729 d->text_align = DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT;
1730 d->entries_ellipsis = PANGO_ELLIPSIZE_END;
1731 d->populate = NULL;
1732 d->text = NULL;
1733
1734 dt_gui_add_class(GTK_WIDGET(w), "bauhaus_combobox");
1735
1736 g_signal_connect(G_OBJECT(w), "button-press-event", G_CALLBACK(dt_bauhaus_combobox_button_press), NULL);
1737}
1738
1740{
1741 if(IS_NULL_PTR(widget)) return NULL;
1742 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1743 if(w->type != DT_BAUHAUS_COMBOBOX) return NULL;
1745
1746 if(d->active >= d->entries->len) d->active = -1;
1747
1748 return d;
1749}
1750
1758{
1759 int count = 0;
1760 for(int i = 0; !IS_NULL_PTR(d) && i < d->entries->len; i++)
1761 {
1762 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
1763 if(!entry->is_separator) count++;
1764 }
1765 return count;
1766}
1767
1775 const gboolean allow_end)
1776{
1777 if(IS_NULL_PTR(d) || pos < 0) return -1;
1778
1779 int selectable_pos = 0;
1780 for(int i = 0; i < d->entries->len; i++)
1781 {
1782 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
1783 if(entry->is_separator) continue;
1784 if(selectable_pos == pos) return i;
1785 selectable_pos++;
1786 }
1787
1788 return (allow_end && selectable_pos == pos) ? (int)d->entries->len : -1;
1789}
1790
1794static int _combobox_entry_pos_to_public(const dt_bauhaus_combobox_data_t *d, const int entry_pos)
1795{
1796 if(IS_NULL_PTR(d) || entry_pos < 0 || entry_pos >= d->entries->len) return -1;
1797
1798 int selectable_pos = 0;
1799 for(int i = 0; i <= entry_pos; i++)
1800 {
1801 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
1802 if(entry->is_separator) continue;
1803 if(i == entry_pos) return selectable_pos;
1804 selectable_pos++;
1805 }
1806
1807 return -1;
1808}
1809
1810void dt_bauhaus_combobox_add_populate_fct(GtkWidget *widget, void (*fct)(GtkWidget *w, void *module))
1811{
1812 if(IS_NULL_PTR(widget)) return;
1813 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1814 if(w->type == DT_BAUHAUS_COMBOBOX)
1815 w->data.combobox.populate = fct;
1816}
1817
1818void dt_bauhaus_combobox_add_list(GtkWidget *widget, const char **texts)
1819{
1820 while(texts && *texts)
1821 dt_bauhaus_combobox_add_full(widget, _(*(texts++)), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, NULL, NULL, TRUE);
1822}
1823
1824void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
1825{
1827}
1828
1829void dt_bauhaus_combobox_add_with_tooltip(GtkWidget *widget, const char *text, const char *tooltip)
1830{
1831 if(IS_NULL_PTR(widget)) return;
1832 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1833 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1837 g_ptr_array_add(d->entries, entry);
1838 if(d->active < 0) d->active = (int)d->entries->len - 1;
1839}
1840
1842{
1843 dt_bauhaus_combobox_add_full(widget, text, align, NULL, NULL, TRUE);
1844}
1845
1847 gpointer data, void (free_func)(void *data), gboolean sensitive)
1848{
1849 if(IS_NULL_PTR(widget)) return;
1850 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1851 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1853 dt_bauhaus_combobox_entry_t *entry = new_combobox_entry(text, NULL, align, sensitive, data, free_func);
1854 g_ptr_array_add(d->entries, entry);
1855 if(d->active < 0) d->active = (int)d->entries->len - 1;
1856}
1857
1862
1863void dt_bauhaus_combobox_add_separator_with_height(GtkWidget *widget, const float row_height_factor)
1864{
1865 if(IS_NULL_PTR(widget)) return;
1866 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1867 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1869 g_ptr_array_add(d->entries, new_combobox_separator(row_height_factor));
1870}
1871
1872void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
1873{
1874 if(IS_NULL_PTR(widget)) return;
1875 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1876 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1878 d->entries_ellipsis = ellipis;
1879}
1880
1881
1883{
1884 if(IS_NULL_PTR(widget)) return;
1885 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1886 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1888 d->editable = editable ? 1 : 0;
1889 if(d->editable && IS_NULL_PTR(d->text))
1890 d->text = calloc(1, DT_BAUHAUS_COMBO_MAX_TEXT);
1891}
1892
1894{
1896
1897 return d ? d->editable : 0;
1898}
1899
1901{
1902 if(IS_NULL_PTR(widget)) return;
1903 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1904 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1906 d->text_align = text_align;
1907}
1908
1910{
1912 if(IS_NULL_PTR(d)) return;
1913 const int entry_pos = _combobox_public_to_entry_pos(d, pos, FALSE);
1914
1915 if(entry_pos < 0) return;
1916
1917 // move active position up if removing anything before it
1918 // or when removing last position that is currently active.
1919 // this also sets active to -1 when removing the last remaining entry in a combobox.
1920 if(d->active > entry_pos || d->active == d->entries->len-1)
1921 d->active--;
1922
1923 g_ptr_array_remove_index(d->entries, entry_pos);
1924}
1925
1926void dt_bauhaus_combobox_insert(GtkWidget *widget, const char *text,int pos)
1927{
1929}
1930
1932 gpointer data, void (*free_func)(void *), int pos)
1933{
1934 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1935 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1937 const int entry_pos = _combobox_public_to_entry_pos(d, pos, TRUE);
1938 if(entry_pos < 0) return;
1939 g_ptr_array_insert(d->entries, entry_pos, new_combobox_entry(text, NULL, align, TRUE, data, free_func));
1940 if(d->active < 0)
1941 d->active = entry_pos;
1942 else if(entry_pos <= d->active)
1943 d->active++;
1944}
1945
1950
1951void dt_bauhaus_combobox_insert_separator_with_height(GtkWidget *widget, int pos, const float row_height_factor)
1952{
1953 if(IS_NULL_PTR(widget)) return;
1954 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1955 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1957 const int entry_pos = _combobox_public_to_entry_pos(d, pos, TRUE);
1958 if(entry_pos < 0) return;
1959 g_ptr_array_insert(d->entries, entry_pos, new_combobox_separator(row_height_factor));
1960 if(entry_pos <= d->active) d->active++;
1961}
1962
1964{
1966
1968}
1969
1971{
1973 if(IS_NULL_PTR(d)) return NULL;
1974
1975 if(d->active < 0)
1976 {
1977 return d->editable ? d->text : NULL;
1978 }
1979 else
1980 {
1981 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
1982 if(entry->is_separator) return NULL;
1983 return entry->label;
1984 }
1985}
1986
1988{
1990 if(IS_NULL_PTR(d) || d->active < 0) return NULL;
1991
1992 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
1993 if(entry->is_separator) return NULL;
1994 return entry->data;
1995}
1996
1998{
1999 if(IS_NULL_PTR(widget)) return;
2000 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2001 if(w->type != DT_BAUHAUS_COMBOBOX) return;
2003 d->active = -1;
2004 g_ptr_array_set_size(d->entries, 0);
2005}
2006
2007const char *dt_bauhaus_combobox_get_entry(GtkWidget *widget, int pos)
2008{
2010 const int entry_pos = _combobox_public_to_entry_pos(d, pos, FALSE);
2011 if(entry_pos < 0) return NULL;
2012
2013 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, entry_pos);
2014 return entry->label;
2015}
2016
2017void dt_bauhaus_combobox_set_text(GtkWidget *widget, const char *text)
2018{
2020 if(IS_NULL_PTR(d) || !d->editable) return;
2021
2022 g_strlcpy(d->text, text, DT_BAUHAUS_COMBO_MAX_TEXT);
2023}
2024
2025
2027{
2028 // Announce the new value. Whoever listens decides what it costs: the history commit
2029 // batches the pipeline recompute it triggers, so emitting on every scroll step is fine
2030 // and this widget has no reason to hold anything back.
2031
2032 // Don't commit anything while a reset is in progress (reloading history, applying a
2033 // style, ...): those set widget values programmatically and must not reach history.
2034 if(dt_gui_widgets_suppressed()) return;
2035
2037 {
2039 w->bauhaus->default_value_changed_callback(GTK_WIDGET(w));
2040 else
2041 fprintf(stderr, "ERROR: %s - %s is set to use default callback but none is provided\n", w->module->name,
2042 w->label);
2043 }
2044 else
2045 {
2046 if(w->module)
2047 fprintf(stderr, "WARNING: %s - %s has an IOP module but doesn't use default callback\n", w->module->name,
2048 w->label);
2049 }
2050
2051 // We need te emit this signal inconditionnaly
2052 g_signal_emit_by_name(G_OBJECT(w), "value-changed");
2053}
2054
2061void _combobox_set(GtkWidget *widget, const int pos)
2062{
2063 if(IS_NULL_PTR(widget)) return;
2064 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2066 const int old_pos = d->active;
2067 const int new_pos = (d->entries) ? CLAMP(pos, -1, (int)d->entries->len - 1)
2068 : -1;
2069 if(new_pos >= 0)
2070 {
2071 // if the new position is a separator, don't change the active position but still redraw to update the visual selection
2072 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, new_pos);
2073 if(entry->is_separator) return;
2074 }
2075
2076 if(old_pos != new_pos)
2077 {
2078 d->active = new_pos;
2079
2080 if(w->bauhaus->current == w)
2081 gtk_widget_queue_draw(w->bauhaus->popup_area);
2082
2083 gtk_widget_queue_draw(GTK_WIDGET(w));
2084
2086 }
2087}
2088
2089// Public API function, called from GUI init and update
2090void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
2091{
2093 const int selectable_count = _combobox_selectable_count(d);
2094 const int public_pos = (selectable_count > 0) ? CLAMP(pos, -1, selectable_count - 1) : -1;
2095 const int entry_pos = _combobox_public_to_entry_pos(d, public_pos, FALSE);
2096 _combobox_set(widget, entry_pos);
2097}
2098
2099
2100gboolean dt_bauhaus_combobox_set_from_text(GtkWidget *widget, const char *text)
2101{
2102 if(IS_NULL_PTR(text)) return FALSE;
2103
2105
2106 for(int i = 0; d && i < d->entries->len; i++)
2107 {
2108 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
2109 if(entry->is_separator) continue;
2110 if(!g_strcmp0(entry->label, text))
2111 {
2112 _combobox_set(widget, i);
2113 return TRUE;
2114 }
2115 }
2116 return FALSE;
2117}
2118
2120{
2122
2123 for(int i = 0; d && i < d->entries->len; i++)
2124 {
2125 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
2126 if(entry->is_separator) continue;
2127 if(GPOINTER_TO_INT(entry->data) == value)
2128 {
2129 _combobox_set(widget, i);
2130 return TRUE;
2131 }
2132 }
2133 return FALSE;
2134}
2135
2137{
2139
2140 return _combobox_entry_pos_to_public(d, d ? d->active : -1);
2141}
2142
2143void dt_bauhaus_combobox_entry_set_sensitive(GtkWidget *widget, int pos, gboolean sensitive)
2144{
2146 const int entry_pos = _combobox_public_to_entry_pos(d, pos, FALSE);
2147 if(entry_pos < 0) return;
2148
2149 dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)g_ptr_array_index(d->entries, entry_pos);
2150 entry->sensitive = sensitive;
2151}
2152
2154{
2155 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2156 if(w->type != DT_BAUHAUS_SLIDER) return;
2158 d->grad_cnt = 0;
2159}
2160
2161void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
2162{
2163 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2164 if(w->type != DT_BAUHAUS_SLIDER) return;
2166
2167 if(IS_NULL_PTR(d->grad_col))
2168 {
2169 d->grad_col = malloc(DT_BAUHAUS_SLIDER_MAX_STOPS * sizeof(*d->grad_col));
2170 d->grad_pos = malloc(DT_BAUHAUS_SLIDER_MAX_STOPS * sizeof(*d->grad_pos));
2171 }
2172 // need to replace stop?
2173 for(int k = 0; k < d->grad_cnt; k++)
2174 {
2175 if(d->grad_pos[k] == stop)
2176 {
2177 d->grad_col[k][0] = r;
2178 d->grad_col[k][1] = g;
2179 d->grad_col[k][2] = b;
2180 return;
2181 }
2182 }
2183 // new stop:
2184 if(d->grad_cnt < DT_BAUHAUS_SLIDER_MAX_STOPS)
2185 {
2186 int k = d->grad_cnt++;
2187 d->grad_pos[k] = stop;
2188 d->grad_col[k][0] = r;
2189 d->grad_col[k][1] = g;
2190 d->grad_col[k][2] = b;
2191 }
2192 else
2193 {
2194 fprintf(stderr, "[bauhaus_slider_set_stop] only %d stops allowed.\n", DT_BAUHAUS_SLIDER_MAX_STOPS);
2195 }
2196}
2197
2198static void dt_bauhaus_draw_indicator(struct dt_bauhaus_widget_t *w, float pos, cairo_t *cr, float wd)
2199{
2200 // draw scale indicator (the tiny triangle)
2201 const float size = w->bauhaus->marker_size;
2202 const float vertical_position = _get_indicator_y_position(w);
2203 const float horizontal_position = pos * wd;
2204
2205 cairo_save(cr);
2206 cairo_translate(cr, horizontal_position, vertical_position);
2207
2209 const gboolean has_colored_background = d->grad_cnt > 0;
2210
2211 if(!has_colored_background && d->fill_feedback)
2212 {
2213 // Plain indicator (regular sliders)
2214 cairo_arc(cr, 0, 0, size / 2., 0, M_PI * 2);
2215 set_color(cr, w->bauhaus->color_fg);
2216 cairo_set_line_width(cr, 0);
2217 cairo_fill(cr);
2218 }
2219 else
2220 {
2221 // Hollow indicator to keep the slider background visible through the marker.
2222 const float border = (size - w->bauhaus->baseline_size) / 2.;
2223 set_color(cr, w->bauhaus->color_fg);
2224 cairo_set_line_width(cr, border);
2225 const float radius = size / 2. - border / 2.;
2226 cairo_arc(cr, 0, 0, radius, 0, M_PI * 2);
2227 cairo_stroke(cr);
2228 }
2229 cairo_restore(cr);
2230}
2231
2232static void dt_bauhaus_draw_quad(struct dt_bauhaus_widget_t *w, cairo_t *cr, const double x, const double y)
2233{
2234 if(!w->show_quad) return;
2235
2236 cairo_save(cr);
2237 if(w->quad_paint)
2238 {
2239 // draw color picker
2240 w->quad_paint(cr, x, y,
2241 w->bauhaus->quad_width, // width
2242 w->bauhaus->quad_width, // height
2244 }
2245 else if(w->type == DT_BAUHAUS_COMBOBOX)
2246 {
2247 // draw combobox chevron
2248 // Inset the chevron from the right edge of the (now flush) quad box so it
2249 // doesn't crowd or overflow the widget border. The quad box itself is placed
2250 // identically to the slider's, so this keeps the chevron in line with slider
2251 // quad icons when widgets are stacked.
2252 // FIXME: Why do we need an extra half-pixel shift to the left for pixel-correctness ?
2253 cairo_translate(cr, x + w->bauhaus->quad_width / 2. - 0.5, y + w->bauhaus->line_height / 2.);
2254 const float r = w->bauhaus->quad_width * .2f;
2255 cairo_move_to(cr, -r, -r * .5f);
2256 cairo_line_to(cr, 0, r * .5f);
2257 cairo_line_to(cr, r, -r * .5f);
2258 cairo_stroke(cr);
2259 }
2260 cairo_restore(cr);
2261}
2262
2270static void dt_bauhaus_draw_baseline(struct dt_bauhaus_widget_t *w, cairo_t *cr, float width)
2271{
2272 // draw line for orientation in slider
2273 cairo_save(cr);
2275 const gboolean has_colored_background = d->grad_cnt > 0;
2276 const float baseline_top = w->bauhaus->line_height + INNER_PADDING;
2277 const float baseline_height = w->bauhaus->baseline_size;
2278 const gboolean is_sensitive = gtk_widget_is_sensitive(GTK_WIDGET(w));
2279
2280 // the background of the line
2281 // Note: to ensure the indicator is not clipped on the left side,
2282 // we push the whole slider of an indicator radius to the right.
2283 // Issue is that leaves a dent on the baseline, so the trick
2284 // is to make it overlap back by a radius on the left.
2285 const double x_origin = -w->bauhaus->marker_size / 3.;
2286 // The left edge was pushed by x_origin to overlap the indicator's leftmost
2287 // resting position. Extend the width by the same amount so the right edge
2288 // stays at `width` (flush with the right-aligned numeric value) instead of
2289 // retreating by |x_origin| and letting the value overhang the baseline.
2290 const double baseline_width = width - x_origin;
2291 // Make sure we use integer coordinates to limit anti-aliasing blur
2292 cairo_rectangle(cr, round(x_origin), round(baseline_top),
2293 round(baseline_width), round(baseline_height));
2294
2295 cairo_pattern_t *gradient = NULL;
2296 if(has_colored_background && is_sensitive)
2297 {
2298 // gradient line as used in some modules for hue, saturation, lightness
2299 const double zoom = (d->max - d->min) / (d->hard_max - d->hard_min);
2300 const double offset = (d->min - d->hard_min) / (d->hard_max - d->hard_min);
2301 gradient = cairo_pattern_create_linear(x_origin, 0, width, baseline_height);
2302 for(int k = 0; k < d->grad_cnt; k++)
2303 cairo_pattern_add_color_stop_rgba(gradient, (d->grad_pos[k] - offset) / zoom,
2304 d->grad_col[k][0], d->grad_col[k][1], d->grad_col[k][2], 0.4f);
2305 cairo_set_source(cr, gradient);
2306 }
2307 else
2308 {
2309 // regular baseline
2310 set_color(cr, w->bauhaus->color_bg);
2311 }
2312
2313 cairo_fill(cr);
2314
2315
2316 if(w->bauhaus->border_width > 0)
2317 {
2318 // Draw the border on top
2319 // We need to recess the coordinates by half a line-width for perfect border matching
2320 const double line_width = 1.;
2321 cairo_rectangle(cr, round(x_origin) + line_width / 2., round(baseline_top) + line_width / 2.,
2322 round(baseline_width) - line_width, round(baseline_height) - line_width);
2323 cairo_set_line_width(cr, line_width);
2325 cairo_stroke(cr);
2326 }
2327
2328 if(gradient) cairo_pattern_destroy(gradient);
2329
2330 // get the reference of the slider aka the position of the 0 value
2331 float origin = fmaxf(fminf((d->factor > 0 ? -d->min - d->offset/d->factor
2332 : d->max + d->offset/d->factor)
2333 / (d->max - d->min),
2334 1.0f) * width,
2335 0.f);
2336
2337 // have a `fill ratio feel' from zero to current position
2338 if(!has_colored_background && d->fill_feedback && is_sensitive)
2339 {
2340 set_color(cr, w->bauhaus->color_value);
2341
2342 const float position = d->pos * width;
2343 const float delta = position - origin;
2344
2345 // origin = 0 means start on left, but our left is shifted to the right,
2346 // so offset it. (see x_origin)
2347 if(origin == 0.f)
2348 cairo_rectangle(cr, x_origin, baseline_top, delta - x_origin, baseline_height);
2349 else
2350 cairo_rectangle(cr, origin, baseline_top, delta, baseline_height);
2351
2352 cairo_fill(cr);
2353 }
2354
2355 // draw the 0 reference graduation if it's different than the bounds of the slider
2356 // If the max of the slider is 360, it is likely an absolute hue slider in degrees
2357 // a zero in periodic stuff has not much meaning so we skip it.
2358 if(d->hard_max != 360.0f && is_sensitive)
2359 {
2360 set_color(cr, w->bauhaus->color_fg);
2361
2362 // If we are to put the 0 reference on the "left", this time we need to account for
2363 // actual leftmost resting position of the indicator, not the starting point of the baseline.
2364 cairo_arc(cr, origin, baseline_top + w->bauhaus->marker_size, w->bauhaus->border_width / 2., 0, 2 * M_PI);
2365 cairo_fill(cr);
2366 }
2367
2368 cairo_restore(cr);
2369}
2370
2372{
2373 switch(w->type)
2374 {
2376 break;
2377 case DT_BAUHAUS_SLIDER:
2378 {
2381 }
2382 break;
2383 default:
2384 break;
2385 }
2386}
2387
2389{
2390 GtkWidget *widget = GTK_WIDGET(w);
2391
2392 switch(w->type)
2393 {
2395 {
2397
2398 if(d->editable && w->bauhaus->keys_cnt > 0)
2399 {
2400 // combobox is editable and we have text, assume it is a custom input
2401 memset(d->text, 0, DT_BAUHAUS_COMBO_MAX_TEXT);
2402 g_strlcpy(d->text, w->bauhaus->keys, DT_BAUHAUS_COMBO_MAX_TEXT);
2403 _combobox_set(widget, -1); // select custom entry
2404
2405#if DEBUG
2406 fprintf(stdout, "combobox went the custom path\n");
2407#endif
2408 }
2409 else
2410 {
2411 if(w->bauhaus->keys_cnt > 0)
2412 {
2413 // combobox is not editable, but we have text. Assume user wanted to init a selection from keyboard.
2414 // find the closest match by looking for the entry having the maximum number
2415 // of characters in common with the user input.
2416 gchar *keys = g_utf8_casefold(w->bauhaus->keys, -1);
2417 int match = -1;
2418 int matches = 0;
2419
2420 for(int j = 0; j < d->entries->len; j++)
2421 {
2422 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, j);
2423 gchar *text_cmp = g_utf8_casefold(entry->label, -1);
2424 if(entry->sensitive && !strncmp(text_cmp, keys, w->bauhaus->keys_cnt))
2425 {
2426 matches++;
2427 match = j;
2428 }
2429 dt_free(text_cmp);
2430 }
2431 dt_free(keys);
2432
2433 // Accept result only if exactly one match was found. Anything else is ambiguous
2434 if(matches == 1)
2435 _combobox_set(widget, match);
2436 }
2437 else {
2438 // Active entry (below cursor or scrolled)
2439 _combobox_set(widget, d->hovered);
2440 }
2441 }
2442
2443 break;
2444 }
2445 case DT_BAUHAUS_SLIDER:
2446 {
2447 // The slider popup uses the quadratic magnifier for accurate setting.
2448 // We need extra conversions from cursor coordinates to set it right.
2449 // This needs to be kept in sync with popup_draw()
2451
2452 // This is needed to accept the change.
2453 // d->pos is soft-updated with corrected coordinates for drawing purposes only in popup_redraw().
2454 // We need to reset it to the original value temporarily, and request a proper setting
2455 // with value-changed signal through dt_bauhaus_slider_set_normalized()
2456 const float value = d->pos;
2457 d->pos = d->oldpos;
2459 break;
2460 }
2461 default:
2462 break;
2463 }
2464}
2465
2466static gchar *_build_label(const struct dt_bauhaus_widget_t *w)
2467{
2468 return g_strdup(w->label);
2469}
2470
2471static gboolean dt_bauhaus_popup_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
2472{
2473 // Popups belong to the app, not to the bauhaus widget. That's confusing.
2474 // Also the *widget param here is the popup container, still not the bauhaus widget. Even more confusing.
2475 // This is the actual parent bauhaus widget :
2476 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
2478
2479 // get area properties
2480 GtkAllocation allocation;
2481 gtk_widget_get_allocation(widget, &allocation);
2482 cairo_surface_t *cst = dt_cairo_surface_create_at_scale(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height, dt_widget_ppd());
2483 cairo_t *cr = cairo_create(cst);
2484 GtkStyleContext *context = gtk_widget_get_style_context(widget);
2485
2486 // look up some colors once
2487 GdkRGBA text_color, text_color_selected, text_color_hover, text_color_insensitive, text_color_focused;
2488 gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &text_color);
2489 gtk_style_context_get_color(context, GTK_STATE_FLAG_SELECTED, &text_color_selected);
2490 gtk_style_context_get_color(context, GTK_STATE_FLAG_PRELIGHT, &text_color_hover);
2491 gtk_style_context_get_color(context, GTK_STATE_FLAG_INSENSITIVE, &text_color_insensitive);
2492 gtk_style_context_get_color(context, GTK_STATE_FLAG_FOCUSED, &text_color_focused);
2493
2494 GdkRGBA *fg_color = default_color_assign();
2495 GdkRGBA *bg_color;
2496 GtkStateFlags state = gtk_widget_get_state_flags(widget);
2497
2498 gtk_style_context_get(context, state, "background-color", &bg_color, NULL);
2499 gtk_style_context_get_color(context, state, fg_color);
2500
2501 // draw background
2502 gtk_render_background(context, cr, 0, 0, allocation.width, allocation.height);
2503 gtk_render_frame(context, cr, 0, 0, allocation.width, allocation.height);
2504
2505 const double main_height = _widget_get_main_height(w, widget);
2506 double total_width = 0.f;
2507 const double main_width = _widget_get_main_width(w, NULL, &total_width);
2508
2509 // translate to account for the widget spacing
2510 cairo_translate(cr, w->padding->left, w->padding->top);
2511
2512 // switch on bauhaus widget type (so we only need one static window)
2513 switch(w->type)
2514 {
2515 case DT_BAUHAUS_SLIDER:
2516 {
2518 const double slider_cursor_radius = 0.5 * w->bauhaus->marker_size;
2519 const double text_width = main_width + slider_cursor_radius;
2520 cairo_save(cr);
2521 cairo_translate(cr, slider_cursor_radius, 0.0);
2522 set_color(cr, *fg_color);
2523
2524 float scale = _bh_slider_get_scale(w);
2525 const int num_scales = 1.f / scale;
2526 const float bottom_baseline = _get_slider_bar_height(w);
2527
2528 for(int k = 0; k < num_scales; k++)
2529 {
2530 const float off = k * scale - d->oldpos;
2531 GdkRGBA fg_copy = *fg_color;
2532 fg_copy.alpha = scale / fabsf(off);
2533 set_color(cr, fg_copy);
2534 draw_slider_line(cr, d->oldpos, off, scale, main_width, main_height, bottom_baseline, 1);
2535 cairo_stroke(cr);
2536 }
2537 cairo_restore(cr);
2538
2539 // Get the x offset compared to d->oldpos accounting for vertical position magnification
2540 const double mouse_off = d->pos - d->oldpos;
2541
2542 cairo_save(cr);
2543 cairo_translate(cr, slider_cursor_radius, 0.0);
2544
2545 // Draw the baseline with fill feedback if any (needs the new d->pos set before)
2546 dt_bauhaus_draw_baseline(w, cr, main_width);
2547
2548 // draw mouse over indicator line
2550 draw_slider_line(cr, d->oldpos, mouse_off, scale, main_width, main_height, bottom_baseline, 2);
2551 cairo_stroke(cr);
2552
2553 // draw indicator
2554 dt_bauhaus_draw_indicator(w, d->pos, cr, main_width);
2555
2556 cairo_restore(cr);
2557
2558 // draw numerical value:
2559 cairo_save(cr);
2561
2562 float value_width = 0.f;
2563 char *text = dt_bauhaus_slider_get_text(GTK_WIDGET(w), dt_bauhaus_slider_get(GTK_WIDGET(w)));
2564 GdkRectangle bounding_value = { .x = 0.,
2565 .y = 0.,
2566 .width = text_width,
2567 .height = w->bauhaus->line_height };
2568 // Display user keyboard input if any, otherwise the current value
2569 show_pango_text(w, context, cr, &bounding_value,
2570 (w->bauhaus->keys_cnt) ? w->bauhaus->keys : text, BH_ALIGN_RIGHT,
2571 BH_ALIGN_MIDDLE, PANGO_ELLIPSIZE_NONE, NULL, &value_width, NULL, GTK_STATE_FLAG_NORMAL);
2572 dt_free(text);
2573
2574 // label on top of marker:
2575 gchar *label_text = _build_label(w);
2576 const float label_width = text_width - value_width - INNER_PADDING;
2577 GdkRectangle bounding_label = { .x = 0.,
2578 .y = 0.,
2579 .width = label_width,
2580 .height = w->bauhaus->line_height };
2581 set_color(cr, *fg_color);
2582 show_pango_text(w, context, cr, &bounding_label, label_text, BH_ALIGN_LEFT, BH_ALIGN_MIDDLE,
2583 PANGO_ELLIPSIZE_END, NULL, NULL, NULL, GTK_STATE_FLAG_NORMAL);
2584 dt_free(label_text);
2585
2586 cairo_restore(cr);
2587 }
2588 break;
2590 {
2592
2593 // User keyboard input goes first
2594 double popup_y = 0.;
2595 if(w->bauhaus->keys_cnt > 0)
2596 {
2597 cairo_save(cr);
2598 set_color(cr, text_color_focused);
2599 const double query_height = _bh_get_row_height(w);
2600 GdkRectangle query_label = { .x = 0.,
2601 .y = popup_y,
2602 .width = main_width,
2603 .height = query_height };
2604 show_pango_text(w, context, cr, &query_label, w->bauhaus->keys, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE,
2605 PANGO_ELLIPSIZE_NONE, NULL, NULL, NULL, GTK_STATE_FLAG_NORMAL);
2606 popup_y += query_height;
2607 cairo_restore(cr);
2608 }
2609
2610 cairo_save(cr);
2611 gchar *keys = g_utf8_casefold(w->bauhaus->keys, -1);
2612 for(int j = 0; j < d->entries->len; j++)
2613 {
2614 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, j);
2615 const double entry_height = _bh_get_combobox_entry_height(w, entry);
2616 if(entry->is_separator)
2617 {
2618 if(w->bauhaus->keys_cnt == 0)
2619 {
2620 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.);
2621 cairo_move_to(cr, 0., popup_y + 0.5 * entry_height);
2622 cairo_line_to(cr, main_width, popup_y + 0.5 * entry_height);
2623 cairo_set_line_width(cr, 1.);
2624 cairo_stroke(cr);
2625 }
2626 popup_y += entry_height;
2627 continue;
2628 }
2629
2630 gchar *text_cmp = g_utf8_casefold(entry->label, -1);
2631 if(!strncmp(text_cmp, keys, w->bauhaus->keys_cnt))
2632 {
2633 // If user typed some keys, display matching entries only
2634
2635 // The GTK state flag is applied to the whole widget,
2636 // we need to dispatch it individually to each entry
2637 if(!entry->sensitive)
2638 {
2639 set_color(cr, text_color_insensitive);
2640 state = GTK_STATE_FLAG_INSENSITIVE;
2641 }
2642 else if(j == d->active)
2643 {
2644 set_color(cr, text_color_selected);
2645 state = GTK_STATE_FLAG_SELECTED;
2646 }
2647 else if(j == d->hovered)
2648 {
2649 set_color(cr, text_color_hover);
2650 state = GTK_STATE_FLAG_PRELIGHT;
2651 }
2652 else
2653 {
2654 set_color(cr, text_color);
2655 state = GTK_STATE_FLAG_NORMAL;
2656 }
2657
2658 GdkRectangle bounding_label = { .x = 0.,
2659 .y = popup_y,
2660 .width = main_width,
2661 .height = entry_height };
2662#if DEBUG
2663 cairo_rectangle(cr, bounding_label.x, bounding_label.y, bounding_label.width, bounding_label.height);
2664 cairo_set_line_width(cr, 2);
2665 cairo_stroke(cr);
2666#endif
2667 show_pango_text(w, context, cr, &bounding_label, entry->label, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE,
2668 d->entries_ellipsis, bg_color, NULL, NULL, state);
2669 }
2670
2671 dt_free(text_cmp);
2672 popup_y += entry_height;
2673 }
2674 cairo_restore(cr);
2675 dt_free(keys);
2676 }
2677 break;
2678 default:
2679 // yell
2680 break;
2681 }
2682
2683 cairo_destroy(cr);
2684 cairo_set_source_surface(crf, cst, 0, 0);
2685 cairo_paint(crf);
2686 cairo_surface_destroy(cst);
2687
2688 gdk_rgba_free(bg_color);
2689 gdk_rgba_free(fg_color);
2690
2691 return TRUE;
2692}
2693
2694
2695// Get the maximum width of a full combobox without ellipsization
2697{
2698 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2700 GtkStyleContext *context = gtk_widget_get_style_context(widget);
2701 const GtkStateFlags state = gtk_widget_get_state_flags(widget);
2702
2703 cairo_surface_t *cst = dt_cairo_surface_create_at_scale(CAIRO_FORMAT_ARGB32, 999, 999, dt_widget_ppd());
2704 cairo_t *cr = cairo_create(cst);
2705
2706 float width = 0.f;
2707
2708 // Get chevron width + padding if any
2709 if(w->show_quad)
2711
2712 float label_width = 0.f;
2713 GdkRectangle bounding_label = { .x = 0.,
2714 .y = 0.,
2715 .width = 999,
2716 .height = 999 };
2717
2718 show_pango_text(w, context, cr, &bounding_label, w->label, BH_ALIGN_LEFT, BH_ALIGN_MIDDLE,
2719 PANGO_ELLIPSIZE_NONE, NULL, &label_width, NULL, state);
2720
2721 if(label_width > 0.f) width += label_width + INNER_PADDING;
2722
2723 // Get width of the longest entry
2724 float max_entry = 0.f;
2725 for(int i = 0; i < d->entries->len; i++)
2726 {
2727 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
2728
2729 // The value is shown right-aligned, ellipsized if needed.
2730 GdkRectangle bounding_value = { .x = 0,
2731 .y = 0.,
2732 .width = 999,
2733 .height = 999 };
2734 float entry_label_width = 0.f;
2735
2736 show_pango_text(w, context, cr, &bounding_value, entry->label, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE, PANGO_ELLIPSIZE_NONE, NULL,
2737 &entry_label_width, NULL, state);
2738
2739 if(entry_label_width + INNER_PADDING > max_entry) max_entry = entry_label_width + INNER_PADDING;
2740 }
2741
2742 width += max_entry;
2743 width += w->margin->left + w->margin->right + w->padding->left + w->padding->right;
2744
2745 cairo_destroy(cr);
2746 cairo_surface_destroy(cst);
2747
2748 return width;
2749}
2750
2751static gboolean _widget_draw(GtkWidget *widget, cairo_t *crf)
2752{
2753 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2754 GtkStyleContext *context = gtk_widget_get_style_context(widget);
2755
2756 // Get current Gtk allocation
2757 GtkAllocation allocation;
2758 gtk_widget_get_allocation(widget, &allocation);
2759
2760 if(w->type == DT_BAUHAUS_COMBOBOX)
2761 allocation.height = _get_combobox_height(widget);
2762 else if(w->type == DT_BAUHAUS_SLIDER)
2763 allocation.height = _get_slider_height(widget);
2764
2765 if(w->type == DT_BAUHAUS_COMBOBOX && !w->expand)
2766 {
2767 // For comboboxes that are not set to hexpand, limit the width span to what's needed
2768 // to display the internal text, aka prevent them to grow out of proportions
2769 float max_width = _get_combobox_max_width(widget);
2770 if(max_width < allocation.width)
2771 allocation.width = ceilf(max_width);
2772 }
2773
2774 // Force allocate to our requirements. Yes, it's ugly.
2775 gtk_widget_size_allocate(widget, &allocation);
2776
2777 cairo_surface_t *cst = dt_cairo_surface_create_at_scale(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height, dt_widget_ppd());
2778 cairo_t *cr = cairo_create(cst);
2779
2780 GdkRGBA *text_color = default_color_assign();
2781 GdkRGBA *value_color = default_color_assign();
2782 GdkRGBA *value_text_color = default_color_assign();
2783 GtkStateFlags state = gtk_widget_get_state_flags(widget);
2784 if(gtk_widget_has_focus(widget))
2785 state |= GTK_STATE_FLAG_FOCUSED;
2786
2787 gtk_style_context_save(context);
2788 gtk_style_context_set_state(context, state);
2789 gtk_style_context_get_color(context, state, text_color);
2790
2791 *value_color = gtk_widget_is_sensitive(widget) ? w->bauhaus->color_value : w->bauhaus->color_value_insensitive;
2792 *value_text_color = gtk_widget_is_sensitive(widget) ? w->bauhaus->color_value_text : w->bauhaus->color_value_text_insensitive;
2793
2794 // Compute internal widget sizes, that is allocation minus margins
2796 // Note : box is the border box in CSS conventions
2797 const float box_width = allocation.width - w->margin->left - w->margin->right;
2798 const float box_height = allocation.height - w->margin->top - w->margin->bottom;
2799
2800 // Paint background and borders first
2801 gtk_render_background(context, crf, w->margin->left, w->margin->top, box_width, box_height);
2802 gtk_render_frame(context, crf, w->margin->left, w->margin->top, box_width, box_height);
2803
2804 // Translate Cairo coordinates to account for the widget spacing
2805 cairo_translate(cr,
2806 w->margin->left + w->padding->left,
2807 w->margin->top + w->padding->top);
2808
2809 const float available_width = _widget_get_main_width(w, NULL, NULL);
2810 const float inner_height = _widget_get_main_height(w, NULL);
2811
2812 // draw type specific content:
2813 cairo_save(cr);
2814 set_color(cr, *text_color);
2815 cairo_set_line_width(cr, 1.0);
2816 switch(w->type)
2817 {
2819 {
2820 // draw label and quad area at right end
2821 // Gap must match the one reserved by _widget_get_main_width() (2*INNER_PADDING)
2822 // and used by the slider, so combobox quads stay flush and vertically aligned
2823 // with slider quad icons when stacked.
2824 if(w->show_quad)
2825 dt_bauhaus_draw_quad(w, cr, available_width + 2 * INNER_PADDING, 0.);
2826
2828 const PangoEllipsizeMode combo_ellipsis = d->entries_ellipsis;
2829
2830 float label_width = 0.f;
2831 float label_height = 0.f;
2832
2833 GdkRectangle bounding_label = { .x = 0.,
2834 .y = 0.,
2835 .width = available_width,
2836 .height = inner_height };
2837 set_color(cr, *text_color);
2838 show_pango_text(w, context, cr, &bounding_label, w->label, BH_ALIGN_LEFT, BH_ALIGN_MIDDLE,
2839 combo_ellipsis, NULL, &label_width, &label_height, state);
2840
2841 // The value is shown right-aligned, ellipsized if needed.
2842 gchar *text = d->text;
2843 if(d->active >= 0 && d->active < d->entries->len)
2844 {
2845 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
2846 text = entry->label;
2847 }
2848 GdkRectangle bounding_value = { .x = label_width + INNER_PADDING,
2849 .y = 0.,
2850 .width = available_width - label_width - INNER_PADDING,
2851 .height = inner_height };
2852 set_color(cr, *value_text_color);
2853 show_pango_text(w, context, cr, &bounding_value, text, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE, combo_ellipsis, NULL,
2854 NULL, NULL, state);
2855
2856 break;
2857 }
2858 case DT_BAUHAUS_SLIDER:
2859 {
2860 const double slider_cursor_radius = 0.5 * w->bauhaus->marker_size;
2861 const double text_width = available_width + slider_cursor_radius;
2862
2863 // line for orientation
2864 cairo_save(cr);
2865 cairo_translate(cr, slider_cursor_radius, 0.0);
2866 dt_bauhaus_draw_baseline(w, cr, available_width);
2867 cairo_restore(cr);
2868
2869 // Paint the non-active quad icon with some transparency, because
2870 // icons are bolder than the neighbouring text and appear brighter.
2871 cairo_save(cr);
2872 if(!(w->quad_paint_flags & CPF_ACTIVE))
2873 cairo_set_source_rgba(cr, text_color->red, text_color->green, text_color->blue, text_color->alpha * 0.7);
2874
2875 dt_bauhaus_draw_quad(w, cr, text_width + 2. * INNER_PADDING, 0.);
2876 cairo_restore(cr);
2877
2878 float value_width = 0;
2879 if(gtk_widget_is_sensitive(widget))
2880 {
2881 cairo_save(cr);
2882 cairo_translate(cr, slider_cursor_radius, 0.0);
2883 dt_bauhaus_draw_indicator(w, w->data.slider.pos, cr, available_width);
2884 cairo_restore(cr);
2885
2886 char *text = dt_bauhaus_slider_get_text(widget, dt_bauhaus_slider_get(widget));
2887 GdkRectangle bounding_value = { .x = 0.,
2888 .y = 0.,
2889 .width = text_width,
2890 .height = w->bauhaus->line_height };
2891 set_color(cr, *value_text_color);
2892 show_pango_text(w, context, cr, &bounding_value, text, BH_ALIGN_RIGHT, BH_ALIGN_MIDDLE,
2893 PANGO_ELLIPSIZE_NONE, NULL, &value_width, NULL, state);
2894 dt_free(text);
2895 }
2896
2897 // label on top of marker:
2898 gchar *label_text = _build_label(w);
2899 const float label_width = text_width - value_width - INNER_PADDING;
2900 GdkRectangle bounding_label = { .x = 0.,
2901 .y = 0.,
2902 .width = label_width,
2903 .height = w->bauhaus->line_height };
2904 set_color(cr, *text_color);
2905 show_pango_text(w, context, cr, &bounding_label, label_text, BH_ALIGN_LEFT, BH_ALIGN_MIDDLE,
2906 PANGO_ELLIPSIZE_END, NULL, NULL, NULL, state);
2907 dt_free(label_text);
2908 }
2909 break;
2910 default:
2911 break;
2912 }
2913 cairo_restore(cr);
2914 cairo_destroy(cr);
2915 cairo_set_source_surface(crf, cst, 0, 0);
2916 cairo_paint(crf);
2917 cairo_surface_destroy(cst);
2918 gtk_style_context_restore(context);
2919
2920 gdk_rgba_free(text_color);
2921 gdk_rgba_free(value_color);
2922 gdk_rgba_free(value_text_color);
2923
2924 return TRUE;
2925}
2926
2927static void _get_preferred_width(GtkWidget *widget, gint *minimum_size, gint *natural_size)
2928{
2929 // Nothing clever here : preferred size is the size of the container.
2930 // If user is not happy with that, it's his responsibility to resize sidebars.
2931 // Minimum size is left at 0 so the widget can shrink with its parent;
2932 // leaving it unset would read as uninitialized stack garbage and could
2933 // clamp natural_size up to a stale value, preventing the widget from
2934 // growing again when the parent grows.
2935 *minimum_size = 0;
2936
2937 // The host knows which panel this sits in and how wide it is; -1 means it has no opinion.
2938 const gint host_width = dt_widget_natural_width(widget);
2939 *natural_size = (host_width >= 0) ? host_width : DT_PIXEL_APPLY_DPI(300);
2940}
2941
2942static void _get_preferred_height(GtkWidget *widget, gint *minimum_size, gint *natural_size)
2943{
2944 // Resolve the height from the same getters used everywhere else so it is
2945 // always correct at measure time, regardless of when "style-updated" fires.
2946 // Relying solely on the size-request set in _style_updated is not enough:
2947 // when the widget lives inside a freshly popped-up GtkMenu, the menu measures
2948 // its children before their style context is resolved, so it would size
2949 // itself from a stale/zero height and end up showing scroll arrows.
2950 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
2952 const gint height = (w->type == DT_BAUHAUS_COMBOBOX) ? (gint)ceil(_get_combobox_height(widget))
2953 : (gint)ceil(_get_slider_height(widget));
2954 *minimum_size = height;
2955 *natural_size = height;
2956}
2957
2959{
2960 if(bh->current)
2961 {
2962 gtk_grab_remove(bh->popup_area);
2963 gtk_widget_hide(bh->popup_window);
2964 gtk_window_set_attached_to(GTK_WINDOW(bh->popup_window), NULL);
2965
2966 // Give back focus to the attached widget
2967 gtk_widget_grab_focus(GTK_WIDGET(bh->current));
2968 dt_widget_set_scroll_focus(GTK_WIDGET(bh->current));
2969
2970 bh->current = NULL;
2971 }
2972}
2973
2975{
2976 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2978
2979 // Re-parent onto the window this widget actually lives in, which is not necessarily the
2980 // application's main window: modules shown in a modal (the export dialog) have their own
2981 // toplevel. The popup's position is computed relative to that same window below, so the two
2982 // must agree or the popup lands outside its parent.
2983 GtkWidget *parent_toplevel = gtk_widget_get_toplevel(widget);
2984 if(parent_toplevel && gtk_widget_is_toplevel(parent_toplevel))
2985 gtk_window_set_transient_for(GTK_WINDOW(w->bauhaus->popup_window), GTK_WINDOW(parent_toplevel));
2986 w->bauhaus->current = w;
2987 w->bauhaus->keys_cnt = 0;
2988 memset(w->bauhaus->keys, 0, sizeof(w->bauhaus->keys));
2989 w->bauhaus->change_active = 0;
2990 w->bauhaus->mouse_line_distance = 0.0f;
2991 w->bauhaus->hiding = FALSE;
2992
2993 // Make sure all relevant widgets exist
2994 gtk_widget_realize(w->bauhaus->popup_window);
2995 gtk_widget_realize(widget);
2997
2998 GtkAllocation tmp;
2999 gtk_widget_get_allocation(widget, &tmp);
3000 int width = MAX(tmp.width, 1);
3001 int height = MAX(tmp.height, 1);
3002
3003 switch(w->bauhaus->current->type)
3004 {
3005 case DT_BAUHAUS_SLIDER:
3006 {
3007 // Slider popup: make it square
3009 d->oldpos = d->pos;
3010 d->is_dragging = FALSE;
3011 height = tmp.width;
3012 break;
3013 }
3015 {
3017 break;
3018 }
3019 default:
3020 {
3021 fprintf(stderr, "[dt_bauhaus_show_popup] The bauhaus widget has an unknown type\n");
3022 break;
3023 }
3024 }
3025
3026 /* Bind to CSS rules from parent widget */
3027 GtkStyleContext *context = gtk_widget_get_style_context(w->bauhaus->popup_area);
3028 gtk_style_context_add_class(context, "dt_bauhaus_popup");
3029 gtk_window_set_attached_to(GTK_WINDOW(w->bauhaus->popup_window), widget);
3030 #ifdef GDK_WINDOWING_WAYLAND
3031 if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) {
3032 GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
3033 gtk_window_set_transient_for(GTK_WINDOW(w->bauhaus->popup_window), GTK_WINDOW(toplevel));
3034 }
3035 #endif
3036
3037 // The popup window stays transient for the main application window, so the anchor
3038 // rectangle needs to remain expressed in that coordinate space even when the popup is
3039 // logically attached to an enclosing popover on Wayland.
3040 gint wx = 0, wy = 0;
3041 GdkWindow *widget_window = gtk_widget_get_window(widget);
3042 if(widget_window) gdk_window_get_origin(widget_window, &wx, &wy);
3043 wx += w->margin->left;
3044 wy += w->margin->top;
3045
3046 gint wwx = 0, wwy = 0;
3047 // The widget's OWN toplevel, not the host's main window: correct for popups raised from a
3048 // secondary window, and it removes the last reason to ask the host which window is primary.
3049 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(w));
3050 GdkWindow *main_window = (toplevel && gtk_widget_is_toplevel(toplevel))
3051 ? gtk_widget_get_window(toplevel) : NULL;
3052 if(main_window) gdk_window_get_origin(main_window, &wwx, &wwy);
3053
3054 GdkRectangle anchor = {
3055 .x = wx - wwx,
3056 .y = wy - wwy,
3057 .width = MAX(tmp.width, 1),
3058 .height = MAX(tmp.height, 1)
3059 };
3060
3061 // Set desired size, but it's more a guide than a rule.
3062 gtk_widget_set_size_request(w->bauhaus->popup_area, width, height);
3063 gtk_widget_set_size_request(w->bauhaus->popup_window, width, height);
3064
3065 // Need to call resize to actually change something
3066 gtk_window_resize(GTK_WINDOW(w->bauhaus->popup_window), width, height);
3067
3068 GdkWindow *window = gtk_widget_get_window(w->bauhaus->popup_window);
3069 if(IS_NULL_PTR(window))
3070 {
3071 gtk_widget_show_all(w->bauhaus->popup_window);
3072 gtk_widget_grab_focus(w->bauhaus->popup_area);
3073 return;
3074 }
3075
3076 // For Wayland (and supposed to work on X11 too) and Gtk 3.24 this is how you do it
3077 gdk_window_move_to_rect(GDK_WINDOW(window), &anchor, GDK_GRAVITY_STATIC, GDK_GRAVITY_STATIC,
3078 GDK_ANCHOR_SLIDE, 0, 0);
3079
3080 gtk_widget_show_all(w->bauhaus->popup_window);
3081 gtk_widget_grab_focus(w->bauhaus->popup_area);
3082}
3083
3084static void _slider_add_step(GtkWidget *widget, float delta, guint state)
3085{
3086 if(delta == 0.f) return;
3087 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3089
3092 else if(dt_modifier_is(state, GDK_SHIFT_MASK)) delta *= 5.f;
3093
3094 // Ensure the requested delta is at least visible given current number of digits in display
3095 const float min_visible = 1.f / (fabsf(d->factor) * ipow(10, d->digits));
3096 if(fabsf(delta) < min_visible)
3097 delta = copysignf(min_visible, delta);
3098
3099 const float value = dt_bauhaus_slider_get(widget);
3101}
3102
3103static gboolean _widget_scroll(GtkWidget *widget, GdkEventScroll *event)
3104{
3105 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3106 const gboolean popup_captured = (w->bauhaus->current == w);
3107 const gboolean scroll_captured = (dt_widget_scroll_focus() == widget);
3108 const gboolean key_captured = gtk_widget_has_focus(widget);
3109 const gboolean smooth = (event->direction == GDK_SCROLL_SMOOTH);
3110
3111 // - native Gtk focus (keyboard), that takes precedence and records all keyboard events,
3112 // - custom scroll focus (mouse wheel), that should not overlap with vertical scrolling.
3113 // Scroll focus is a subset of Gtk focus, aka we can lose scroll focus while we have Gtk focus,
3114 // but delayed history commits may transiently steal Gtk focus while the
3115 // pointer never leaves the widget. In that case, keep trusting the explicit
3116 // scroll focus until leave-notify releases it.
3117 // We also extend widget focus with the popup window focus if it is captured
3118 // by the current widget.
3119 if(!key_captured && !popup_captured && !scroll_captured)
3120 return FALSE;
3121
3122 // Keep ownership of the whole touchpad gesture sequence.
3124
3125 int delta_x = 0;
3126 int delta_y = 0;
3127
3128 if(!dt_gui_get_scroll_unit_deltas(event, &delta_x, &delta_y))
3129 {
3130 // Important: for touchpad scroll, sub-unit deltas and stop events must not
3131 // propagate to the parent scrolled window, otherwise the same gesture gets
3132 // re-accumulated there.
3133 return smooth;
3134 }
3135
3136 const gboolean vscroll = delta_y != 0 && abs(delta_y) > abs(delta_x);
3137 const gboolean hscroll = delta_x != 0 && abs(delta_x) > abs(delta_y);
3138
3139 if(w->type == DT_BAUHAUS_SLIDER)
3140 {
3141 if(hscroll)
3142 {
3143 // inconditionnaly record horizontal scroll on slider
3144 _slider_add_step(widget, delta_x, event->state);
3145 return TRUE;
3146 }
3147 else if(vscroll && dt_widget_scroll_focus())
3148 {
3149 // convert vertical scrolling to horizontal only if we have the scroll focus
3150 _slider_add_step(widget, -delta_y, event->state);
3151 return TRUE;
3152 }
3153
3154 // Diagonal smooth gestures: consume them if captured, so they do not leak.
3155 return smooth;
3156 }
3157 else
3158 {
3159 if(vscroll && dt_widget_scroll_focus())
3160 {
3161 _combobox_next_sensitive(w, delta_y);
3162 return TRUE;
3163 }
3164
3165 return smooth;
3166 }
3167}
3168
3169static gboolean _widget_key_press(GtkWidget *widget, GdkEventKey *event)
3170{
3171 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3172 guint key = dt_keys_mainpad_alternatives(event->keyval);
3173
3174 if(w->type == DT_BAUHAUS_SLIDER)
3175 {
3176 switch(key)
3177 {
3178 case GDK_KEY_Right:
3179 _slider_add_step(widget, 1, event->state);
3180 return TRUE;
3181
3182 case GDK_KEY_Left:
3183 _slider_add_step(widget, -1, event->state);
3184 return TRUE;
3185
3186 case GDK_KEY_Insert:
3187 if(w->quad_toggle)
3188 {
3191 return TRUE;
3192 }
3193
3194 default:
3195 return FALSE;
3196 }
3197 }
3198 else if(w->type == DT_BAUHAUS_COMBOBOX)
3199 {
3200 switch(key)
3201 {
3202 case GDK_KEY_Return:
3203 dt_bauhaus_show_popup(widget);
3204 return TRUE;
3205
3206 case GDK_KEY_Insert:
3207 if(w->quad_toggle)
3208 {
3211 return TRUE;
3212 }
3213
3214 default:
3215 return FALSE;
3216 }
3217 }
3218 return FALSE;
3219}
3220
3221static gboolean dt_bauhaus_combobox_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3222{
3223 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3224
3225 double event_x = event->x;
3226 double event_y = event->y;
3227 double width;
3228 _bh_active_region_t activated = _bh_get_active_region(widget, &event_x, &event_y, &width, NULL);
3229
3231
3232 if(activated == BH_REGION_OUT)
3233 {
3235 return FALSE;
3236 }
3237
3238 gtk_widget_grab_focus(widget);
3240
3241 if(activated == BH_REGION_QUAD && w->quad_toggle)
3242 {
3244 return TRUE;
3245 }
3246 else
3247 {
3248 // If no quad toggle, treat the whole widget as unit pack.
3249 if(event->button == 3)
3250 {
3251 w->bauhaus->mouse_x = event_x;
3252 w->bauhaus->mouse_y = event_y;
3253 dt_bauhaus_show_popup(widget);
3254 return TRUE;
3255 }
3256 else if(event->button == 1)
3257 {
3258 // reset to default.
3259 if(event->type == GDK_2BUTTON_PRESS)
3260 {
3261 // never called, as we popup the other window under your cursor before.
3262 // (except in weird corner cases where the popup is under the -1st entry
3263 dt_bauhaus_combobox_set(widget, d->defpos);
3265 }
3266 else
3267 {
3268 // single click, show options
3269 w->bauhaus->opentime = event->time;
3270 w->bauhaus->mouse_x = event_x;
3271 w->bauhaus->mouse_y = event_y;
3272 dt_bauhaus_show_popup(widget);
3273 }
3274 return TRUE;
3275 }
3276 }
3277 return FALSE;
3278}
3279
3281{
3282 // first cast to bh widget, to check that type:
3283 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3285 return d->min + d->pos * (d->max - d->min);
3286}
3287
3289{
3290 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(widget)->data.slider;
3291 return dt_bauhaus_slider_get(widget) * d->factor + d->offset;
3292}
3293
3295{
3296 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(w)->data.slider;
3297 if((d->hard_max * d->factor + d->offset)*(d->hard_min * d->factor + d->offset) < 0)
3298 return g_strdup_printf("%+.*f%s", d->digits, val * d->factor + d->offset, d->format);
3299 else
3300 return g_strdup_printf( "%.*f%s", d->digits, val * d->factor + d->offset, d->format);
3301}
3302
3303static void _dt_bauhaus_slider_set_with_raise(GtkWidget *widget, float pos, gboolean raise)
3304{
3305 // translate by bounds and call set_normalized.
3306 // raise == FALSE re-applies the value (re-clamped to the current bounds) and
3307 // redraws without committing to history or emitting "value-changed". This is
3308 // what bound changes (soft min/max) need: re-clamping the current value to a
3309 // new display range is not a user edit, and emitting would re-enter the
3310 // module's gui_changed handler -- which during gui_init runs before all
3311 // widgets exist (NULL deref), and at runtime would spuriously commit.
3312 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3314 const float rpos = CLAMP(pos, d->hard_min, d->hard_max);
3315
3316 // Restore soft min/max if we are in its range
3317 float rrpos = (rpos - d->soft_min) / (d->soft_max - d->soft_min);
3318 if(rrpos > 0.f)
3319 d->min = d->soft_min;
3320 else
3321 d->min = rpos;
3322
3323 if(rrpos < 1.f)
3324 d->max = d->soft_max;
3325 else
3326 d->max = rpos;
3327
3328 dt_bauhaus_slider_set_normalized(w, (rpos - d->min) / (d->max - d->min), raise);
3329}
3330
3331void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
3332{
3333 // this is the public interface function, it commits the value and emits "value-changed"
3335}
3336
3337void dt_bauhaus_slider_set_val(GtkWidget *widget, float val)
3338{
3339 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(widget)->data.slider;
3340 dt_bauhaus_slider_set(widget, (val - d->offset) / d->factor);
3341}
3342
3344{
3345 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3347 d->digits = val;
3348}
3349
3351{
3352 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3354 return d->digits;
3355}
3356
3358{
3359 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3361 d->step = val;
3362}
3363
3365{
3366 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3368 float step = d->step;
3369
3370 if(step == 0.f)
3371 {
3372 const float min = d->soft_min;
3373 const float max = d->soft_max;
3374
3375 // Derive the automatic keyboard/wheel step from the internal slider domain.
3376 // Display formatting may rescale that domain (for example "%" multiplies by 100),
3377 // but interaction needs to stay in the unformatted value space to avoid jumping
3378 // across the whole range in a single step for small-percent sliders.
3379 const float top = fminf(max-min, fmaxf(fabsf(min), fabsf(max)));
3380 if(top >= 100.f)
3381 step = 1.f;
3382 else
3383 step = top / 100.f;
3384 }
3385
3386 return copysignf(step, d->factor);
3387}
3388
3390{
3391 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3393 d->fill_feedback = feedback;
3394 gtk_widget_queue_draw(widget);
3395}
3396
3398{
3399 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3401 d->min = d->soft_min;
3402 d->max = d->soft_max;
3403 dt_bauhaus_slider_set(widget, d->defpos);
3404 return;
3405}
3406
3407void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
3408{
3409 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3411 d->format = g_intern_string(format);
3412
3413 if(strstr(format,"%") && fabsf(d->hard_max) <= 10)
3414 {
3415 if(d->factor == 1.0f) d->factor = 100;
3416 // Scaling by 100 costs two decimals, but a slider declared with fewer than two must not end
3417 // up with a negative digit count : it is a decimal exponent, fed to ipow() on every value
3418 // change and every redraw, and there is no such thing as fewer than zero decimals.
3419 d->digits = MAX(d->digits - 2, 0);
3420 }
3421}
3422
3424{
3425 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3427 d->factor = factor;
3428}
3429
3430void dt_bauhaus_slider_set_offset(GtkWidget *widget, float offset)
3431{
3432 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3434 d->offset = offset;
3435}
3436
3438{
3439 // Announce the new value. Whoever listens decides what it costs: the history commit
3440 // batches the pipeline recompute it triggers, so emitting on every scroll step is fine
3441 // and this widget has no reason to hold anything back.
3442
3443 // Don't commit anything while a reset is in progress (reloading history, applying a
3444 // style, ...): those set widget values programmatically and must not reach history.
3445 if(dt_gui_widgets_suppressed()) return;
3446
3448 {
3450 w->bauhaus->default_value_changed_callback(GTK_WIDGET(w));
3451 else
3452 fprintf(stderr, "ERROR: %s - %s is set to use default callback but none is provided\n", w->module->name,
3453 w->label);
3454 }
3455 else
3456 {
3457 if(w->module)
3458 fprintf(stderr, "WARNING: %s - %s has an IOP module but doesn't use default callback\n", w->module->name,
3459 w->label);
3460 }
3461
3462 // We need te emit this signal inconditionnaly
3463 g_signal_emit_by_name(G_OBJECT(w), "value-changed");
3464}
3465
3475static void dt_bauhaus_slider_set_normalized(struct dt_bauhaus_widget_t *w, float pos, gboolean raise)
3476{
3478 const float old_pos = d->pos;
3479 const float new_pos = CLAMP(pos, 0.0f, 1.0f);
3480
3481 if(old_pos != new_pos || raise)
3482 {
3483 const float new_value = new_pos * (d->max - d->min) + d->min;
3484 const float precision = (float)ipow(10, d->digits) * d->factor;
3485 const float rounded_value = roundf(new_value * precision) / precision;
3486 d->pos = (rounded_value - d->min) / (d->max - d->min);
3487
3488 if(w->bauhaus->current == w)
3489 gtk_widget_queue_draw(w->bauhaus->popup_area);
3490
3491 gtk_widget_queue_draw(GTK_WIDGET(w));
3492
3493 if(raise) _commit_slider_value(w);
3494 }
3495}
3496
3497static gboolean dt_bauhaus_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
3498{
3499 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
3501
3502 guint key = dt_keys_mainpad_alternatives(event->keyval);
3503
3504 switch(w->type)
3505 {
3506 case DT_BAUHAUS_SLIDER:
3507 {
3508 if(bh->keys_cnt + 2 < 64
3509 && (key == GDK_KEY_space || // SPACE
3510 key == GDK_KEY_percent || // %
3511 (event->string[0] >= 40 && event->string[0] <= 57) || // ()+-*/.,0-9
3512 key == GDK_KEY_asciicircum || key == GDK_KEY_dead_circumflex || // ^
3513 key == GDK_KEY_X || key == GDK_KEY_x)) // Xx
3514 {
3515 if(key == GDK_KEY_dead_circumflex)
3516 bh->keys[bh->keys_cnt++] = '^';
3517 else
3518 bh->keys[bh->keys_cnt++] = event->string[0];
3519 gtk_widget_queue_draw(bh->popup_area);
3520 }
3521 else if(bh->keys_cnt > 0
3522 && (key == GDK_KEY_BackSpace || key == GDK_KEY_Delete))
3523 {
3524 bh->keys[--bh->keys_cnt] = 0;
3525 gtk_widget_queue_draw(bh->popup_area);
3526 }
3527 else if(bh->keys_cnt > 0 && bh->keys_cnt + 1 < 64
3528 && (key == GDK_KEY_Return))
3529 {
3530 // accept input
3531 bh->keys[bh->keys_cnt] = 0;
3532 // unnormalized input, user was typing this:
3533 const float old_value = dt_bauhaus_slider_get_val(GTK_WIDGET(w));
3534 const float new_value = dt_calculator_solve(old_value, bh->keys);
3535 if(isfinite(new_value)) dt_bauhaus_slider_set_val(GTK_WIDGET(w), new_value);
3536 bh->keys_cnt = 0;
3537 memset(bh->keys, 0, sizeof(bh->keys));
3539 }
3540 else if(key == GDK_KEY_Escape)
3541 {
3542 // discard input ands close popup
3543 bh->keys_cnt = 0;
3544 memset(bh->keys, 0, sizeof(bh->keys));
3546 }
3547 else
3548 return FALSE;
3549
3550 return TRUE;
3551 }
3553 {
3554 if(!g_utf8_validate(event->string, -1, NULL)) return FALSE;
3555 const gunichar c = g_utf8_get_char(event->string);
3556 const long int char_width = g_utf8_next_char(event->string) - event->string;
3557 if(bh->keys_cnt + 1 + char_width < 64 && g_unichar_isprint(c))
3558 {
3559 // only accept key input if still valid or editable?
3560 g_utf8_strncpy(bh->keys + bh->keys_cnt, event->string, 1);
3561 bh->keys_cnt += char_width;
3562 gtk_widget_queue_draw(bh->popup_area);
3563 }
3564 else if(bh->keys_cnt > 0
3565 && (key == GDK_KEY_BackSpace || key == GDK_KEY_Delete))
3566 {
3567 bh->keys_cnt
3568 -= (bh->keys + bh->keys_cnt)
3569 - g_utf8_prev_char(bh->keys + bh->keys_cnt);
3570 bh->keys[bh->keys_cnt] = 0;
3571 gtk_widget_queue_draw(bh->popup_area);
3572 }
3573 else if(bh->keys_cnt > 0 && bh->keys_cnt + 1 < 64
3574 && (key == GDK_KEY_Return))
3575 {
3576 // accept unique matches only for editable:
3577 if(w->data.combobox.editable)
3578 bh->end_mouse_y = FLT_MAX;
3579 else
3580 bh->end_mouse_y = 0;
3581 bh->keys[bh->keys_cnt] = 0;
3583 bh->keys_cnt = 0;
3584 memset(bh->keys, 0, sizeof(bh->keys));
3586 }
3587 else if(key == GDK_KEY_Escape)
3588 {
3589 // discard input and close popup
3590 bh->keys_cnt = 0;
3591 memset(bh->keys, 0, sizeof(bh->keys));
3593 }
3594 else if(key == GDK_KEY_Up)
3595 {
3597 }
3598 else if(key == GDK_KEY_Down)
3599 {
3601 }
3602 else if(key == GDK_KEY_Return)
3603 {
3604 // return pressed, but didn't type anything
3605 bh->end_mouse_y = -1; // negative will use currently highlighted instead.
3606 bh->keys[bh->keys_cnt] = 0;
3607 bh->keys_cnt = 0;
3608 memset(bh->keys, 0, sizeof(bh->keys));
3611 }
3612 else
3613 return FALSE;
3614 return TRUE;
3615 }
3616 default:
3617 return FALSE;
3618 }
3619}
3620
3621static gboolean dt_bauhaus_slider_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3622{
3623 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3625
3626 double event_x = event->x;
3627 double event_y = event->y;
3628 double main_width = 0.;
3629 _bh_active_region_t activated = _bh_get_active_region(widget, &event_x, &event_y, &main_width, NULL);
3630 w->bauhaus->mouse_x = event_x;
3631 w->bauhaus->mouse_y = event_y;
3632
3633 if(activated == BH_REGION_OUT)
3634 {
3636 return FALSE;
3637 }
3638
3639 gtk_widget_grab_focus(widget);
3641
3642 if(activated == BH_REGION_QUAD && w->quad_toggle)
3643 {
3645 return TRUE;
3646 }
3647 else if(activated == BH_REGION_MAIN)
3648 {
3649 if(event->button == 1)
3650 {
3651 if(event->type == GDK_2BUTTON_PRESS)
3652 {
3653 // double left click on the main region : reset value to default
3655 d->is_dragging = 0;
3656 }
3657 else
3658 {
3659 // single left click on main region : redraw the slider immediately
3660 // but without committing results to pipeline yet.
3661 if(event_y < w->bauhaus->line_height)
3662 {
3663 // single left click on the header name : do nothing (only give focus)
3664 d->is_dragging = 0;
3665 }
3666 else
3667 {
3668 // single left click on slider bar : set new value
3669 d->is_dragging = 1;
3670 dt_bauhaus_slider_set_normalized(w, event_x / main_width, FALSE);
3671 }
3672 }
3673 }
3674 else if(event->button == 3)
3675 {
3676 // right click : show accurate slider setting popup
3677 d->oldpos = d->pos;
3678 dt_bauhaus_show_popup(widget);
3679 }
3680 else if(event->button == 2)
3681 {
3682 // middle click : reset zoom range to soft min/max
3683 _slider_zoom_range(w, 0);
3684 }
3685 return TRUE;
3686 }
3687 return FALSE;
3688}
3689
3690static gboolean dt_bauhaus_slider_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3691{
3692 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3694
3696
3697 // is_dragging is set TRUE no matter what on button_press, except if it's a double click.
3698 // double click is the only event handled in button_press, otherwise we assume every event
3699 // is drag and drop, and handle the final drag coordinate here.
3700 if(d->is_dragging)
3701 {
3702 d->is_dragging = 0;
3703
3704 if(event->button == 1)
3705 {
3707 return TRUE;
3708 }
3709 }
3710 return FALSE;
3711}
3712
3713static gboolean dt_bauhaus_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
3714{
3715 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3717 _bh_active_region_t activated = BH_REGION_OUT; // = 0
3718
3719 if(d->is_dragging && event->state & GDK_BUTTON1_MASK)
3720 {
3721 double event_x = event->x;
3722 double event_y = event->y;
3723 double main_width;
3724 activated = _bh_get_active_region(widget, &event_x, &event_y, &main_width, NULL);
3725
3726 w->bauhaus->mouse_x = event_x;
3727 w->bauhaus->mouse_y = event_y;
3728 dt_bauhaus_slider_set_normalized(w, event_x / main_width, TRUE);
3729 }
3730
3731 return activated;
3732}
3733
3735{
3736 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3737 w->no_accels = TRUE;
3738}
3739
3741{
3742 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3743 w->no_module_list = TRUE;
3744}
3745
3747{
3748 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3750}
3751
3752
3753// clang-format off
3754// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3755// vim: shiftwidth=2 expandtab tabstop=2 cindent
3756// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3757// 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)
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:1627
static void dt_bauhaus_widget_accept(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2388
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:3350
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:1705
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:2471
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3343
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:1663
void dt_bauhaus_combobox_insert(GtkWidget *widget, const char *text, int pos)
Definition bauhaus.c:1926
static gboolean _widget_key_press(GtkWidget *widget, GdkEventKey *event)
Definition bauhaus.c:3169
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:1997
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:3475
static gboolean _widget_scroll(GtkWidget *widget, GdkEventScroll *event)
Definition bauhaus.c:3103
void dt_bauhaus_slider_clear_stops(GtkWidget *widget)
Definition bauhaus.c:2153
static gboolean dt_bauhaus_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition bauhaus.c:3713
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:2371
char * dt_bauhaus_slider_get_text(GtkWidget *w, float val)
Definition bauhaus.c:3294
void dt_bauhaus_combobox_set_selected_text_align(GtkWidget *widget, const dt_bauhaus_combobox_alignment_t text_align)
Definition bauhaus.c:1900
void dt_bauhaus_combobox_set_editable(GtkWidget *widget, int editable)
Definition bauhaus.c:1882
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:2958
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:3397
static void dt_bauhaus_window_show(GtkWidget *w, gpointer user_data)
Definition bauhaus.c:1040
static void _style_updated(GtkWidget *widget)
Definition bauhaus.c:1645
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:2007
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:2270
void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
Definition bauhaus.c:2161
#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:3280
void dt_bauhaus_combobox_set_text(GtkWidget *widget, const char *text)
Definition bauhaus.c:2017
static void dt_bauhaus_draw_indicator(struct dt_bauhaus_widget_t *w, float pos, cairo_t *cr, float wd)
Definition bauhaus.c:2198
void dt_bauhaus_combobox_entry_set_sensitive(GtkWidget *widget, int pos, gboolean sensitive)
Definition bauhaus.c:2143
gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
Definition bauhaus.c:2119
_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:1614
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:1638
void dt_bauhaus_combobox_from_widget(dt_bauhaus_t *bh, dt_bauhaus_widget_t *w, dt_gui_module_t *self)
Definition bauhaus.c:1720
static void _get_preferred_width(GtkWidget *widget, gint *minimum_size, gint *natural_size)
Definition bauhaus.c:2927
void dt_bauhaus_combobox_insert_separator_with_height(GtkWidget *widget, int pos, const float row_height_factor)
Definition bauhaus.c:1951
static gboolean dt_bauhaus_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
Definition bauhaus.c:3497
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:1562
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:3389
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:3740
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:3288
static dt_bauhaus_combobox_data_t * _combobox_data(GtkWidget *widget)
Definition bauhaus.c:1739
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:1931
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
static void _commit_combobox_value(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2026
void dt_bauhaus_widget_set_quad_toggle(GtkWidget *widget, int toggle)
Definition bauhaus.c:1572
static int _combobox_selectable_count(const dt_bauhaus_combobox_data_t *d)
Count selectable combobox entries.
Definition bauhaus.c:1757
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:3430
static void _dt_bauhaus_slider_set_with_raise(GtkWidget *widget, float pos, gboolean raise)
Definition bauhaus.c:3303
#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:3621
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:2061
void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
Definition bauhaus.c:1872
void dt_bauhaus_slider_set_step(GtkWidget *widget, float val)
Definition bauhaus.c:3357
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:1946
void dt_bauhaus_show_popup(GtkWidget *widget)
Definition bauhaus.c:2974
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:3746
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:1963
void dt_bauhaus_widget_set_quad_active(GtkWidget *widget, int active)
Definition bauhaus.c:1578
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:1846
gpointer dt_bauhaus_combobox_get_data(GtkWidget *widget)
Definition bauhaus.c:1987
static void _commit_slider_value(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:3437
void dt_bauhaus_combobox_add_populate_fct(GtkWidget *widget, void(*fct)(GtkWidget *w, void *module))
Definition bauhaus.c:1810
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:1858
static float _get_combobox_max_width(GtkWidget *widget)
Definition bauhaus.c:2696
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:1818
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:1893
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:1548
int dt_bauhaus_widget_get_quad_active(GtkWidget *widget)
Definition bauhaus.c:1595
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:3734
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:3690
static gchar * _build_label(const struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2466
const char * dt_bauhaus_combobox_get_text(GtkWidget *widget)
Definition bauhaus.c:1970
void dt_bauhaus_slider_set_val(GtkWidget *widget, float val)
Definition bauhaus.c:3337
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
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:3364
static gboolean _widget_draw(GtkWidget *widget, cairo_t *crf)
Definition bauhaus.c:2751
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:3084
void dt_bauhaus_widget_press_quad(GtkWidget *widget)
Definition bauhaus.c:1601
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:1794
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
#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:1632
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:1909
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_widget_set_quad_visibility(GtkWidget *widget, const gboolean visible)
Definition bauhaus.c:1588
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:2942
gboolean dt_bauhaus_combobox_set_from_text(GtkWidget *widget, const char *text)
Definition bauhaus.c:2100
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
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:2232
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:1774
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:1824
void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
Definition bauhaus.c:1554
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:1829
void dt_bauhaus_combobox_add_separator_with_height(GtkWidget *widget, const float row_height_factor)
Definition bauhaus.c:1863
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:3423
static gboolean dt_bauhaus_combobox_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition bauhaus.c:3221
void dt_bauhaus_combobox_add_aligned(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align)
Definition bauhaus.c:1841
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:310
dt_introspection_type_t
static int precision(double x, double adj)
Definition lens.c:2509
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