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
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{
128 const GtkStateFlags state = gtk_widget_get_state_flags(GTK_WIDGET(w));
131
132 // Deal with borders by extending margins because we don't care
135 w->margin->left += borders->left;
136 w->margin->right += borders->right;
137 w->margin->top += borders->top;
138 w->margin->bottom += borders->bottom;
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;
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;
213}
214
223{
224 GtkWidget *box_reference = (widget) ? widget : GTK_WIDGET(w);
225 GtkAllocation 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;
343
344 if(width) *width = main_width;
345 _translate_cursor(x, y, w);
346
347 // Check if we are within popup frame
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;
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;
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
462 const gint page_num = !IS_NULL_PTR(page) ? gtk_notebook_page_num(nb, page) : -1;
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
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.
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,
490 (void *)dt_widget_scroll_focus());
493 return G_SOURCE_REMOVE;
494 }
495
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));
503 return G_SOURCE_REMOVE;
504 }
505
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
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
539 GdkModifierType modifier, gpointer data)
540{
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);
561 if(previous_source > 0)
563
566 (GDestroyNotify)g_object_unref);
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 {
581 if(entry->sensitive)
582 {
583 new_pos = cur;
584 delta -= inc;
585 }
586 cur += inc;
587 }
588 d->hovered = 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,
677 const char *text,
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
692
693 if(css_font)
694 {
695 // Apply only style-related fields, NOT size
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.
727
728 // --- Build the layout ---
729 PangoLayout *layout = pango_layout_new(pc);
730 pango_layout_set_font_description(layout, 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 ---
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 {
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
760 {
761 cairo_save(cr);
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
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
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");
889 dt_bauhaus_widget_t *w = bh->current;
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");
897 dt_bauhaus_widget_t *w = bh->current;
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),
926
927 if(d->is_dragging)
928 {
929 // On dragging (when holding a click), we commit intermediate values to pipeline for "realtime" preview
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{
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");
954 dt_bauhaus_widget_t *w = bh->current;
955
956 if(w && (w->type == DT_BAUHAUS_COMBOBOX) && (event->button == 1)
957 && (event->time >= bh->opentime + delay) && !bh->hiding)
958 {
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");
976 dt_bauhaus_widget_t *w = bh->current;
977
978 if(event->button == 1)
979 {
981 && event->time < bh->opentime + delay)
982 {
983 // counts as double click, reset:
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 {
1056 }
1057 else
1058 {
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
1081 if(focus_source > 0)
1082 {
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
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 }
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,
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,
1120
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
1139 if(IS_NULL_PTR(root_window)) return;
1142
1144 gtk_style_context_lookup_color(ctx, "bauhaus_fg_insensitive", &bauhaus->color_fg_insensitive);
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
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
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
1190
1191 // Create a scratch Cairo surface to draw on
1193 cairo_t *cr = cairo_create(cst);
1194
1195 // Let Gtk create a fake label and grab its font description
1199
1201 PangoLayout *layout = pango_layout_new(context);
1202 pango_layout_set_text(layout, "em", -1);
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;
1219 g_object_unref(layout);
1220 cairo_destroy(cr);
1222
1225
1226 bauhaus->baseline_size = 5; // absolute size in Cairo unit
1227 bauhaus->border_width = 1; // absolute size in Cairo unit
1229}
1230
1232{
1234 bauhaus->keys_cnt = 0;
1235 bauhaus->current = 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.
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
1255
1256 // Needed for Wayland and Sway :
1259
1262
1263 // needed on macOS to avoid fullscreening the popup with newer GTK
1265
1271
1277
1280 g_object_set_data(area, "bauhaus", bauhaus);
1281
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);
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)"));
1303 dt_free(path);
1304 path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Decrease value (fine step)"));
1307 dt_free(path);
1308 path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Increase value (coarse step)"));
1311 dt_free(path);
1312 path = dt_accels_build_path(_("Darkroom/Controls/Sliders"), _("Decrease value (coarse step)"));
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
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);
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
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);
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)
1444
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, scope, plugin_name, 0, 0, _("Focuses the control"));
1536 g_object_set_data_full(G_OBJECT(widget), "accel-path",
1537 dt_accels_build_path("Darkroom/Modules", plugin_name), dt_free_gpointer);
1539 dt_free(scope);
1540 dt_free(plugin_name);
1541 }
1542
1544 }
1545}
1546
1548{
1549 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1550 return w->label;
1551}
1552
1560
1562{
1563 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1564 if(*w->label)
1565 fprintf(stderr, "[dt_bauhaus_widget_set_field] bauhaus label '%s' set before field (needs to be after)\n", w->label);
1566 w->field = field;
1568}
1569
1570// make this quad a toggle button:
1572{
1573 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1574 w->quad_toggle = toggle;
1575}
1576
1578{
1579 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1580 if (active)
1582 else
1585}
1586
1587void dt_bauhaus_widget_set_quad_visibility(GtkWidget *widget, const gboolean visible)
1588{
1589 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1590 w->show_quad = visible;
1592}
1593
1595{
1596 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1597 return (w->quad_paint_flags & CPF_ACTIVE) == CPF_ACTIVE;
1598}
1599
1601{
1602 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1603 if(w->quad_toggle)
1604 {
1606 }
1607 else
1609
1610 g_signal_emit_by_name(G_OBJECT(w), "quad-pressed");
1611}
1612
1614{
1615 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1616 gtk_widget_grab_focus(widget);
1617
1618 if(!w->quad_toggle)
1619 {
1623 }
1624}
1625
1627{
1628 return dt_bauhaus_slider_new_with_range(bh, self, 0.0, 1.0, 0.1, 0.5, 3);
1629}
1630
1632 float defval, int digits)
1633{
1634 return dt_bauhaus_slider_new_with_range_and_feedback(bh, self, min, max, step, defval, digits, 1);
1635}
1636
1638 float step, float defval, int digits, int feedback)
1639{
1641 return dt_bauhaus_slider_from_widget(bh, w,self, min, max, step, defval, digits, feedback);
1642}
1643
1644static void _style_updated(GtkWidget *widget)
1645{
1646 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
1648
1649 // gtk_widget_set_size_request is the minimal preferred, size.
1650 // it NEEDS to be defined and will be contextually adapted, possibly overriden by CSS.
1651 // Thing is Gtk CSS min-width in combination with hexpand is wonky so this is how it should be done.
1652 if(w->type == DT_BAUHAUS_COMBOBOX)
1654 else if(w->type == DT_BAUHAUS_SLIDER)
1656}
1657
1659 float step, float defval, int digits, int feedback)
1660{
1662 _bauhaus_widget_init(bh, w, self);
1664 d->min = d->soft_min = d->hard_min = min;
1665 d->max = d->soft_max = d->hard_max = max;
1666 d->step = step;
1667 // normalize default:
1668 d->defpos = defval;
1669 d->pos = (defval - min) / (max - min);
1670 d->oldpos = d->pos;
1671 d->digits = digits;
1672 d->format = "";
1673 d->factor = 1.0f;
1674 d->offset = 0.0f;
1675
1676 d->grad_cnt = 0;
1677 d->grad_col = NULL;
1678 d->grad_pos = NULL;
1679
1680 d->fill_feedback = feedback;
1681
1682 d->is_dragging = 0;
1683
1684 dt_gui_add_class(GTK_WIDGET(w), "bauhaus_slider");
1685
1689
1690 return GTK_WIDGET(w);
1691}
1692
1699
1701 int pos, GtkCallback callback, gpointer data, const char **texts)
1702{
1703 GtkWidget *combo = dt_bauhaus_combobox_new(bh, self);
1706 dt_bauhaus_combobox_set(combo, pos);
1708 if(callback) g_signal_connect(G_OBJECT(combo), "value-changed", G_CALLBACK(callback), data);
1709
1710 return combo;
1711}
1712
1713
1714
1716{
1718 _bauhaus_widget_init(bh, w, self);
1721 d->defpos = 0;
1722 d->active = -1;
1723 d->editable = 0;
1724 d->text_align = DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT;
1725 d->entries_ellipsis = PANGO_ELLIPSIZE_END;
1726 d->populate = NULL;
1727 d->text = NULL;
1728
1729 dt_gui_add_class(GTK_WIDGET(w), "bauhaus_combobox");
1730
1732}
1733
1735{
1736 if(IS_NULL_PTR(widget)) return NULL;
1737 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1738 if(w->type != DT_BAUHAUS_COMBOBOX) return NULL;
1740
1741 if(d->active >= d->entries->len) d->active = -1;
1742
1743 return d;
1744}
1745
1753{
1754 int count = 0;
1755 for(int i = 0; !IS_NULL_PTR(d) && i < d->entries->len; i++)
1756 {
1757 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
1758 if(!entry->is_separator) count++;
1759 }
1760 return count;
1761}
1762
1770 const gboolean allow_end)
1771{
1772 if(IS_NULL_PTR(d) || pos < 0) return -1;
1773
1774 int selectable_pos = 0;
1775 for(int i = 0; i < d->entries->len; i++)
1776 {
1777 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
1778 if(entry->is_separator) continue;
1779 if(selectable_pos == pos) return i;
1781 }
1782
1783 return (allow_end && selectable_pos == pos) ? (int)d->entries->len : -1;
1784}
1785
1790{
1791 if(IS_NULL_PTR(d) || entry_pos < 0 || entry_pos >= d->entries->len) return -1;
1792
1793 int selectable_pos = 0;
1794 for(int i = 0; i <= entry_pos; i++)
1795 {
1796 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
1797 if(entry->is_separator) continue;
1798 if(i == entry_pos) return selectable_pos;
1800 }
1801
1802 return -1;
1803}
1804
1806{
1807 if(IS_NULL_PTR(widget)) return;
1808 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1809 if(w->type == DT_BAUHAUS_COMBOBOX)
1810 w->data.combobox.populate = fct;
1811}
1812
1814{
1815 while(texts && *texts)
1817}
1818
1819void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
1820{
1822}
1823
1824void dt_bauhaus_combobox_add_with_tooltip(GtkWidget *widget, const char *text, const char *tooltip)
1825{
1826 if(IS_NULL_PTR(widget)) return;
1827 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1828 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1832 g_ptr_array_add(d->entries, entry);
1833 if(d->active < 0) d->active = (int)d->entries->len - 1;
1834}
1835
1837{
1838 dt_bauhaus_combobox_add_full(widget, text, align, NULL, NULL, TRUE);
1839}
1840
1842 gpointer data, void (free_func)(void *data), gboolean sensitive)
1843{
1844 if(IS_NULL_PTR(widget)) return;
1845 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1846 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1848 dt_bauhaus_combobox_entry_t *entry = new_combobox_entry(text, NULL, align, sensitive, data, free_func);
1849 g_ptr_array_add(d->entries, entry);
1850 if(d->active < 0) d->active = (int)d->entries->len - 1;
1851}
1852
1857
1858void dt_bauhaus_combobox_add_separator_with_height(GtkWidget *widget, const float row_height_factor)
1859{
1860 if(IS_NULL_PTR(widget)) return;
1861 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1862 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1864 g_ptr_array_add(d->entries, new_combobox_separator(row_height_factor));
1865}
1866
1868{
1869 if(IS_NULL_PTR(widget)) return;
1870 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1871 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1874}
1875
1876
1878{
1879 if(IS_NULL_PTR(widget)) return;
1880 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1881 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1883 d->editable = editable ? 1 : 0;
1884 if(d->editable && IS_NULL_PTR(d->text))
1886}
1887
1889{
1891
1892 return d ? d->editable : 0;
1893}
1894
1896{
1897 if(IS_NULL_PTR(widget)) return;
1898 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1899 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1901 d->text_align = text_align;
1902}
1903
1905{
1907 if(IS_NULL_PTR(d)) return;
1909
1910 if(entry_pos < 0) return;
1911
1912 // move active position up if removing anything before it
1913 // or when removing last position that is currently active.
1914 // this also sets active to -1 when removing the last remaining entry in a combobox.
1915 if(d->active > entry_pos || d->active == d->entries->len-1)
1916 d->active--;
1917
1919}
1920
1921void dt_bauhaus_combobox_insert(GtkWidget *widget, const char *text,int pos)
1922{
1924}
1925
1927 gpointer data, void (*free_func)(void *), int pos)
1928{
1929 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1930 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1932 const int entry_pos = _combobox_public_to_entry_pos(d, pos, TRUE);
1933 if(entry_pos < 0) return;
1934 g_ptr_array_insert(d->entries, entry_pos, new_combobox_entry(text, NULL, align, TRUE, data, free_func));
1935 if(d->active < 0)
1936 d->active = entry_pos;
1937 else if(entry_pos <= d->active)
1938 d->active++;
1939}
1940
1945
1946void dt_bauhaus_combobox_insert_separator_with_height(GtkWidget *widget, int pos, const float row_height_factor)
1947{
1948 if(IS_NULL_PTR(widget)) return;
1949 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1950 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1952 const int entry_pos = _combobox_public_to_entry_pos(d, pos, TRUE);
1953 if(entry_pos < 0) return;
1954 g_ptr_array_insert(d->entries, entry_pos, new_combobox_separator(row_height_factor));
1955 if(entry_pos <= d->active) d->active++;
1956}
1957
1959{
1961
1963}
1964
1966{
1968 if(IS_NULL_PTR(d)) return NULL;
1969
1970 if(d->active < 0)
1971 {
1972 return d->editable ? d->text : NULL;
1973 }
1974 else
1975 {
1976 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
1977 if(entry->is_separator) return NULL;
1978 return entry->label;
1979 }
1980}
1981
1983{
1985 if(IS_NULL_PTR(d) || d->active < 0) return NULL;
1986
1987 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
1988 if(entry->is_separator) return NULL;
1989 return entry->data;
1990}
1991
1993{
1994 if(IS_NULL_PTR(widget)) return;
1995 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1996 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1998 d->active = -1;
1999 g_ptr_array_set_size(d->entries, 0);
2000}
2001
2002const char *dt_bauhaus_combobox_get_entry(GtkWidget *widget, int pos)
2003{
2006 if(entry_pos < 0) return NULL;
2007
2008 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, entry_pos);
2009 return entry->label;
2010}
2011
2012void dt_bauhaus_combobox_set_text(GtkWidget *widget, const char *text)
2013{
2015 if(IS_NULL_PTR(d) || !d->editable) return;
2016
2018}
2019
2020
2022{
2023 // Announce the new value. Whoever listens decides what it costs: the history commit
2024 // batches the pipeline recompute it triggers, so emitting on every scroll step is fine
2025 // and this widget has no reason to hold anything back.
2026
2027 // Don't commit anything while a reset is in progress (reloading history, applying a
2028 // style, ...): those set widget values programmatically and must not reach history.
2029 if(dt_gui_widgets_suppressed()) return;
2030
2032 {
2035 else
2036 fprintf(stderr, "ERROR: %s - %s is set to use default callback but none is provided\n", w->module->name,
2037 w->label);
2038 }
2039 else
2040 {
2041 if(w->module)
2042 fprintf(stderr, "WARNING: %s - %s has an IOP module but doesn't use default callback\n", w->module->name,
2043 w->label);
2044 }
2045
2046 // We need te emit this signal inconditionnaly
2047 g_signal_emit_by_name(G_OBJECT(w), "value-changed");
2048}
2049
2056void _combobox_set(GtkWidget *widget, const int pos)
2057{
2058 if(IS_NULL_PTR(widget)) return;
2059 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2061 const int old_pos = d->active;
2062 const int new_pos = (d->entries) ? CLAMP(pos, -1, (int)d->entries->len - 1)
2063 : -1;
2064 if(new_pos >= 0)
2065 {
2066 // if the new position is a separator, don't change the active position but still redraw to update the visual selection
2067 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, new_pos);
2068 if(entry->is_separator) return;
2069 }
2070
2071 if(old_pos != new_pos)
2072 {
2073 d->active = new_pos;
2074
2075 if(w->bauhaus->current == w)
2077
2079
2081 }
2082}
2083
2084// Public API function, called from GUI init and update
2085void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
2086{
2089 const int public_pos = (selectable_count > 0) ? CLAMP(pos, -1, selectable_count - 1) : -1;
2091 _combobox_set(widget, entry_pos);
2092}
2093
2094
2095gboolean dt_bauhaus_combobox_set_from_text(GtkWidget *widget, const char *text)
2096{
2097 if(IS_NULL_PTR(text)) return FALSE;
2098
2100
2101 for(int i = 0; d && i < d->entries->len; i++)
2102 {
2103 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
2104 if(entry->is_separator) continue;
2105 if(!g_strcmp0(entry->label, text))
2106 {
2107 _combobox_set(widget, i);
2108 return TRUE;
2109 }
2110 }
2111 return FALSE;
2112}
2113
2115{
2117
2118 for(int i = 0; d && i < d->entries->len; i++)
2119 {
2120 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
2121 if(entry->is_separator) continue;
2122 if(GPOINTER_TO_INT(entry->data) == value)
2123 {
2124 _combobox_set(widget, i);
2125 return TRUE;
2126 }
2127 }
2128 return FALSE;
2129}
2130
2132{
2134
2135 return _combobox_entry_pos_to_public(d, d ? d->active : -1);
2136}
2137
2138void dt_bauhaus_combobox_entry_set_sensitive(GtkWidget *widget, int pos, gboolean sensitive)
2139{
2142 if(entry_pos < 0) return;
2143
2145 entry->sensitive = sensitive;
2146}
2147
2149{
2150 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2151 if(w->type != DT_BAUHAUS_SLIDER) return;
2153 d->grad_cnt = 0;
2154}
2155
2156void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
2157{
2158 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2159 if(w->type != DT_BAUHAUS_SLIDER) return;
2161
2162 if(IS_NULL_PTR(d->grad_col))
2163 {
2164 d->grad_col = malloc(DT_BAUHAUS_SLIDER_MAX_STOPS * sizeof(*d->grad_col));
2165 d->grad_pos = malloc(DT_BAUHAUS_SLIDER_MAX_STOPS * sizeof(*d->grad_pos));
2166 }
2167 // need to replace stop?
2168 for(int k = 0; k < d->grad_cnt; k++)
2169 {
2170 if(d->grad_pos[k] == stop)
2171 {
2172 d->grad_col[k][0] = r;
2173 d->grad_col[k][1] = g;
2174 d->grad_col[k][2] = b;
2175 return;
2176 }
2177 }
2178 // new stop:
2179 if(d->grad_cnt < DT_BAUHAUS_SLIDER_MAX_STOPS)
2180 {
2181 int k = d->grad_cnt++;
2182 d->grad_pos[k] = stop;
2183 d->grad_col[k][0] = r;
2184 d->grad_col[k][1] = g;
2185 d->grad_col[k][2] = b;
2186 }
2187 else
2188 {
2189 fprintf(stderr, "[bauhaus_slider_set_stop] only %d stops allowed.\n", DT_BAUHAUS_SLIDER_MAX_STOPS);
2190 }
2191}
2192
2193static void dt_bauhaus_draw_indicator(struct dt_bauhaus_widget_t *w, float pos, cairo_t *cr, float wd)
2194{
2195 // draw scale indicator (the tiny triangle)
2196 const float size = w->bauhaus->marker_size;
2198 const float horizontal_position = pos * wd;
2199
2200 cairo_save(cr);
2202
2204 const gboolean has_colored_background = d->grad_cnt > 0;
2205
2206 if(!has_colored_background && d->fill_feedback)
2207 {
2208 // Plain indicator (regular sliders)
2209 cairo_arc(cr, 0, 0, size / 2., 0, M_PI * 2);
2210 set_color(cr, w->bauhaus->color_fg);
2211 cairo_set_line_width(cr, 0);
2212 cairo_fill(cr);
2213 }
2214 else
2215 {
2216 // Hollow indicator to keep the slider background visible through the marker.
2217 const float border = (size - w->bauhaus->baseline_size) / 2.;
2218 set_color(cr, w->bauhaus->color_fg);
2219 cairo_set_line_width(cr, border);
2220 const float radius = size / 2. - border / 2.;
2221 cairo_arc(cr, 0, 0, radius, 0, M_PI * 2);
2222 cairo_stroke(cr);
2223 }
2224 cairo_restore(cr);
2225}
2226
2227static void dt_bauhaus_draw_quad(struct dt_bauhaus_widget_t *w, cairo_t *cr, const double x, const double y)
2228{
2229 if(!w->show_quad) return;
2230
2231 cairo_save(cr);
2232 if(w->quad_paint)
2233 {
2234 // draw color picker
2235 w->quad_paint(cr, x, y,
2236 w->bauhaus->quad_width, // width
2237 w->bauhaus->quad_width, // height
2239 }
2240 else if(w->type == DT_BAUHAUS_COMBOBOX)
2241 {
2242 // draw combobox chevron
2243 // Inset the chevron from the right edge of the (now flush) quad box so it
2244 // doesn't crowd or overflow the widget border. The quad box itself is placed
2245 // identically to the slider's, so this keeps the chevron in line with slider
2246 // quad icons when widgets are stacked.
2247 // FIXME: Why do we need an extra half-pixel shift to the left for pixel-correctness ?
2248 cairo_translate(cr, x + w->bauhaus->quad_width / 2. - 0.5, y + w->bauhaus->line_height / 2.);
2249 const float r = w->bauhaus->quad_width * .2f;
2250 cairo_move_to(cr, -r, -r * .5f);
2251 cairo_line_to(cr, 0, r * .5f);
2252 cairo_line_to(cr, r, -r * .5f);
2253 cairo_stroke(cr);
2254 }
2255 cairo_restore(cr);
2256}
2257
2266{
2267 // draw line for orientation in slider
2268 cairo_save(cr);
2270 const gboolean has_colored_background = d->grad_cnt > 0;
2271 const float baseline_top = w->bauhaus->line_height + INNER_PADDING;
2272 const float baseline_height = w->bauhaus->baseline_size;
2273 const gboolean is_sensitive = gtk_widget_is_sensitive(GTK_WIDGET(w));
2274
2275 // the background of the line
2276 // Note: to ensure the indicator is not clipped on the left side,
2277 // we push the whole slider of an indicator radius to the right.
2278 // Issue is that leaves a dent on the baseline, so the trick
2279 // is to make it overlap back by a radius on the left.
2280 const double x_origin = -w->bauhaus->marker_size / 3.;
2281 // The left edge was pushed by x_origin to overlap the indicator's leftmost
2282 // resting position. Extend the width by the same amount so the right edge
2283 // stays at `width` (flush with the right-aligned numeric value) instead of
2284 // retreating by |x_origin| and letting the value overhang the baseline.
2285 const double baseline_width = width - x_origin;
2286 // Make sure we use integer coordinates to limit anti-aliasing blur
2287 cairo_rectangle(cr, round(x_origin), round(baseline_top),
2289
2292 {
2293 // gradient line as used in some modules for hue, saturation, lightness
2294 const double zoom = (d->max - d->min) / (d->hard_max - d->hard_min);
2295 const double offset = (d->min - d->hard_min) / (d->hard_max - d->hard_min);
2297 for(int k = 0; k < d->grad_cnt; k++)
2298 cairo_pattern_add_color_stop_rgba(gradient, (d->grad_pos[k] - offset) / zoom,
2299 d->grad_col[k][0], d->grad_col[k][1], d->grad_col[k][2], 0.4f);
2301 }
2302 else
2303 {
2304 // regular baseline
2305 set_color(cr, w->bauhaus->color_bg);
2306 }
2307
2308 cairo_fill(cr);
2309
2310
2311 if(w->bauhaus->border_width > 0)
2312 {
2313 // Draw the border on top
2314 // We need to recess the coordinates by half a line-width for perfect border matching
2315 const double line_width = 1.;
2316 cairo_rectangle(cr, round(x_origin) + line_width / 2., round(baseline_top) + line_width / 2.,
2320 cairo_stroke(cr);
2321 }
2322
2324
2325 // get the reference of the slider aka the position of the 0 value
2326 float origin = fmaxf(fminf((d->factor > 0 ? -d->min - d->offset/d->factor
2327 : d->max + d->offset/d->factor)
2328 / (d->max - d->min),
2329 1.0f) * width,
2330 0.f);
2331
2332 // have a `fill ratio feel' from zero to current position
2333 if(!has_colored_background && d->fill_feedback && is_sensitive)
2334 {
2335 set_color(cr, w->bauhaus->color_value);
2336
2337 const float position = d->pos * width;
2338 const float delta = position - origin;
2339
2340 // origin = 0 means start on left, but our left is shifted to the right,
2341 // so offset it. (see x_origin)
2342 if(origin == 0.f)
2343 cairo_rectangle(cr, x_origin, baseline_top, delta - x_origin, baseline_height);
2344 else
2346
2347 cairo_fill(cr);
2348 }
2349
2350 // draw the 0 reference graduation if it's different than the bounds of the slider
2351 // If the max of the slider is 360, it is likely an absolute hue slider in degrees
2352 // a zero in periodic stuff has not much meaning so we skip it.
2353 if(d->hard_max != 360.0f && is_sensitive)
2354 {
2355 set_color(cr, w->bauhaus->color_fg);
2356
2357 // If we are to put the 0 reference on the "left", this time we need to account for
2358 // actual leftmost resting position of the indicator, not the starting point of the baseline.
2360 cairo_fill(cr);
2361 }
2362
2363 cairo_restore(cr);
2364}
2365
2367{
2368 switch(w->type)
2369 {
2371 break;
2372 case DT_BAUHAUS_SLIDER:
2373 {
2376 }
2377 break;
2378 default:
2379 break;
2380 }
2381}
2382
2384{
2385 GtkWidget *widget = GTK_WIDGET(w);
2386
2387 switch(w->type)
2388 {
2390 {
2392
2393 if(d->editable && w->bauhaus->keys_cnt > 0)
2394 {
2395 // combobox is editable and we have text, assume it is a custom input
2398 _combobox_set(widget, -1); // select custom entry
2399
2400#if DEBUG
2401 fprintf(stdout, "combobox went the custom path\n");
2402#endif
2403 }
2404 else
2405 {
2406 if(w->bauhaus->keys_cnt > 0)
2407 {
2408 // combobox is not editable, but we have text. Assume user wanted to init a selection from keyboard.
2409 // find the closest match by looking for the entry having the maximum number
2410 // of characters in common with the user input.
2411 gchar *keys = g_utf8_casefold(w->bauhaus->keys, -1);
2412 int match = -1;
2413 int matches = 0;
2414
2415 for(int j = 0; j < d->entries->len; j++)
2416 {
2417 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, j);
2418 gchar *text_cmp = g_utf8_casefold(entry->label, -1);
2419 if(entry->sensitive && !strncmp(text_cmp, keys, w->bauhaus->keys_cnt))
2420 {
2421 matches++;
2422 match = j;
2423 }
2425 }
2426 dt_free(keys);
2427
2428 // Accept result only if exactly one match was found. Anything else is ambiguous
2429 if(matches == 1)
2430 _combobox_set(widget, match);
2431 }
2432 else {
2433 // Active entry (below cursor or scrolled)
2434 _combobox_set(widget, d->hovered);
2435 }
2436 }
2437
2438 break;
2439 }
2440 case DT_BAUHAUS_SLIDER:
2441 {
2442 // The slider popup uses the quadratic magnifier for accurate setting.
2443 // We need extra conversions from cursor coordinates to set it right.
2444 // This needs to be kept in sync with popup_draw()
2446
2447 // This is needed to accept the change.
2448 // d->pos is soft-updated with corrected coordinates for drawing purposes only in popup_redraw().
2449 // We need to reset it to the original value temporarily, and request a proper setting
2450 // with value-changed signal through dt_bauhaus_slider_set_normalized()
2451 const float value = d->pos;
2452 d->pos = d->oldpos;
2454 break;
2455 }
2456 default:
2457 break;
2458 }
2459}
2460
2461static gchar *_build_label(const struct dt_bauhaus_widget_t *w)
2462{
2463 return g_strdup(w->label);
2464}
2465
2466static gboolean dt_bauhaus_popup_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
2467{
2468 // Popups belong to the app, not to the bauhaus widget. That's confusing.
2469 // Also the *widget param here is the popup container, still not the bauhaus widget. Even more confusing.
2470 // This is the actual parent bauhaus widget :
2471 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
2472 dt_bauhaus_widget_t *w = bh->current;
2473
2474 // get area properties
2475 GtkAllocation allocation;
2476 gtk_widget_get_allocation(widget, &allocation);
2477 cairo_surface_t *cst = dt_cairo_surface_create_at_scale(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height, dt_widget_ppd());
2478 cairo_t *cr = cairo_create(cst);
2480
2481 // look up some colors once
2488
2489 GdkRGBA *fg_color = default_color_assign();
2490 GdkRGBA *bg_color;
2491 GtkStateFlags state = gtk_widget_get_state_flags(widget);
2492
2493 gtk_style_context_get(context, state, "background-color", &bg_color, NULL);
2495
2496 // draw background
2497 gtk_render_background(context, cr, 0, 0, allocation.width, allocation.height);
2498 gtk_render_frame(context, cr, 0, 0, allocation.width, allocation.height);
2499
2500 const double main_height = _widget_get_main_height(w, widget);
2501 double total_width = 0.f;
2503
2504 // translate to account for the widget spacing
2505 cairo_translate(cr, w->padding->left, w->padding->top);
2506
2507 // switch on bauhaus widget type (so we only need one static window)
2508 switch(w->type)
2509 {
2510 case DT_BAUHAUS_SLIDER:
2511 {
2513 const double slider_cursor_radius = 0.5 * w->bauhaus->marker_size;
2515 cairo_save(cr);
2517 set_color(cr, *fg_color);
2518
2519 float scale = _bh_slider_get_scale(w);
2520 const int num_scales = 1.f / scale;
2521 const float bottom_baseline = _get_slider_bar_height(w);
2522
2523 for(int k = 0; k < num_scales; k++)
2524 {
2525 const float off = k * scale - d->oldpos;
2526 GdkRGBA fg_copy = *fg_color;
2527 fg_copy.alpha = scale / fabsf(off);
2528 set_color(cr, fg_copy);
2529 draw_slider_line(cr, d->oldpos, off, scale, main_width, main_height, bottom_baseline, 1);
2530 cairo_stroke(cr);
2531 }
2532 cairo_restore(cr);
2533
2534 // Get the x offset compared to d->oldpos accounting for vertical position magnification
2535 const double mouse_off = d->pos - d->oldpos;
2536
2537 cairo_save(cr);
2539
2540 // Draw the baseline with fill feedback if any (needs the new d->pos set before)
2542
2543 // draw mouse over indicator line
2546 cairo_stroke(cr);
2547
2548 // draw indicator
2550
2551 cairo_restore(cr);
2552
2553 // draw numerical value:
2554 cairo_save(cr);
2556
2557 float value_width = 0.f;
2559 GdkRectangle bounding_value = { .x = 0.,
2560 .y = 0.,
2561 .width = text_width,
2562 .height = w->bauhaus->line_height };
2563 // Display user keyboard input if any, otherwise the current value
2564 show_pango_text(w, context, cr, &bounding_value,
2565 (w->bauhaus->keys_cnt) ? w->bauhaus->keys : text, BH_ALIGN_RIGHT,
2567 dt_free(text);
2568
2569 // label on top of marker:
2570 gchar *label_text = _build_label(w);
2572 GdkRectangle bounding_label = { .x = 0.,
2573 .y = 0.,
2574 .width = label_width,
2575 .height = w->bauhaus->line_height };
2576 set_color(cr, *fg_color);
2580
2581 cairo_restore(cr);
2582 }
2583 break;
2585 {
2587
2588 // User keyboard input goes first
2589 double popup_y = 0.;
2590 if(w->bauhaus->keys_cnt > 0)
2591 {
2592 cairo_save(cr);
2594 const double query_height = _bh_get_row_height(w);
2595 GdkRectangle query_label = { .x = 0.,
2596 .y = popup_y,
2597 .width = main_width,
2598 .height = query_height };
2602 cairo_restore(cr);
2603 }
2604
2605 cairo_save(cr);
2606 gchar *keys = g_utf8_casefold(w->bauhaus->keys, -1);
2607 for(int j = 0; j < d->entries->len; j++)
2608 {
2609 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, j);
2610 const double entry_height = _bh_get_combobox_entry_height(w, entry);
2611 if(entry->is_separator)
2612 {
2613 if(w->bauhaus->keys_cnt == 0)
2614 {
2615 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.);
2616 cairo_move_to(cr, 0., popup_y + 0.5 * entry_height);
2618 cairo_set_line_width(cr, 1.);
2619 cairo_stroke(cr);
2620 }
2622 continue;
2623 }
2624
2625 gchar *text_cmp = g_utf8_casefold(entry->label, -1);
2626 if(!strncmp(text_cmp, keys, w->bauhaus->keys_cnt))
2627 {
2628 // If user typed some keys, display matching entries only
2629
2630 // The GTK state flag is applied to the whole widget,
2631 // we need to dispatch it individually to each entry
2632 if(!entry->sensitive)
2633 {
2636 }
2637 else if(j == d->active)
2638 {
2641 }
2642 else if(j == d->hovered)
2643 {
2646 }
2647 else
2648 {
2649 set_color(cr, text_color);
2651 }
2652
2653 GdkRectangle bounding_label = { .x = 0.,
2654 .y = popup_y,
2655 .width = main_width,
2656 .height = entry_height };
2657#if DEBUG
2659 cairo_set_line_width(cr, 2);
2660 cairo_stroke(cr);
2661#endif
2663 d->entries_ellipsis, bg_color, NULL, NULL, state);
2664 }
2665
2668 }
2669 cairo_restore(cr);
2670 dt_free(keys);
2671 }
2672 break;
2673 default:
2674 // yell
2675 break;
2676 }
2677
2678 cairo_destroy(cr);
2679 cairo_set_source_surface(crf, cst, 0, 0);
2682
2685
2686 return TRUE;
2687}
2688
2689
2690// Get the maximum width of a full combobox without ellipsization
2692{
2693 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2696 const GtkStateFlags state = gtk_widget_get_state_flags(widget);
2697
2699 cairo_t *cr = cairo_create(cst);
2700
2701 float width = 0.f;
2702
2703 // Get chevron width + padding if any
2704 if(w->show_quad)
2706
2707 float label_width = 0.f;
2708 GdkRectangle bounding_label = { .x = 0.,
2709 .y = 0.,
2710 .width = 999,
2711 .height = 999 };
2712
2715
2717
2718 // Get width of the longest entry
2719 float max_entry = 0.f;
2720 for(int i = 0; i < d->entries->len; i++)
2721 {
2722 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, i);
2723
2724 // The value is shown right-aligned, ellipsized if needed.
2725 GdkRectangle bounding_value = { .x = 0,
2726 .y = 0.,
2727 .width = 999,
2728 .height = 999 };
2729 float entry_label_width = 0.f;
2730
2733
2735 }
2736
2737 width += max_entry;
2738 width += w->margin->left + w->margin->right + w->padding->left + w->padding->right;
2739
2740 cairo_destroy(cr);
2742
2743 return width;
2744}
2745
2746static gboolean _widget_draw(GtkWidget *widget, cairo_t *crf)
2747{
2748 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2750
2751 // Get current Gtk allocation
2752 GtkAllocation allocation;
2753 gtk_widget_get_allocation(widget, &allocation);
2754
2755 if(w->type == DT_BAUHAUS_COMBOBOX)
2756 allocation.height = _get_combobox_height(widget);
2757 else if(w->type == DT_BAUHAUS_SLIDER)
2758 allocation.height = _get_slider_height(widget);
2759
2760 if(w->type == DT_BAUHAUS_COMBOBOX && !w->expand)
2761 {
2762 // For comboboxes that are not set to hexpand, limit the width span to what's needed
2763 // to display the internal text, aka prevent them to grow out of proportions
2764 float max_width = _get_combobox_max_width(widget);
2765 if(max_width < allocation.width)
2766 allocation.width = ceilf(max_width);
2767 }
2768
2769 // Force allocate to our requirements. Yes, it's ugly.
2770 gtk_widget_size_allocate(widget, &allocation);
2771
2772 cairo_surface_t *cst = dt_cairo_surface_create_at_scale(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height, dt_widget_ppd());
2773 cairo_t *cr = cairo_create(cst);
2774
2775 GdkRGBA *text_color = default_color_assign();
2776 GdkRGBA *value_color = default_color_assign();
2778 GtkStateFlags state = gtk_widget_get_state_flags(widget);
2779 if(gtk_widget_has_focus(widget))
2781
2782 gtk_style_context_save(context);
2785
2788
2789 // Compute internal widget sizes, that is allocation minus margins
2791 // Note : box is the border box in CSS conventions
2792 const float box_width = allocation.width - w->margin->left - w->margin->right;
2793 const float box_height = allocation.height - w->margin->top - w->margin->bottom;
2794
2795 // Paint background and borders first
2796 gtk_render_background(context, crf, w->margin->left, w->margin->top, box_width, box_height);
2797 gtk_render_frame(context, crf, w->margin->left, w->margin->top, box_width, box_height);
2798
2799 // Translate Cairo coordinates to account for the widget spacing
2800 cairo_translate(cr,
2801 w->margin->left + w->padding->left,
2802 w->margin->top + w->padding->top);
2803
2805 const float inner_height = _widget_get_main_height(w, NULL);
2806
2807 // draw type specific content:
2808 cairo_save(cr);
2809 set_color(cr, *text_color);
2810 cairo_set_line_width(cr, 1.0);
2811 switch(w->type)
2812 {
2814 {
2815 // draw label and quad area at right end
2816 // Gap must match the one reserved by _widget_get_main_width() (2*INNER_PADDING)
2817 // and used by the slider, so combobox quads stay flush and vertically aligned
2818 // with slider quad icons when stacked.
2819 if(w->show_quad)
2821
2823 const PangoEllipsizeMode combo_ellipsis = d->entries_ellipsis;
2824
2825 float label_width = 0.f;
2826 float label_height = 0.f;
2827
2828 GdkRectangle bounding_label = { .x = 0.,
2829 .y = 0.,
2830 .width = available_width,
2831 .height = inner_height };
2832 set_color(cr, *text_color);
2835
2836 // The value is shown right-aligned, ellipsized if needed.
2837 gchar *text = d->text;
2838 if(d->active >= 0 && d->active < d->entries->len)
2839 {
2840 const dt_bauhaus_combobox_entry_t *entry = g_ptr_array_index(d->entries, d->active);
2841 text = entry->label;
2842 }
2844 .y = 0.,
2846 .height = inner_height };
2849 NULL, NULL, state);
2850
2851 break;
2852 }
2853 case DT_BAUHAUS_SLIDER:
2854 {
2855 const double slider_cursor_radius = 0.5 * w->bauhaus->marker_size;
2857
2858 // line for orientation
2859 cairo_save(cr);
2862 cairo_restore(cr);
2863
2864 // Paint the non-active quad icon with some transparency, because
2865 // icons are bolder than the neighbouring text and appear brighter.
2866 cairo_save(cr);
2867 if(!(w->quad_paint_flags & CPF_ACTIVE))
2868 cairo_set_source_rgba(cr, text_color->red, text_color->green, text_color->blue, text_color->alpha * 0.7);
2869
2871 cairo_restore(cr);
2872
2873 float value_width = 0;
2874 if(gtk_widget_is_sensitive(widget))
2875 {
2876 cairo_save(cr);
2879 cairo_restore(cr);
2880
2881 char *text = dt_bauhaus_slider_get_text(widget, dt_bauhaus_slider_get(widget));
2882 GdkRectangle bounding_value = { .x = 0.,
2883 .y = 0.,
2884 .width = text_width,
2885 .height = w->bauhaus->line_height };
2889 dt_free(text);
2890 }
2891
2892 // label on top of marker:
2893 gchar *label_text = _build_label(w);
2895 GdkRectangle bounding_label = { .x = 0.,
2896 .y = 0.,
2897 .width = label_width,
2898 .height = w->bauhaus->line_height };
2899 set_color(cr, *text_color);
2903 }
2904 break;
2905 default:
2906 break;
2907 }
2908 cairo_restore(cr);
2909 cairo_destroy(cr);
2910 cairo_set_source_surface(crf, cst, 0, 0);
2914
2918
2919 return TRUE;
2920}
2921
2923{
2924 // Nothing clever here : preferred size is the size of the container.
2925 // If user is not happy with that, it's his responsibility to resize sidebars.
2926 // Minimum size is left at 0 so the widget can shrink with its parent;
2927 // leaving it unset would read as uninitialized stack garbage and could
2928 // clamp natural_size up to a stale value, preventing the widget from
2929 // growing again when the parent grows.
2930 *minimum_size = 0;
2931
2932 // The host knows which panel this sits in and how wide it is; -1 means it has no opinion.
2933 const gint host_width = dt_widget_natural_width(widget);
2935}
2936
2938{
2939 // Resolve the height from the same getters used everywhere else so it is
2940 // always correct at measure time, regardless of when "style-updated" fires.
2941 // Relying solely on the size-request set in _style_updated is not enough:
2942 // when the widget lives inside a freshly popped-up GtkMenu, the menu measures
2943 // its children before their style context is resolved, so it would size
2944 // itself from a stale/zero height and end up showing scroll arrows.
2945 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
2947 const gint height = (w->type == DT_BAUHAUS_COMBOBOX) ? (gint)ceil(_get_combobox_height(widget))
2948 : (gint)ceil(_get_slider_height(widget));
2951}
2952
2954{
2955 if(bh->current)
2956 {
2957 gtk_grab_remove(bh->popup_area);
2958 gtk_widget_hide(bh->popup_window);
2960
2961 // Give back focus to the attached widget
2964
2965 bh->current = NULL;
2966 }
2967}
2968
2970{
2971 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
2973
2974 // Re-parent onto the window this widget actually lives in, which is not necessarily the
2975 // application's main window: modules shown in a modal (the export dialog) have their own
2976 // toplevel. The popup's position is computed relative to that same window below, so the two
2977 // must agree or the popup lands outside its parent.
2981 w->bauhaus->current = w;
2982 w->bauhaus->keys_cnt = 0;
2983 memset(w->bauhaus->keys, 0, sizeof(w->bauhaus->keys));
2984 w->bauhaus->change_active = 0;
2985 w->bauhaus->mouse_line_distance = 0.0f;
2986 w->bauhaus->hiding = FALSE;
2987
2988 // Make sure all relevant widgets exist
2990 gtk_widget_realize(widget);
2992
2993 GtkAllocation tmp;
2995 int width = MAX(tmp.width, 1);
2996 int height = MAX(tmp.height, 1);
2997
2998 switch(w->bauhaus->current->type)
2999 {
3000 case DT_BAUHAUS_SLIDER:
3001 {
3002 // Slider popup: make it square
3004 d->oldpos = d->pos;
3005 d->is_dragging = FALSE;
3006 height = tmp.width;
3007 break;
3008 }
3010 {
3012 break;
3013 }
3014 default:
3015 {
3016 fprintf(stderr, "[dt_bauhaus_show_popup] The bauhaus widget has an unknown type\n");
3017 break;
3018 }
3019 }
3020
3021 /* Bind to CSS rules from parent widget */
3023 gtk_style_context_add_class(context, "dt_bauhaus_popup");
3025 #ifdef GDK_WINDOWING_WAYLAND
3029 }
3030 #endif
3031
3032 // The popup window stays transient for the main application window, so the anchor
3033 // rectangle needs to remain expressed in that coordinate space even when the popup is
3034 // logically attached to an enclosing popover on Wayland.
3035 gint wx = 0, wy = 0;
3038 wx += w->margin->left;
3039 wy += w->margin->top;
3040
3041 gint wwx = 0, wwy = 0;
3042 // The widget's OWN toplevel, not the host's main window: correct for popups raised from a
3043 // secondary window, and it removes the last reason to ask the host which window is primary.
3047 if(main_window) gdk_window_get_origin(main_window, &wwx, &wwy);
3048
3049 GdkRectangle anchor = {
3050 .x = wx - wwx,
3051 .y = wy - wwy,
3052 .width = MAX(tmp.width, 1),
3053 .height = MAX(tmp.height, 1)
3054 };
3055
3056 // Set desired size, but it's more a guide than a rule.
3059
3060 // Need to call resize to actually change something
3062
3064 if(IS_NULL_PTR(window))
3065 {
3068 return;
3069 }
3070
3071 // For Wayland (and supposed to work on X11 too) and Gtk 3.24 this is how you do it
3073 GDK_ANCHOR_SLIDE, 0, 0);
3074
3077}
3078
3079static void _slider_add_step(GtkWidget *widget, float delta, guint state)
3080{
3081 if(delta == 0.f) return;
3082 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3084
3087 else if(dt_modifier_is(state, GDK_SHIFT_MASK)) delta *= 5.f;
3088
3089 // Ensure the requested delta is at least visible given current number of digits in display
3090 const float min_visible = 1.f / (fabsf(d->factor) * ipow(10, d->digits));
3091 if(fabsf(delta) < min_visible)
3093
3094 const float value = dt_bauhaus_slider_get(widget);
3096}
3097
3098static gboolean _widget_scroll(GtkWidget *widget, GdkEventScroll *event)
3099{
3100 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3101 const gboolean popup_captured = (w->bauhaus->current == w);
3102 const gboolean scroll_captured = (dt_widget_scroll_focus() == widget);
3103 const gboolean key_captured = gtk_widget_has_focus(widget);
3104 const gboolean smooth = (event->direction == GDK_SCROLL_SMOOTH);
3105
3106 // - native Gtk focus (keyboard), that takes precedence and records all keyboard events,
3107 // - custom scroll focus (mouse wheel), that should not overlap with vertical scrolling.
3108 // Scroll focus is a subset of Gtk focus, aka we can lose scroll focus while we have Gtk focus,
3109 // but delayed history commits may transiently steal Gtk focus while the
3110 // pointer never leaves the widget. In that case, keep trusting the explicit
3111 // scroll focus until leave-notify releases it.
3112 // We also extend widget focus with the popup window focus if it is captured
3113 // by the current widget.
3115 return FALSE;
3116
3117 // Keep ownership of the whole touchpad gesture sequence.
3119
3120 int delta_x = 0;
3121 int delta_y = 0;
3122
3124 {
3125 // Important: for touchpad scroll, sub-unit deltas and stop events must not
3126 // propagate to the parent scrolled window, otherwise the same gesture gets
3127 // re-accumulated there.
3128 return smooth;
3129 }
3130
3131 const gboolean vscroll = delta_y != 0 && abs(delta_y) > abs(delta_x);
3132 const gboolean hscroll = delta_x != 0 && abs(delta_x) > abs(delta_y);
3133
3134 if(w->type == DT_BAUHAUS_SLIDER)
3135 {
3136 if(hscroll)
3137 {
3138 // inconditionnaly record horizontal scroll on slider
3139 _slider_add_step(widget, delta_x, event->state);
3140 return TRUE;
3141 }
3142 else if(vscroll && dt_widget_scroll_focus())
3143 {
3144 // convert vertical scrolling to horizontal only if we have the scroll focus
3145 _slider_add_step(widget, -delta_y, event->state);
3146 return TRUE;
3147 }
3148
3149 // Diagonal smooth gestures: consume them if captured, so they do not leak.
3150 return smooth;
3151 }
3152 else
3153 {
3155 {
3157 return TRUE;
3158 }
3159
3160 return smooth;
3161 }
3162}
3163
3164static gboolean _widget_key_press(GtkWidget *widget, GdkEventKey *event)
3165{
3166 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3167 guint key = dt_keys_mainpad_alternatives(event->keyval);
3168
3169 if(w->type == DT_BAUHAUS_SLIDER)
3170 {
3171 switch(key)
3172 {
3173 case GDK_KEY_Right:
3174 _slider_add_step(widget, 1, event->state);
3175 return TRUE;
3176
3177 case GDK_KEY_Left:
3178 _slider_add_step(widget, -1, event->state);
3179 return TRUE;
3180
3181 case GDK_KEY_Insert:
3182 if(w->quad_toggle)
3183 {
3186 return TRUE;
3187 }
3188
3189 default:
3190 return FALSE;
3191 }
3192 }
3193 else if(w->type == DT_BAUHAUS_COMBOBOX)
3194 {
3195 switch(key)
3196 {
3197 case GDK_KEY_Return:
3198 dt_bauhaus_show_popup(widget);
3199 return TRUE;
3200
3201 case GDK_KEY_Insert:
3202 if(w->quad_toggle)
3203 {
3206 return TRUE;
3207 }
3208
3209 default:
3210 return FALSE;
3211 }
3212 }
3213 return FALSE;
3214}
3215
3216static gboolean dt_bauhaus_combobox_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3217{
3218 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3219
3220 double event_x = event->x;
3221 double event_y = event->y;
3222 double width;
3224
3226
3228 {
3230 return FALSE;
3231 }
3232
3233 gtk_widget_grab_focus(widget);
3235
3237 {
3239 return TRUE;
3240 }
3241 else
3242 {
3243 // If no quad toggle, treat the whole widget as unit pack.
3244 if(event->button == 3)
3245 {
3246 w->bauhaus->mouse_x = event_x;
3247 w->bauhaus->mouse_y = event_y;
3248 dt_bauhaus_show_popup(widget);
3249 return TRUE;
3250 }
3251 else if(event->button == 1)
3252 {
3253 // reset to default.
3254 if(event->type == GDK_2BUTTON_PRESS)
3255 {
3256 // never called, as we popup the other window under your cursor before.
3257 // (except in weird corner cases where the popup is under the -1st entry
3258 dt_bauhaus_combobox_set(widget, d->defpos);
3260 }
3261 else
3262 {
3263 // single click, show options
3264 w->bauhaus->opentime = event->time;
3265 w->bauhaus->mouse_x = event_x;
3266 w->bauhaus->mouse_y = event_y;
3267 dt_bauhaus_show_popup(widget);
3268 }
3269 return TRUE;
3270 }
3271 }
3272 return FALSE;
3273}
3274
3276{
3277 // first cast to bh widget, to check that type:
3278 const struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3280 return d->min + d->pos * (d->max - d->min);
3281}
3282
3284{
3285 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(widget)->data.slider;
3286 return dt_bauhaus_slider_get(widget) * d->factor + d->offset;
3287}
3288
3290{
3291 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(w)->data.slider;
3292 if((d->hard_max * d->factor + d->offset)*(d->hard_min * d->factor + d->offset) < 0)
3293 return g_strdup_printf("%+.*f%s", d->digits, val * d->factor + d->offset, d->format);
3294 else
3295 return g_strdup_printf( "%.*f%s", d->digits, val * d->factor + d->offset, d->format);
3296}
3297
3298static void _dt_bauhaus_slider_set_with_raise(GtkWidget *widget, float pos, gboolean raise)
3299{
3300 // translate by bounds and call set_normalized.
3301 // raise == FALSE re-applies the value (re-clamped to the current bounds) and
3302 // redraws without committing to history or emitting "value-changed". This is
3303 // what bound changes (soft min/max) need: re-clamping the current value to a
3304 // new display range is not a user edit, and emitting would re-enter the
3305 // module's gui_changed handler -- which during gui_init runs before all
3306 // widgets exist (NULL deref), and at runtime would spuriously commit.
3307 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3309 const float rpos = CLAMP(pos, d->hard_min, d->hard_max);
3310
3311 // Restore soft min/max if we are in its range
3312 float rrpos = (rpos - d->soft_min) / (d->soft_max - d->soft_min);
3313 if(rrpos > 0.f)
3314 d->min = d->soft_min;
3315 else
3316 d->min = rpos;
3317
3318 if(rrpos < 1.f)
3319 d->max = d->soft_max;
3320 else
3321 d->max = rpos;
3322
3323 dt_bauhaus_slider_set_normalized(w, (rpos - d->min) / (d->max - d->min), raise);
3324}
3325
3326void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
3327{
3328 // this is the public interface function, it commits the value and emits "value-changed"
3330}
3331
3333{
3334 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(widget)->data.slider;
3335 dt_bauhaus_slider_set(widget, (val - d->offset) / d->factor);
3336}
3337
3339{
3340 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3342 d->digits = val;
3343}
3344
3346{
3347 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3349 return d->digits;
3350}
3351
3353{
3354 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3356 d->step = val;
3357}
3358
3360{
3361 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3363 float step = d->step;
3364
3365 if(step == 0.f)
3366 {
3367 const float min = d->soft_min;
3368 const float max = d->soft_max;
3369
3370 // Derive the automatic keyboard/wheel step from the internal slider domain.
3371 // Display formatting may rescale that domain (for example "%" multiplies by 100),
3372 // but interaction needs to stay in the unformatted value space to avoid jumping
3373 // across the whole range in a single step for small-percent sliders.
3374 const float top = fminf(max-min, fmaxf(fabsf(min), fabsf(max)));
3375 if(top >= 100.f)
3376 step = 1.f;
3377 else
3378 step = top / 100.f;
3379 }
3380
3381 return copysignf(step, d->factor);
3382}
3383
3385{
3386 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3388 d->fill_feedback = feedback;
3389 gtk_widget_queue_draw(widget);
3390}
3391
3393{
3394 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3396 d->min = d->soft_min;
3397 d->max = d->soft_max;
3398 dt_bauhaus_slider_set(widget, d->defpos);
3399 return;
3400}
3401
3402void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
3403{
3404 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3406 d->format = g_intern_string(format);
3407
3408 if(strstr(format,"%") && fabsf(d->hard_max) <= 10)
3409 {
3410 if(d->factor == 1.0f) d->factor = 100;
3411 // Scaling by 100 costs two decimals, but a slider declared with fewer than two must not end
3412 // up with a negative digit count : it is a decimal exponent, fed to ipow() on every value
3413 // change and every redraw, and there is no such thing as fewer than zero decimals.
3414 d->digits = MAX(d->digits - 2, 0);
3415 }
3416}
3417
3419{
3420 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3422 d->factor = factor;
3423}
3424
3425void dt_bauhaus_slider_set_offset(GtkWidget *widget, float offset)
3426{
3427 struct dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
3429 d->offset = offset;
3430}
3431
3433{
3434 // Announce the new value. Whoever listens decides what it costs: the history commit
3435 // batches the pipeline recompute it triggers, so emitting on every scroll step is fine
3436 // and this widget has no reason to hold anything back.
3437
3438 // Don't commit anything while a reset is in progress (reloading history, applying a
3439 // style, ...): those set widget values programmatically and must not reach history.
3440 if(dt_gui_widgets_suppressed()) return;
3441
3443 {
3446 else
3447 fprintf(stderr, "ERROR: %s - %s is set to use default callback but none is provided\n", w->module->name,
3448 w->label);
3449 }
3450 else
3451 {
3452 if(w->module)
3453 fprintf(stderr, "WARNING: %s - %s has an IOP module but doesn't use default callback\n", w->module->name,
3454 w->label);
3455 }
3456
3457 // We need te emit this signal inconditionnaly
3458 g_signal_emit_by_name(G_OBJECT(w), "value-changed");
3459}
3460
3470static void dt_bauhaus_slider_set_normalized(struct dt_bauhaus_widget_t *w, float pos, gboolean raise)
3471{
3473 const float old_pos = d->pos;
3474 const float new_pos = CLAMP(pos, 0.0f, 1.0f);
3475
3476 if(old_pos != new_pos || raise)
3477 {
3478 const float new_value = new_pos * (d->max - d->min) + d->min;
3479 const float precision = (float)ipow(10, d->digits) * d->factor;
3481 d->pos = (rounded_value - d->min) / (d->max - d->min);
3482
3483 if(w->bauhaus->current == w)
3485
3487
3489 }
3490}
3491
3492static gboolean dt_bauhaus_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
3493{
3494 dt_bauhaus_t *bh = g_object_get_data(G_OBJECT(widget), "bauhaus");
3495 dt_bauhaus_widget_t *w = bh->current;
3496
3497 guint key = dt_keys_mainpad_alternatives(event->keyval);
3498
3499 switch(w->type)
3500 {
3501 case DT_BAUHAUS_SLIDER:
3502 {
3503 if(bh->keys_cnt + 2 < 64
3504 && (key == GDK_KEY_space || // SPACE
3505 key == GDK_KEY_percent || // %
3506 (event->string[0] >= 40 && event->string[0] <= 57) || // ()+-*/.,0-9
3508 key == GDK_KEY_X || key == GDK_KEY_x)) // Xx
3509 {
3511 bh->keys[bh->keys_cnt++] = '^';
3512 else
3513 bh->keys[bh->keys_cnt++] = event->string[0];
3514 gtk_widget_queue_draw(bh->popup_area);
3515 }
3516 else if(bh->keys_cnt > 0
3518 {
3519 bh->keys[--bh->keys_cnt] = 0;
3520 gtk_widget_queue_draw(bh->popup_area);
3521 }
3522 else if(bh->keys_cnt > 0 && bh->keys_cnt + 1 < 64
3523 && (key == GDK_KEY_Return))
3524 {
3525 // accept input
3526 bh->keys[bh->keys_cnt] = 0;
3527 // unnormalized input, user was typing this:
3529 const float new_value = dt_calculator_solve(old_value, bh->keys);
3531 bh->keys_cnt = 0;
3532 memset(bh->keys, 0, sizeof(bh->keys));
3534 }
3535 else if(key == GDK_KEY_Escape)
3536 {
3537 // discard input ands close popup
3538 bh->keys_cnt = 0;
3539 memset(bh->keys, 0, sizeof(bh->keys));
3541 }
3542 else
3543 return FALSE;
3544
3545 return TRUE;
3546 }
3548 {
3549 if(!g_utf8_validate(event->string, -1, NULL)) return FALSE;
3550 const gunichar c = g_utf8_get_char(event->string);
3551 const long int char_width = g_utf8_next_char(event->string) - event->string;
3552 if(bh->keys_cnt + 1 + char_width < 64 && g_unichar_isprint(c))
3553 {
3554 // only accept key input if still valid or editable?
3555 g_utf8_strncpy(bh->keys + bh->keys_cnt, event->string, 1);
3556 bh->keys_cnt += char_width;
3557 gtk_widget_queue_draw(bh->popup_area);
3558 }
3559 else if(bh->keys_cnt > 0
3561 {
3562 bh->keys_cnt
3563 -= (bh->keys + bh->keys_cnt)
3564 - g_utf8_prev_char(bh->keys + bh->keys_cnt);
3565 bh->keys[bh->keys_cnt] = 0;
3566 gtk_widget_queue_draw(bh->popup_area);
3567 }
3568 else if(bh->keys_cnt > 0 && bh->keys_cnt + 1 < 64
3569 && (key == GDK_KEY_Return))
3570 {
3571 // accept unique matches only for editable:
3572 if(w->data.combobox.editable)
3573 bh->end_mouse_y = FLT_MAX;
3574 else
3575 bh->end_mouse_y = 0;
3576 bh->keys[bh->keys_cnt] = 0;
3578 bh->keys_cnt = 0;
3579 memset(bh->keys, 0, sizeof(bh->keys));
3581 }
3582 else if(key == GDK_KEY_Escape)
3583 {
3584 // discard input and close popup
3585 bh->keys_cnt = 0;
3586 memset(bh->keys, 0, sizeof(bh->keys));
3588 }
3589 else if(key == GDK_KEY_Up)
3590 {
3592 }
3593 else if(key == GDK_KEY_Down)
3594 {
3596 }
3597 else if(key == GDK_KEY_Return)
3598 {
3599 // return pressed, but didn't type anything
3600 bh->end_mouse_y = -1; // negative will use currently highlighted instead.
3601 bh->keys[bh->keys_cnt] = 0;
3602 bh->keys_cnt = 0;
3603 memset(bh->keys, 0, sizeof(bh->keys));
3604 dt_bauhaus_widget_accept(bh->current);
3606 }
3607 else
3608 return FALSE;
3609 return TRUE;
3610 }
3611 default:
3612 return FALSE;
3613 }
3614}
3615
3616static gboolean dt_bauhaus_slider_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3617{
3618 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3620
3621 double event_x = event->x;
3622 double event_y = event->y;
3623 double main_width = 0.;
3625 w->bauhaus->mouse_x = event_x;
3626 w->bauhaus->mouse_y = event_y;
3627
3629 {
3631 return FALSE;
3632 }
3633
3634 gtk_widget_grab_focus(widget);
3636
3638 {
3640 return TRUE;
3641 }
3642 else if(activated == BH_REGION_MAIN)
3643 {
3644 if(event->button == 1)
3645 {
3646 if(event->type == GDK_2BUTTON_PRESS)
3647 {
3648 // double left click on the main region : reset value to default
3650 d->is_dragging = 0;
3651 }
3652 else
3653 {
3654 // single left click on main region : redraw the slider immediately
3655 // but without committing results to pipeline yet.
3657 {
3658 // single left click on the header name : do nothing (only give focus)
3659 d->is_dragging = 0;
3660 }
3661 else
3662 {
3663 // single left click on slider bar : set new value
3664 d->is_dragging = 1;
3666 }
3667 }
3668 }
3669 else if(event->button == 3)
3670 {
3671 // right click : show accurate slider setting popup
3672 d->oldpos = d->pos;
3673 dt_bauhaus_show_popup(widget);
3674 }
3675 else if(event->button == 2)
3676 {
3677 // middle click : reset zoom range to soft min/max
3678 _slider_zoom_range(w, 0);
3679 }
3680 return TRUE;
3681 }
3682 return FALSE;
3683}
3684
3685static gboolean dt_bauhaus_slider_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3686{
3687 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3689
3691
3692 // is_dragging is set TRUE no matter what on button_press, except if it's a double click.
3693 // double click is the only event handled in button_press, otherwise we assume every event
3694 // is drag and drop, and handle the final drag coordinate here.
3695 if(d->is_dragging)
3696 {
3697 d->is_dragging = 0;
3698
3699 if(event->button == 1)
3700 {
3702 return TRUE;
3703 }
3704 }
3705 return FALSE;
3706}
3707
3708static gboolean dt_bauhaus_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
3709{
3710 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3713
3714 if(d->is_dragging && event->state & GDK_BUTTON1_MASK)
3715 {
3716 double event_x = event->x;
3717 double event_y = event->y;
3718 double main_width;
3720
3721 w->bauhaus->mouse_x = event_x;
3722 w->bauhaus->mouse_y = event_y;
3724 }
3725
3726 return activated;
3727}
3728
3730{
3731 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3732 w->no_accels = TRUE;
3733}
3734
3736{
3737 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3738 w->no_module_list = TRUE;
3739}
3740
3742{
3743 struct dt_bauhaus_widget_t *w = (struct dt_bauhaus_widget_t *)widget;
3745}
3746
3747
3748// clang-format off
3749// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3750// vim: shiftwidth=2 expandtab tabstop=2 cindent
3751// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3752// 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, 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:1626
static void dt_bauhaus_widget_accept(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2383
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:3345
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:1700
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:2466
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3338
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:1658
void dt_bauhaus_combobox_insert(GtkWidget *widget, const char *text, int pos)
Definition bauhaus.c:1921
static gboolean _widget_key_press(GtkWidget *widget, GdkEventKey *event)
Definition bauhaus.c:3164
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:1992
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:3470
static gboolean _widget_scroll(GtkWidget *widget, GdkEventScroll *event)
Definition bauhaus.c:3098
void dt_bauhaus_slider_clear_stops(GtkWidget *widget)
Definition bauhaus.c:2148
static gboolean dt_bauhaus_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition bauhaus.c:3708
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:2366
char * dt_bauhaus_slider_get_text(GtkWidget *w, float val)
Definition bauhaus.c:3289
void dt_bauhaus_combobox_set_selected_text_align(GtkWidget *widget, const dt_bauhaus_combobox_alignment_t text_align)
Definition bauhaus.c:1895
void dt_bauhaus_combobox_set_editable(GtkWidget *widget, int editable)
Definition bauhaus.c:1877
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:2953
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:3392
static void dt_bauhaus_window_show(GtkWidget *w, gpointer user_data)
Definition bauhaus.c:1040
static void _style_updated(GtkWidget *widget)
Definition bauhaus.c:1644
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:2002
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:2265
void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
Definition bauhaus.c:2156
#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:3275
void dt_bauhaus_combobox_set_text(GtkWidget *widget, const char *text)
Definition bauhaus.c:2012
static void dt_bauhaus_draw_indicator(struct dt_bauhaus_widget_t *w, float pos, cairo_t *cr, float wd)
Definition bauhaus.c:2193
void dt_bauhaus_combobox_entry_set_sensitive(GtkWidget *widget, int pos, gboolean sensitive)
Definition bauhaus.c:2138
gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
Definition bauhaus.c:2114
_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:1613
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:1637
void dt_bauhaus_combobox_from_widget(dt_bauhaus_t *bh, dt_bauhaus_widget_t *w, dt_gui_module_t *self)
Definition bauhaus.c:1715
static void _get_preferred_width(GtkWidget *widget, gint *minimum_size, gint *natural_size)
Definition bauhaus.c:2922
void dt_bauhaus_combobox_insert_separator_with_height(GtkWidget *widget, int pos, const float row_height_factor)
Definition bauhaus.c:1946
static gboolean dt_bauhaus_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
Definition bauhaus.c:3492
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:1561
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:3384
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:3735
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:3283
static dt_bauhaus_combobox_data_t * _combobox_data(GtkWidget *widget)
Definition bauhaus.c:1734
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:1926
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2131
static void _commit_combobox_value(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2021
void dt_bauhaus_widget_set_quad_toggle(GtkWidget *widget, int toggle)
Definition bauhaus.c:1571
static int _combobox_selectable_count(const dt_bauhaus_combobox_data_t *d)
Count selectable combobox entries.
Definition bauhaus.c:1752
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:3425
static void _dt_bauhaus_slider_set_with_raise(GtkWidget *widget, float pos, gboolean raise)
Definition bauhaus.c:3298
#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:3616
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:2056
void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
Definition bauhaus.c:1867
void dt_bauhaus_slider_set_step(GtkWidget *widget, float val)
Definition bauhaus.c:3352
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:1941
void dt_bauhaus_show_popup(GtkWidget *widget)
Definition bauhaus.c:2969
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:3741
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:1958
void dt_bauhaus_widget_set_quad_active(GtkWidget *widget, int active)
Definition bauhaus.c:1577
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:1841
gpointer dt_bauhaus_combobox_get_data(GtkWidget *widget)
Definition bauhaus.c:1982
static void _commit_slider_value(struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:3432
void dt_bauhaus_combobox_add_populate_fct(GtkWidget *widget, void(*fct)(GtkWidget *w, void *module))
Definition bauhaus.c:1805
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:1853
static float _get_combobox_max_width(GtkWidget *widget)
Definition bauhaus.c:2691
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:1813
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:1888
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:1547
int dt_bauhaus_widget_get_quad_active(GtkWidget *widget)
Definition bauhaus.c:1594
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:3729
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:3685
static gchar * _build_label(const struct dt_bauhaus_widget_t *w)
Definition bauhaus.c:2461
const char * dt_bauhaus_combobox_get_text(GtkWidget *widget)
Definition bauhaus.c:1965
void dt_bauhaus_slider_set_val(GtkWidget *widget, float val)
Definition bauhaus.c:3332
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3326
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:3359
static gboolean _widget_draw(GtkWidget *widget, cairo_t *crf)
Definition bauhaus.c:2746
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:3079
void dt_bauhaus_widget_press_quad(GtkWidget *widget)
Definition bauhaus.c:1600
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:1789
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2085
#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:1631
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:1904
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1693
void dt_bauhaus_widget_set_quad_visibility(GtkWidget *widget, const gboolean visible)
Definition bauhaus.c:1587
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:2937
gboolean dt_bauhaus_combobox_set_from_text(GtkWidget *widget, const char *text)
Definition bauhaus.c:2095
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3402
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:2227
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:1769
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:1819
void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
Definition bauhaus.c:1553
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:1824
void dt_bauhaus_combobox_add_separator_with_height(GtkWidget *widget, const float row_height_factor)
Definition bauhaus.c:1858
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:3418
static gboolean dt_bauhaus_combobox_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition bauhaus.c:3216
void dt_bauhaus_combobox_add_aligned(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align)
Definition bauhaus.c:1836
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:124
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:540
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 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
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