1 /*
2 This file is part of darktable,
3 Copyright (C) 2012-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "bauhaus/bauhaus.h"
20 #include "common/calculator.h"
21 #include "common/darktable.h"
22 #include "control/conf.h"
23 #include "develop/develop.h"
24 #include "develop/imageop.h"
25 #include "gui/accelerators.h"
26 #include "gui/gtk.h"
27 #ifdef GDK_WINDOWING_QUARTZ
28 #include "osx/osx.h"
29 #endif
30
31 #include <math.h>
32 #include <strings.h>
33
34 #include <pango/pangocairo.h>
35
36 G_DEFINE_TYPE(DtBauhausWidget, dt_bh, GTK_TYPE_DRAWING_AREA)
37
38 enum
39 {
40 // Sliders
41 DT_ACTION_ELEMENT_VALUE = 0,
42 DT_ACTION_ELEMENT_BUTTON = 1,
43 DT_ACTION_ELEMENT_FORCE = 2,
44 DT_ACTION_ELEMENT_ZOOM = 3,
45
46 // Combos
47 DT_ACTION_ELEMENT_SELECTION = 0,
48 //DT_ACTION_ELEMENT_BUTTON = 1,
49 };
50
51 // INNER_PADDING is the horizontal space between slider and quad
52 // and vertical space between labels and slider baseline
53 static const double INNER_PADDING = 4.0;
54
55 // fwd declare
56 static gboolean dt_bauhaus_popup_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data);
57 static gboolean dt_bauhaus_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
58 static void dt_bauhaus_widget_accept(dt_bauhaus_widget_t *w);
59 static void dt_bauhaus_widget_reject(dt_bauhaus_widget_t *w);
60 static void _bauhaus_combobox_set(GtkWidget *widget, const int pos, const gboolean mute);
61
bauhaus_request_focus(dt_bauhaus_widget_t * w)62 static void bauhaus_request_focus(dt_bauhaus_widget_t *w)
63 {
64 if(w->module && w->module->type == DT_ACTION_TYPE_IOP_INSTANCE)
65 dt_iop_request_focus((dt_iop_module_t *)w->module);
66 gtk_widget_set_state_flags(GTK_WIDGET(w), GTK_STATE_FLAG_FOCUSED, TRUE);
67 }
68
_combobox_next_entry(GList * entries,int * new_pos,int delta_y)69 static gboolean _combobox_next_entry(GList *entries, int *new_pos, int delta_y)
70 {
71 dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)g_list_nth_data(entries, *new_pos);
72 while(entry && !entry->sensitive)
73 {
74 *new_pos += delta_y;
75 entry = (dt_bauhaus_combobox_entry_t *)g_list_nth_data(entries, *new_pos);
76 }
77 return entry != NULL;
78 }
79
get_line_height()80 static inline int get_line_height()
81 {
82 return darktable.bauhaus->scale * darktable.bauhaus->line_height;
83 }
84
new_combobox_entry(const char * label,dt_bauhaus_combobox_alignment_t alignment,gboolean sensitive,void * data,void (* free_func)(void *))85 static dt_bauhaus_combobox_entry_t *new_combobox_entry(const char *label, dt_bauhaus_combobox_alignment_t alignment,
86 gboolean sensitive, void *data, void (*free_func)(void *))
87 {
88 dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)calloc(1, sizeof(dt_bauhaus_combobox_entry_t));
89 entry->label = g_strdup(label);
90 entry->alignment = alignment;
91 entry->sensitive = sensitive;
92 entry->data = data;
93 entry->free_func = free_func;
94 return entry;
95 }
96
free_combobox_entry(gpointer data)97 static void free_combobox_entry(gpointer data)
98 {
99 dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)data;
100 g_free(entry->label);
101 if(entry->free_func)
102 entry->free_func(entry->data);
103 free(entry);
104 }
105
inner_height(GtkAllocation allocation)106 static inline float inner_height(GtkAllocation allocation)
107 {
108 // retrieve the inner height of the widget (inside the top/bottom margin)
109 return allocation.height - 2.0f * darktable.bauhaus->widget_space;
110 }
111
default_color_assign()112 static GdkRGBA * default_color_assign()
113 {
114 // helper to initialize a color pointer with red color as a default
115 GdkRGBA color;
116 color.red = 1.0f;
117 color.green = 0.0f;
118 color.blue = 0.0f;
119 color.alpha = 1.0f;
120 return gdk_rgba_copy(&color);
121 }
122
dt_bauhaus_widget_set_section(GtkWidget * widget,const gboolean is_section)123 void dt_bauhaus_widget_set_section(GtkWidget *widget, const gboolean is_section)
124 {
125 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
126 w->is_section = is_section;
127 }
128
show_pango_text(dt_bauhaus_widget_t * w,GtkStyleContext * context,cairo_t * cr,char * text,float x_pos,float y_pos,float max_width,gboolean right_aligned,gboolean calc_only,PangoEllipsizeMode ellipsize,gboolean is_markup,gboolean is_label)129 static int show_pango_text(dt_bauhaus_widget_t *w, GtkStyleContext *context, cairo_t *cr,
130 char *text, float x_pos, float y_pos, float max_width,
131 gboolean right_aligned, gboolean calc_only,
132 PangoEllipsizeMode ellipsize, gboolean is_markup, gboolean is_label)
133 {
134 PangoLayout *layout = pango_cairo_create_layout(cr);
135
136 if(max_width > 0)
137 {
138 pango_layout_set_ellipsize(layout, ellipsize);
139 pango_layout_set_width(layout, (int)(PANGO_SCALE * max_width + 0.5f));
140 }
141
142 if(text)
143 {
144 if(is_markup)
145 pango_layout_set_markup(layout, text, -1);
146 else
147 pango_layout_set_text(layout, text, -1);
148 }
149 else
150 {
151 // length of -1 is not allowed with NULL string (wtf)
152 pango_layout_set_text(layout, NULL, 0);
153 }
154
155 PangoFontDescription *font_desc =
156 w->is_section && is_label
157 ? pango_font_description_copy_static(darktable.bauhaus->pango_sec_font_desc)
158 : pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
159
160 // This should be able to update the font style for current text depending on :hover, :focused, etc.
161 // CSS pseudo-classes, yet it defaults to system font.
162 // FIXME: get that working so we can put :active text in bold, for example.
163 //gtk_style_context_get(context, gtk_widget_get_state_flags(GTK_WIDGET(w)), "font", font_desc, NULL);
164
165 pango_layout_set_font_description(layout, font_desc);
166
167 PangoAttrList *attrlist = pango_attr_list_new();
168 PangoAttribute *attr = pango_attr_font_features_new("tnum");
169 pango_attr_list_insert(attrlist, attr);
170 pango_layout_set_attributes(layout, attrlist);
171 pango_attr_list_unref(attrlist);
172
173 pango_cairo_context_set_resolution(pango_layout_get_context(layout), darktable.gui->dpi);
174
175 int pango_width, pango_height;
176 pango_layout_get_size(layout, &pango_width, &pango_height);
177 const float text_width = ((double)pango_width/PANGO_SCALE);
178
179 if(right_aligned) x_pos -= text_width;
180
181 if(!calc_only)
182 {
183 cairo_move_to(cr, x_pos, y_pos);
184 pango_cairo_show_layout(cr, layout);
185 }
186 pango_font_description_free(font_desc);
187 g_object_unref(layout);
188
189 return text_width;
190 }
191
192 // -------------------------------
_cursor_timeout_callback(gpointer user_data)193 static gboolean _cursor_timeout_callback(gpointer user_data)
194 {
195 if(darktable.bauhaus->cursor_blink_counter > 0) darktable.bauhaus->cursor_blink_counter--;
196
197 darktable.bauhaus->cursor_visible = !darktable.bauhaus->cursor_visible;
198 gtk_widget_queue_draw(darktable.bauhaus->popup_area);
199
200 // this can be >0 when we haven't reached the desired number or -1 when blinking forever
201 if(darktable.bauhaus->cursor_blink_counter != 0)
202 return TRUE;
203
204 darktable.bauhaus->cursor_timeout = 0; // otherwise the cursor won't come up when starting to type
205 return FALSE;
206 }
207
_start_cursor(int max_blinks)208 static void _start_cursor(int max_blinks)
209 {
210 darktable.bauhaus->cursor_blink_counter = max_blinks;
211 darktable.bauhaus->cursor_visible = FALSE;
212 if(darktable.bauhaus->cursor_timeout == 0)
213 darktable.bauhaus->cursor_timeout = g_timeout_add(500, _cursor_timeout_callback, NULL);
214 }
215
_stop_cursor()216 static void _stop_cursor()
217 {
218 if(darktable.bauhaus->cursor_timeout > 0)
219 {
220 g_source_remove(darktable.bauhaus->cursor_timeout);
221 darktable.bauhaus->cursor_timeout = 0;
222 darktable.bauhaus->cursor_visible = FALSE;
223 }
224 }
225 // -------------------------------
226
227
228 static void dt_bauhaus_slider_set_normalized(dt_bauhaus_widget_t *w, float pos);
229
slider_right_pos(float width)230 static float slider_right_pos(float width)
231 {
232 // relative position (in widget) of the right bound of the slider corrected with the inner padding
233 return 1.0f - (darktable.bauhaus->quad_width + INNER_PADDING) / width;
234 }
235
slider_coordinate(const float abs_position,const float width)236 static float slider_coordinate(const float abs_position, const float width)
237 {
238 // Translates an horizontal position relative to the slider
239 // in an horizontal position relative to the widget
240 const float left_bound = 0.0f;
241 const float right_bound = slider_right_pos(width); // exclude the quad area on the right
242 return (left_bound + abs_position * (right_bound - left_bound)) * width;
243 }
244
245
get_slider_line_offset(float pos,float scale,float x,float y,float ht,const int width)246 static float get_slider_line_offset(float pos, float scale, float x, float y, float ht, const int width)
247 {
248 // ht is in [0,1] scale here
249 const float l = 0.0f;
250 const float r = slider_right_pos(width);
251
252 float offset = 0.0f;
253 // handle linear startup and rescale y to fit the whole range again
254 if(y < ht)
255 {
256 offset = (x - l) / (r - l) - pos;
257 }
258 else
259 {
260 y -= ht;
261 y /= (1.0f - ht);
262
263 offset = (x - y * y * .5f - (1.0f - y * y) * (l + pos * (r - l)))
264 / (.5f * y * y / scale + (1.0f - y * y) * (r - l));
265 }
266 // clamp to result in a [0,1] range:
267 if(pos + offset > 1.0f) offset = 1.0f - pos;
268 if(pos + offset < 0.0f) offset = -pos;
269 return offset;
270 }
271
272 // draw a loupe guideline for the quadratic zoom in in the slider interface:
draw_slider_line(cairo_t * cr,float pos,float off,float scale,const int width,const int height,const int ht)273 static void draw_slider_line(cairo_t *cr, float pos, float off, float scale, const int width,
274 const int height, const int ht)
275 {
276 // pos is normalized position [0,1], offset is on that scale.
277 // ht is in pixels here
278 const float l = 0.0f;
279 const float r = slider_right_pos(width);
280
281 const int steps = 64;
282 cairo_move_to(cr, width * (l + (pos + off) * (r - l)), ht * .7f);
283 cairo_line_to(cr, width * (l + (pos + off) * (r - l)), ht);
284 for(int j = 1; j < steps; j++)
285 {
286 const float y = j / (steps - 1.0f);
287 const float x = y * y * .5f * (1.f + off / scale) + (1.0f - y * y) * (l + (pos + off) * (r - l));
288 cairo_line_to(cr, x * width, ht + y * (height - ht));
289 }
290 }
291 // -------------------------------
292
293 // handlers on the popup window, to close popup:
dt_bauhaus_window_motion_notify(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)294 static gboolean dt_bauhaus_window_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
295 {
296 const float tol = 50;
297 gint wx, wy;
298 GtkAllocation allocation;
299 gtk_widget_get_allocation(widget, &allocation);
300 gdk_window_get_origin(gtk_widget_get_window(widget), &wx, &wy);
301
302 if(event->x_root > wx + allocation.width + tol || event->y_root > wy + inner_height(allocation) + tol
303 || event->x_root < (int)wx - tol || event->y_root < (int)wy - tol)
304 {
305 dt_bauhaus_widget_reject(darktable.bauhaus->current);
306 gtk_widget_set_state_flags(GTK_WIDGET(darktable.bauhaus->current), GTK_STATE_FLAG_NORMAL, TRUE);
307 dt_bauhaus_hide_popup();
308 return TRUE;
309 }
310 // make sure to propagate the event further
311 return FALSE;
312 }
313
dt_bauhaus_window_button_press(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)314 static gboolean dt_bauhaus_window_button_press(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
315 {
316 const float tol = 0;
317 gint wx, wy;
318 GtkAllocation allocation;
319 gtk_widget_get_allocation(widget, &allocation);
320 gdk_window_get_origin(gtk_widget_get_window(widget), &wx, &wy);
321
322 if((event->x_root > wx + allocation.width + tol || event->y_root > wy + inner_height(allocation) + tol
323 || event->x_root < (int)wx - tol || event->y_root < (int)wy - tol))
324 {
325 dt_bauhaus_widget_reject(darktable.bauhaus->current);
326 gtk_widget_set_state_flags(GTK_WIDGET(darktable.bauhaus->current), GTK_STATE_FLAG_NORMAL, FALSE);
327 dt_bauhaus_hide_popup();
328 return TRUE;
329 }
330 // make sure to propagate the event further
331 return FALSE;
332 }
333
combobox_popup_scroll(int amt)334 static void combobox_popup_scroll(int amt)
335 {
336 const dt_bauhaus_combobox_data_t *d = &darktable.bauhaus->current->data.combobox;
337 int new_value = CLAMP(d->active + amt, 0, d->num_labels - 1);
338
339 // skip insensitive ones
340 if(!_combobox_next_entry(d->entries, &new_value, amt))
341 return;
342
343 gint wx = 0, wy = 0;
344 const int skip = darktable.bauhaus->line_height;
345 GdkWindow *w = gtk_widget_get_window(darktable.bauhaus->popup_window);
346 gdk_window_get_origin(w, &wx, &wy);
347 // gdk_window_get_position(w, &wx, &wy);
348 gdk_window_move(w, wx, wy - skip * (new_value - d->active));
349
350 // make sure highlighted entry is updated:
351 darktable.bauhaus->mouse_x = 0;
352 darktable.bauhaus->mouse_y = new_value * skip + skip / 2;
353 gtk_widget_queue_draw(darktable.bauhaus->popup_area);
354
355 // and we change the value
356 _bauhaus_combobox_set(GTK_WIDGET(darktable.bauhaus->current), new_value, d->mute_scrolling);
357 }
358
359
dt_bauhaus_popup_scroll(GtkWidget * widget,GdkEventScroll * event,gpointer user_data)360 static gboolean dt_bauhaus_popup_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
361 {
362 switch(darktable.bauhaus->current->type)
363 {
364 case DT_BAUHAUS_COMBOBOX:
365 {
366 int delta_y = 0;
367 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
368 combobox_popup_scroll(delta_y);
369 break;
370 }
371 case DT_BAUHAUS_SLIDER:
372 break;
373 default:
374 break;
375 }
376 return TRUE;
377 }
378
dt_bauhaus_popup_motion_notify(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)379 static gboolean dt_bauhaus_popup_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
380 {
381 GtkAllocation allocation_popup_window;
382 gtk_widget_get_allocation(darktable.bauhaus->popup_window, &allocation_popup_window);
383 gtk_widget_queue_draw(darktable.bauhaus->popup_area);
384 dt_bauhaus_widget_t *w = darktable.bauhaus->current;
385 GtkAllocation allocation_w;
386 gtk_widget_get_allocation(GTK_WIDGET(w), &allocation_w);
387 const int width = allocation_popup_window.width, height = inner_height(allocation_popup_window);
388 // coordinate transform is in vain because we're only ever called after a button release.
389 // that means the system is always the one of the popup.
390 // that also means that we can't have hovering combobox entries while still holding the button. :(
391 const float ex = event->x;
392 const float ey = event->y;
393 GtkAllocation allocation;
394 gtk_widget_get_allocation(widget, &allocation);
395
396 gtk_widget_set_state_flags(GTK_WIDGET(w), GTK_STATE_FLAG_PRELIGHT, TRUE);
397
398 if(darktable.bauhaus->keys_cnt == 0) _stop_cursor();
399
400 switch(w->type)
401 {
402 case DT_BAUHAUS_COMBOBOX:
403 darktable.bauhaus->mouse_x = ex;
404 darktable.bauhaus->mouse_y = ey;
405 break;
406 case DT_BAUHAUS_SLIDER:
407 {
408 const dt_bauhaus_slider_data_t *d = &w->data.slider;
409 const float mouse_off = get_slider_line_offset(d->oldpos, d->scale, ex / width, ey / height,
410 allocation_w.height / (float)height, allocation.width);
411 if(!darktable.bauhaus->change_active)
412 {
413 if((darktable.bauhaus->mouse_line_distance < 0 && mouse_off >= 0)
414 || (darktable.bauhaus->mouse_line_distance > 0 && mouse_off <= 0))
415 darktable.bauhaus->change_active = 1;
416 darktable.bauhaus->mouse_line_distance = mouse_off;
417 }
418 if(darktable.bauhaus->change_active)
419 {
420 // remember mouse position for motion effects in draw
421 darktable.bauhaus->mouse_x = ex;
422 darktable.bauhaus->mouse_y = ey;
423 dt_bauhaus_slider_set_normalized(w, d->oldpos + mouse_off);
424 }
425 }
426 break;
427 default:
428 break;
429 }
430 return TRUE;
431 }
432
dt_bauhaus_popup_leave_notify(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)433 static gboolean dt_bauhaus_popup_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
434 {
435 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_NORMAL, TRUE);
436 return TRUE;
437 }
438
dt_bauhaus_popup_button_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)439 static gboolean dt_bauhaus_popup_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
440 {
441 if(darktable.bauhaus->current && (darktable.bauhaus->current->type == DT_BAUHAUS_COMBOBOX)
442 && (event->button == 1) && // only accept left mouse click
443 (dt_get_wtime() - darktable.bauhaus->opentime >= 0.250f)) // default gtk timeout for double-clicks
444 {
445 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_ACTIVE, TRUE);
446
447 // event might be in wrong system, transform ourselves:
448 gint wx, wy, x, y;
449 gdk_window_get_origin(gtk_widget_get_window(darktable.bauhaus->popup_window), &wx, &wy);
450
451 gdk_device_get_position(
452 gdk_seat_get_pointer(gdk_display_get_default_seat(gtk_widget_get_display(widget))), 0, &x, &y);
453 darktable.bauhaus->end_mouse_x = x - wx;
454 darktable.bauhaus->end_mouse_y = y - wy;
455 const dt_bauhaus_combobox_data_t *d = &darktable.bauhaus->current->data.combobox;
456 if(!d->mute_scrolling)
457 dt_bauhaus_widget_accept(darktable.bauhaus->current);
458 dt_bauhaus_hide_popup();
459 }
460 else if(darktable.bauhaus->hiding)
461 {
462 dt_bauhaus_hide_popup();
463 }
464 return TRUE;
465 }
466
dt_bauhaus_popup_button_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)467 static gboolean dt_bauhaus_popup_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
468 {
469 if(event->button == 1)
470 {
471 if(darktable.bauhaus->current->type == DT_BAUHAUS_COMBOBOX
472 && dt_get_wtime() - darktable.bauhaus->opentime < 0.250f) // default gtk timeout for double-clicks
473 {
474 // counts as double click, reset:
475 const dt_bauhaus_combobox_data_t *d = &darktable.bauhaus->current->data.combobox;
476 dt_bauhaus_combobox_set(GTK_WIDGET(darktable.bauhaus->current), d->defpos);
477 dt_bauhaus_widget_reject(darktable.bauhaus->current);
478 gtk_widget_set_state_flags(GTK_WIDGET(darktable.bauhaus->current),
479 GTK_STATE_FLAG_FOCUSED, FALSE);
480 }
481 else
482 {
483 // only accept left mouse click
484 darktable.bauhaus->end_mouse_x = event->x;
485 darktable.bauhaus->end_mouse_y = event->y;
486 dt_bauhaus_widget_accept(darktable.bauhaus->current);
487 gtk_widget_set_state_flags(GTK_WIDGET(darktable.bauhaus->current),
488 GTK_STATE_FLAG_FOCUSED, FALSE);
489 }
490 }
491 else
492 {
493 dt_bauhaus_widget_reject(darktable.bauhaus->current);
494 }
495 darktable.bauhaus->hiding = TRUE;
496 return TRUE;
497 }
498
dt_bauhaus_window_show(GtkWidget * w,gpointer user_data)499 static void dt_bauhaus_window_show(GtkWidget *w, gpointer user_data)
500 {
501 // Could grab the popup_area rather than popup_window, but if so
502 // then popup_area would get all motion events including those
503 // outside of the popup. This way the popup_area gets motion events
504 // related to updating the popup, and popup_window gets all others
505 // which would be the ones telling it to close the popup.
506 gtk_grab_add(w);
507 }
508
dt_bh_init(DtBauhausWidget * class)509 static void dt_bh_init(DtBauhausWidget *class)
510 {
511 // not sure if we want to use this instead of our code in *_new()
512 // TODO: the common code from bauhaus_widget_init() could go here.
513 }
514
dt_bh_class_init(DtBauhausWidgetClass * class)515 static void dt_bh_class_init(DtBauhausWidgetClass *class)
516 {
517 darktable.bauhaus->signals[DT_BAUHAUS_VALUE_CHANGED_SIGNAL]
518 = g_signal_new("value-changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
519 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
520 darktable.bauhaus->signals[DT_BAUHAUS_QUAD_PRESSED_SIGNAL]
521 = g_signal_new("quad-pressed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
522 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
523
524 // TODO: could init callbacks once per class for more efficiency:
525 // GtkWidgetClass *widget_class;
526 // widget_class = GTK_WIDGET_CLASS (class);
527 // widget_class->draw = dt_bauhaus_draw;
528 }
529
dt_bauhaus_load_theme()530 void dt_bauhaus_load_theme()
531 {
532 darktable.bauhaus->line_space = 1.5;
533 darktable.bauhaus->line_height = 9;
534 darktable.bauhaus->marker_size = 0.25f;
535
536 GtkWidget *root_window = dt_ui_main_window(darktable.gui->ui);
537 GtkStyleContext *ctx = gtk_style_context_new();
538 GtkWidgetPath *path = gtk_widget_path_new();
539 const int pos = gtk_widget_path_append_type(path, GTK_TYPE_WIDGET);
540 gtk_widget_path_iter_set_name(path, pos, "iop-plugin-ui");
541 gtk_style_context_set_path(ctx, path);
542 gtk_style_context_set_screen (ctx, gtk_widget_get_screen(root_window));
543
544 gtk_style_context_lookup_color(ctx, "bauhaus_fg", &darktable.bauhaus->color_fg);
545 gtk_style_context_lookup_color(ctx, "bauhaus_fg_insensitive", &darktable.bauhaus->color_fg_insensitive);
546 gtk_style_context_lookup_color(ctx, "bauhaus_bg", &darktable.bauhaus->color_bg);
547 gtk_style_context_lookup_color(ctx, "bauhaus_border", &darktable.bauhaus->color_border);
548 gtk_style_context_lookup_color(ctx, "bauhaus_fill", &darktable.bauhaus->color_fill);
549 gtk_style_context_lookup_color(ctx, "bauhaus_indicator_border", &darktable.bauhaus->indicator_border);
550
551 gtk_style_context_lookup_color(ctx, "graph_bg", &darktable.bauhaus->graph_bg);
552 gtk_style_context_lookup_color(ctx, "graph_exterior", &darktable.bauhaus->graph_exterior);
553 gtk_style_context_lookup_color(ctx, "graph_border", &darktable.bauhaus->graph_border);
554 gtk_style_context_lookup_color(ctx, "graph_grid", &darktable.bauhaus->graph_grid);
555 gtk_style_context_lookup_color(ctx, "graph_fg", &darktable.bauhaus->graph_fg);
556 gtk_style_context_lookup_color(ctx, "graph_fg_active", &darktable.bauhaus->graph_fg_active);
557 gtk_style_context_lookup_color(ctx, "graph_overlay", &darktable.bauhaus->graph_overlay);
558 gtk_style_context_lookup_color(ctx, "inset_histogram", &darktable.bauhaus->inset_histogram);
559 gtk_style_context_lookup_color(ctx, "graph_red", &darktable.bauhaus->graph_colors[0]);
560 gtk_style_context_lookup_color(ctx, "graph_green", &darktable.bauhaus->graph_colors[1]);
561 gtk_style_context_lookup_color(ctx, "graph_blue", &darktable.bauhaus->graph_colors[2]);
562 gtk_style_context_lookup_color(ctx, "colorlabel_red",
563 &darktable.bauhaus->colorlabels[DT_COLORLABELS_RED]);
564 gtk_style_context_lookup_color(ctx, "colorlabel_yellow",
565 &darktable.bauhaus->colorlabels[DT_COLORLABELS_YELLOW]);
566 gtk_style_context_lookup_color(ctx, "colorlabel_green",
567 &darktable.bauhaus->colorlabels[DT_COLORLABELS_GREEN]);
568 gtk_style_context_lookup_color(ctx, "colorlabel_blue",
569 &darktable.bauhaus->colorlabels[DT_COLORLABELS_BLUE]);
570 gtk_style_context_lookup_color(ctx, "colorlabel_purple",
571 &darktable.bauhaus->colorlabels[DT_COLORLABELS_PURPLE]);
572
573 PangoFontDescription *pfont = 0;
574 gtk_style_context_get(ctx, GTK_STATE_FLAG_NORMAL, "font", &pfont, NULL);
575
576 // make sure we release previously loaded font
577 if(darktable.bauhaus->pango_font_desc)
578 pango_font_description_free(darktable.bauhaus->pango_font_desc);
579
580 darktable.bauhaus->pango_font_desc = pfont;
581
582 if(darktable.bauhaus->pango_sec_font_desc)
583 pango_font_description_free(darktable.bauhaus->pango_sec_font_desc);
584
585 // now get the font for the section labels
586 gtk_widget_path_iter_set_name(path, pos, "section_label");
587 gtk_style_context_set_path(ctx, path);
588 gtk_style_context_get(ctx, GTK_STATE_FLAG_NORMAL, "font", &pfont, NULL);
589 darktable.bauhaus->pango_sec_font_desc = pfont;
590
591 gtk_widget_path_free(path);
592
593 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 128, 128);
594 cairo_t *cr = cairo_create(cst);
595 PangoLayout *layout = pango_cairo_create_layout(cr);
596 pango_layout_set_text(layout, "m", -1);
597 pango_layout_set_font_description(layout, darktable.bauhaus->pango_font_desc);
598 pango_cairo_context_set_resolution(pango_layout_get_context(layout), darktable.gui->dpi);
599 int pango_width, pango_height;
600 pango_layout_get_size(layout, &pango_width, &pango_height);
601 g_object_unref(layout);
602 cairo_destroy(cr);
603 cairo_surface_destroy(cst);
604
605 darktable.bauhaus->scale = 1.33f;
606 darktable.bauhaus->line_height = pango_height / PANGO_SCALE;
607 darktable.bauhaus->widget_space = INNER_PADDING / 4.0f; // used as a top/bottom margin for widgets
608 darktable.bauhaus->quad_width = darktable.bauhaus->line_height;
609
610 darktable.bauhaus->baseline_size = darktable.bauhaus->line_height / 2.5f; // absolute size in Cairo unit
611 darktable.bauhaus->border_width = 2.0f; // absolute size in Cairo unit
612 darktable.bauhaus->marker_size = (darktable.bauhaus->baseline_size + darktable.bauhaus->border_width) * 0.9f;
613 }
614
dt_bauhaus_init()615 void dt_bauhaus_init()
616 {
617 darktable.bauhaus = (dt_bauhaus_t *)calloc(1, sizeof(dt_bauhaus_t));
618 darktable.bauhaus->keys_cnt = 0;
619 darktable.bauhaus->current = NULL;
620 darktable.bauhaus->popup_area = gtk_drawing_area_new();
621 gtk_widget_set_name(darktable.bauhaus->popup_area, "bauhaus-popup");
622 darktable.bauhaus->pango_font_desc = NULL;
623
624 dt_bauhaus_load_theme();
625
626 darktable.bauhaus->skip_accel = 1;
627
628 // this easily gets keyboard input:
629 // darktable.bauhaus->popup_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
630 // but this doesn't flicker, and the above hack with key input seems to work well.
631 darktable.bauhaus->popup_window = gtk_window_new(GTK_WINDOW_POPUP);
632 #ifdef GDK_WINDOWING_QUARTZ
633 dt_osx_disallow_fullscreen(darktable.bauhaus->popup_window);
634 #endif
635 // this is needed for popup, not for toplevel.
636 // since popup_area gets the focus if we show the window, this is all
637 // we need.
638
639 gtk_widget_set_size_request(darktable.bauhaus->popup_area, DT_PIXEL_APPLY_DPI(300), DT_PIXEL_APPLY_DPI(300));
640 gtk_window_set_resizable(GTK_WINDOW(darktable.bauhaus->popup_window), FALSE);
641 gtk_window_set_default_size(GTK_WINDOW(darktable.bauhaus->popup_window), 260, 260);
642 // gtk_window_set_modal(GTK_WINDOW(c->popup_window), TRUE);
643 // gtk_window_set_decorated(GTK_WINDOW(c->popup_window), FALSE);
644
645 // for pie menu:
646 // gtk_window_set_position(GTK_WINDOW(c->popup_window), GTK_WIN_POS_MOUSE);// | GTK_WIN_POS_CENTER);
647
648 // needed on macOS to avoid fullscreening the popup with newer GTK
649 gtk_window_set_type_hint(GTK_WINDOW(darktable.bauhaus->popup_window), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
650
651 gtk_container_add(GTK_CONTAINER(darktable.bauhaus->popup_window), darktable.bauhaus->popup_area);
652 // gtk_window_set_title(GTK_WINDOW(c->popup_window), _("dtgtk control popup"));
653 gtk_window_set_keep_above(GTK_WINDOW(darktable.bauhaus->popup_window), TRUE);
654 gtk_window_set_gravity(GTK_WINDOW(darktable.bauhaus->popup_window), GDK_GRAVITY_STATIC);
655
656 gtk_widget_set_can_focus(darktable.bauhaus->popup_area, TRUE);
657 gtk_widget_add_events(darktable.bauhaus->popup_area, GDK_POINTER_MOTION_MASK
658 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
659 | GDK_KEY_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK
660 | darktable.gui->scroll_mask);
661
662 g_signal_connect(G_OBJECT(darktable.bauhaus->popup_window), "show", G_CALLBACK(dt_bauhaus_window_show), (gpointer)NULL);
663 g_signal_connect(G_OBJECT(darktable.bauhaus->popup_area), "draw", G_CALLBACK(dt_bauhaus_popup_draw),
664 (gpointer)NULL);
665 g_signal_connect(G_OBJECT(darktable.bauhaus->popup_window), "motion-notify-event",
666 G_CALLBACK(dt_bauhaus_window_motion_notify), (gpointer)NULL);
667 g_signal_connect(G_OBJECT(darktable.bauhaus->popup_window), "button-press-event", G_CALLBACK(dt_bauhaus_window_button_press),
668 (gpointer)NULL);
669 g_signal_connect(G_OBJECT(darktable.bauhaus->popup_area), "motion-notify-event",
670 G_CALLBACK(dt_bauhaus_popup_motion_notify), (gpointer)NULL);
671 g_signal_connect(G_OBJECT(darktable.bauhaus->popup_area), "leave-notify-event",
672 G_CALLBACK(dt_bauhaus_popup_leave_notify), (gpointer)NULL);
673 g_signal_connect(G_OBJECT(darktable.bauhaus->popup_area), "button-press-event",
674 G_CALLBACK(dt_bauhaus_popup_button_press), (gpointer)NULL);
675 // this is connected to the widget itself, not the popup. we're only interested
676 // in mouse release events that are initiated by a press on the original widget.
677 g_signal_connect (G_OBJECT (darktable.bauhaus->popup_area), "button-release-event",
678 G_CALLBACK (dt_bauhaus_popup_button_release), (gpointer)NULL);
679 g_signal_connect(G_OBJECT(darktable.bauhaus->popup_area), "key-press-event",
680 G_CALLBACK(dt_bauhaus_popup_key_press), (gpointer)NULL);
681 g_signal_connect(G_OBJECT(darktable.bauhaus->popup_area), "scroll-event",
682 G_CALLBACK(dt_bauhaus_popup_scroll), (gpointer)NULL);
683 }
684
dt_bauhaus_cleanup()685 void dt_bauhaus_cleanup()
686 {
687 }
688
689 // fwd declare a few callbacks
690 static gboolean dt_bauhaus_slider_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
691 static gboolean dt_bauhaus_slider_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
692 static gboolean dt_bauhaus_slider_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data);
693 static gboolean dt_bauhaus_slider_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
694 static gboolean dt_bauhaus_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data);
695
696
697 static gboolean dt_bauhaus_combobox_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
698 static gboolean dt_bauhaus_combobox_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data);
699 static gboolean dt_bauhaus_combobox_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
700 static gboolean dt_bauhaus_combobox_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data);
701
702 // static gboolean
703 // dt_bauhaus_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
704 static gboolean dt_bauhaus_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data);
705
706
707 // end static init/cleanup
708 // =================================================
709
710
711
712 // common initialization
dt_bauhaus_widget_init(dt_bauhaus_widget_t * w,dt_iop_module_t * self)713 static void dt_bauhaus_widget_init(dt_bauhaus_widget_t *w, dt_iop_module_t *self)
714 {
715 w->module = DT_ACTION(self);
716
717 w->section = NULL;
718
719 // no quad icon and no toggle button:
720 w->quad_paint = 0;
721 w->quad_paint_data = NULL;
722 w->quad_toggle = 0;
723 w->combo_populate = NULL;
724
725 switch(w->type)
726 {
727 case DT_BAUHAUS_SLIDER:
728 {
729 gtk_widget_set_name(GTK_WIDGET(w), "bauhaus-slider");
730 gtk_widget_set_size_request(GTK_WIDGET(w), -1, 2 * darktable.bauhaus->widget_space + INNER_PADDING + darktable.bauhaus->baseline_size + get_line_height() - darktable.bauhaus->border_width / 2.0f);
731 break;
732 }
733 case DT_BAUHAUS_COMBOBOX:
734 {
735 gtk_widget_set_name(GTK_WIDGET(w), "bauhaus-combobox");
736 gtk_widget_set_size_request(GTK_WIDGET(w), -1, 2 * darktable.bauhaus->widget_space + get_line_height());
737 break;
738 }
739 }
740
741 gtk_widget_add_events(GTK_WIDGET(w), GDK_POINTER_MOTION_MASK
742 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
743 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
744 | GDK_FOCUS_CHANGE_MASK
745 | darktable.gui->scroll_mask);
746
747 g_signal_connect(G_OBJECT(w), "draw", G_CALLBACK(dt_bauhaus_draw), NULL);
748
749 // for combobox, where mouse-release triggers a selection, we need to catch this
750 // event where the mouse-press occurred, which will be this widget. we just pass
751 // it on though:
752 // g_signal_connect (G_OBJECT (w), "button-release-event",
753 // G_CALLBACK (dt_bauhaus_popup_button_release), (gpointer)NULL);
754 }
755
dt_bauhaus_combobox_set_default(GtkWidget * widget,int def)756 void dt_bauhaus_combobox_set_default(GtkWidget *widget, int def)
757 {
758 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
759 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
760 d->defpos = def;
761 }
762
dt_bauhaus_combobox_get_default(GtkWidget * widget)763 int dt_bauhaus_combobox_get_default(GtkWidget *widget)
764 {
765 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
766 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
767 return d->defpos;
768 }
769
dt_bauhaus_slider_set_hard_min(GtkWidget * widget,float val)770 void dt_bauhaus_slider_set_hard_min(GtkWidget* widget, float val)
771 {
772 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
773 dt_bauhaus_slider_data_t *d = &w->data.slider;
774 float pos = dt_bauhaus_slider_get(widget);
775 d->hard_min = val;
776 d->min = MAX(d->min, d->hard_min);
777 d->soft_min = MAX(d->soft_min, d->hard_min);
778
779 if(val > d->hard_max) dt_bauhaus_slider_set_hard_max(widget,val);
780 if(pos < val)
781 {
782 dt_bauhaus_slider_set_soft(widget,val);
783 }
784 else
785 {
786 dt_bauhaus_slider_set_soft(widget,pos);
787 }
788 }
789
dt_bauhaus_slider_get_hard_min(GtkWidget * widget)790 float dt_bauhaus_slider_get_hard_min(GtkWidget* widget)
791 {
792 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
793 const dt_bauhaus_slider_data_t *d = &w->data.slider;
794 return d->hard_min;
795 }
796
dt_bauhaus_slider_set_hard_max(GtkWidget * widget,float val)797 void dt_bauhaus_slider_set_hard_max(GtkWidget* widget, float val)
798 {
799 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
800 dt_bauhaus_slider_data_t *d = &w->data.slider;
801 float pos = dt_bauhaus_slider_get(widget);
802 d->hard_max = val;
803 d->max = MIN(d->max, d->hard_max);
804 d->soft_max = MIN(d->soft_max, d->hard_max);
805
806 if(val < d->hard_min) dt_bauhaus_slider_set_hard_min(widget,val);
807 if(pos > val)
808 {
809 dt_bauhaus_slider_set_soft(widget,val);
810 }
811 else
812 {
813 dt_bauhaus_slider_set_soft(widget,pos);
814 }
815 }
816
dt_bauhaus_slider_get_hard_max(GtkWidget * widget)817 float dt_bauhaus_slider_get_hard_max(GtkWidget* widget)
818 {
819 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
820 const dt_bauhaus_slider_data_t *d = &w->data.slider;
821 return d->hard_max;
822 }
823
dt_bauhaus_slider_set_soft_min(GtkWidget * widget,float val)824 void dt_bauhaus_slider_set_soft_min(GtkWidget* widget, float val)
825 {
826 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
827 dt_bauhaus_slider_data_t *d = &w->data.slider;
828 float oldval = dt_bauhaus_slider_get(widget);
829 d->min = d->soft_min = CLAMP(val,d->hard_min,d->hard_max);
830 dt_bauhaus_slider_set_soft(widget,oldval);
831 }
832
dt_bauhaus_slider_get_soft_min(GtkWidget * widget)833 float dt_bauhaus_slider_get_soft_min(GtkWidget* widget)
834 {
835 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
836 const dt_bauhaus_slider_data_t *d = &w->data.slider;
837 return d->soft_min;
838 }
839
dt_bauhaus_slider_set_soft_max(GtkWidget * widget,float val)840 void dt_bauhaus_slider_set_soft_max(GtkWidget* widget, float val)
841 {
842 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
843 dt_bauhaus_slider_data_t *d = &w->data.slider;
844 float oldval = dt_bauhaus_slider_get(widget);
845 d->max = d->soft_max = CLAMP(val,d->hard_min,d->hard_max);
846 dt_bauhaus_slider_set_soft(widget,oldval);
847 }
848
dt_bauhaus_slider_get_soft_max(GtkWidget * widget)849 float dt_bauhaus_slider_get_soft_max(GtkWidget* widget)
850 {
851 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
852 const dt_bauhaus_slider_data_t *d = &w->data.slider;
853 return d->soft_max;
854 }
855
dt_bauhaus_slider_set_default(GtkWidget * widget,float def)856 void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
857 {
858 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
859 dt_bauhaus_slider_data_t *d = &w->data.slider;
860 d->defpos = def;
861 }
862
dt_bauhaus_slider_set_soft_range(GtkWidget * widget,float soft_min,float soft_max)863 void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
864 {
865 dt_bauhaus_slider_set_soft_min(widget,soft_min);
866 dt_bauhaus_slider_set_soft_max(widget,soft_max);
867 }
868
dt_bauhaus_slider_get_default(GtkWidget * widget)869 float dt_bauhaus_slider_get_default(GtkWidget *widget)
870 {
871 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
872 const dt_bauhaus_slider_data_t *d = &w->data.slider;
873 return d->defpos;
874 }
875
dt_bauhaus_slider_enable_soft_boundaries(GtkWidget * widget,float hard_min,float hard_max)876 void dt_bauhaus_slider_enable_soft_boundaries(GtkWidget *widget, float hard_min, float hard_max)
877 {
878 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
879 dt_bauhaus_slider_data_t *d = &w->data.slider;
880 d->hard_min = hard_min;
881 d->hard_max = hard_max;
882 }
883
884 extern const dt_action_def_t dt_action_def_slider;
885 extern const dt_action_def_t dt_action_def_combo;
886
dt_bauhaus_widget_set_label(GtkWidget * widget,const char * section,const char * label)887 void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *section, const char *label)
888 {
889 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
890 memset(w->label, 0, sizeof(w->label)); // keep valgrind happy
891 if(label) g_strlcpy(w->label, _(label), sizeof(w->label));
892 if(section) w->section = g_strdup(_(section));
893
894 if(w->module)
895 {
896 if(!darktable.bauhaus->skip_accel || w->module->type != DT_ACTION_TYPE_IOP_INSTANCE)
897 {
898 w->module = dt_action_define(w->module, section, label, widget,
899 w->type == DT_BAUHAUS_SLIDER ? &dt_action_def_slider : &dt_action_def_combo);
900 }
901
902 gtk_widget_queue_draw(GTK_WIDGET(w));
903 }
904 }
905
dt_bauhaus_widget_get_label(GtkWidget * widget)906 const char* dt_bauhaus_widget_get_label(GtkWidget *widget)
907 {
908 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
909 return w->label;
910 }
911
dt_bauhaus_widget_set_quad_paint(GtkWidget * widget,dt_bauhaus_quad_paint_f f,int paint_flags,void * paint_data)912 void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
913 {
914 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
915 w->quad_paint = f;
916 w->quad_paint_flags = paint_flags;
917 w->quad_paint_data = paint_data;
918 }
919
920 // make this quad a toggle button:
dt_bauhaus_widget_set_quad_toggle(GtkWidget * widget,int toggle)921 void dt_bauhaus_widget_set_quad_toggle(GtkWidget *widget, int toggle)
922 {
923 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
924 w->quad_toggle = toggle;
925 }
926
dt_bauhaus_widget_set_quad_active(GtkWidget * widget,int active)927 void dt_bauhaus_widget_set_quad_active(GtkWidget *widget, int active)
928 {
929 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
930 if (active)
931 w->quad_paint_flags |= CPF_ACTIVE;
932 else
933 w->quad_paint_flags &= ~CPF_ACTIVE;
934 gtk_widget_queue_draw(GTK_WIDGET(w));
935 }
936
dt_bauhaus_widget_get_quad_active(GtkWidget * widget)937 int dt_bauhaus_widget_get_quad_active(GtkWidget *widget)
938 {
939 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
940 return (w->quad_paint_flags & CPF_ACTIVE) == CPF_ACTIVE;
941 }
942
dt_bauhaus_widget_press_quad(GtkWidget * widget)943 void dt_bauhaus_widget_press_quad(GtkWidget *widget)
944 {
945 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
946 if (w->quad_toggle)
947 {
948 if (w->quad_paint_flags & CPF_ACTIVE)
949 w->quad_paint_flags &= ~CPF_ACTIVE;
950 else
951 w->quad_paint_flags |= CPF_ACTIVE;
952 }
953 else
954 w->quad_paint_flags |= CPF_ACTIVE;
955
956 g_signal_emit_by_name(G_OBJECT(w), "quad-pressed");
957 }
958
dt_bauhaus_widget_release_quad(GtkWidget * widget)959 void dt_bauhaus_widget_release_quad(GtkWidget *widget)
960 {
961 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
962 if (!w->quad_toggle)
963 {
964 if (w->quad_paint_flags & CPF_ACTIVE)
965 w->quad_paint_flags &= ~CPF_ACTIVE;
966 gtk_widget_queue_draw(GTK_WIDGET(w));
967 }
968 }
969
_default_linear_curve(GtkWidget * self,float value,dt_bauhaus_curve_t dir)970 static float _default_linear_curve(GtkWidget *self, float value, dt_bauhaus_curve_t dir)
971 {
972 // regardless of dir: input <-> output
973 return value;
974 }
975
_reverse_linear_curve(GtkWidget * self,float value,dt_bauhaus_curve_t dir)976 static float _reverse_linear_curve(GtkWidget *self, float value, dt_bauhaus_curve_t dir)
977 {
978 // regardless of dir: input <-> output
979 return 1.0 - value;
980 }
981
dt_bauhaus_slider_destroy(dt_bauhaus_widget_t * widget,gpointer user_data)982 static void dt_bauhaus_slider_destroy(dt_bauhaus_widget_t *widget, gpointer user_data)
983 {
984 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
985 if(w->type != DT_BAUHAUS_SLIDER) return;
986 if(w->section) g_free(w->section);
987 dt_bauhaus_slider_data_t *d = &w->data.slider;
988 if(d->timeout_handle) g_source_remove(d->timeout_handle);
989 d->timeout_handle = 0;
990 }
991
dt_bauhaus_slider_new(dt_iop_module_t * self)992 GtkWidget *dt_bauhaus_slider_new(dt_iop_module_t *self)
993 {
994 return dt_bauhaus_slider_new_with_range(self, 0.0, 1.0, 0.1, 0.5, 3);
995 }
996
dt_bauhaus_slider_new_with_range(dt_iop_module_t * self,float min,float max,float step,float defval,int digits)997 GtkWidget *dt_bauhaus_slider_new_with_range(dt_iop_module_t *self, float min, float max, float step,
998 float defval, int digits)
999 {
1000 return dt_bauhaus_slider_new_with_range_and_feedback(self, min, max, step, defval, digits, 1);
1001 }
1002
dt_bauhaus_slider_new_action(dt_action_t * self,float min,float max,float step,float defval,int digits)1003 GtkWidget *dt_bauhaus_slider_new_action(dt_action_t *self, float min, float max, float step,
1004 float defval, int digits)
1005 {
1006 return dt_bauhaus_slider_new_with_range((dt_iop_module_t *)self, min, max, step, defval, digits);
1007 }
1008
dt_bauhaus_slider_new_with_range_and_feedback(dt_iop_module_t * self,float min,float max,float step,float defval,int digits,int feedback)1009 GtkWidget *dt_bauhaus_slider_new_with_range_and_feedback(dt_iop_module_t *self, float min, float max,
1010 float step, float defval, int digits, int feedback)
1011 {
1012 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(g_object_new(DT_BAUHAUS_WIDGET_TYPE, NULL));
1013 return dt_bauhaus_slider_from_widget(w,self, min, max, step, defval, digits, feedback);
1014 }
1015
dt_bauhaus_slider_from_widget(dt_bauhaus_widget_t * w,dt_iop_module_t * self,float min,float max,float step,float defval,int digits,int feedback)1016 GtkWidget *dt_bauhaus_slider_from_widget(dt_bauhaus_widget_t* w,dt_iop_module_t *self, float min, float max,
1017 float step, float defval, int digits, int feedback)
1018 {
1019 w->type = DT_BAUHAUS_SLIDER;
1020 dt_bauhaus_widget_init(w, self);
1021 dt_bauhaus_slider_data_t *d = &w->data.slider;
1022 d->min = d->soft_min = d->hard_min = min;
1023 d->max = d->soft_max = d->hard_max = max;
1024 d->step = step;
1025 // normalize default:
1026 d->defpos = defval;
1027 d->pos = (defval - min) / (max - min);
1028 d->oldpos = d->pos;
1029 d->scale = 5.0f * step / (max - min);
1030 d->digits = digits;
1031 snprintf(d->format, sizeof(d->format), "%%.0%df", digits);
1032 d->factor = 1.0f;
1033 d->offset = 0.0f;
1034
1035 d->grad_cnt = 0;
1036
1037 d->fill_feedback = feedback;
1038
1039 d->is_dragging = 0;
1040 d->is_changed = 0;
1041 d->timeout_handle = 0;
1042 d->curve = _default_linear_curve;
1043
1044 gtk_widget_add_events(GTK_WIDGET(w), GDK_KEY_PRESS_MASK);
1045 gtk_widget_set_can_focus(GTK_WIDGET(w), TRUE);
1046
1047 g_signal_connect(G_OBJECT(w), "button-press-event", G_CALLBACK(dt_bauhaus_slider_button_press),
1048 (gpointer)NULL);
1049 g_signal_connect(G_OBJECT(w), "button-release-event", G_CALLBACK(dt_bauhaus_slider_button_release),
1050 (gpointer)NULL);
1051 g_signal_connect(G_OBJECT(w), "scroll-event", G_CALLBACK(dt_bauhaus_slider_scroll), (gpointer)NULL);
1052 g_signal_connect(G_OBJECT(w), "key-press-event", G_CALLBACK(dt_bauhaus_slider_key_press), (gpointer)NULL);
1053 g_signal_connect(G_OBJECT(w), "motion-notify-event", G_CALLBACK(dt_bauhaus_slider_motion_notify),
1054 (gpointer)NULL);
1055 g_signal_connect(G_OBJECT(w), "destroy", G_CALLBACK(dt_bauhaus_slider_destroy), (gpointer)NULL);
1056 return GTK_WIDGET(w);
1057 }
1058
dt_bauhaus_combobox_destroy(dt_bauhaus_widget_t * widget,gpointer user_data)1059 static void dt_bauhaus_combobox_destroy(dt_bauhaus_widget_t *widget, gpointer user_data)
1060 {
1061 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1062 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1063 if(w->section) g_free(w->section);
1064 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1065 g_list_free_full(d->entries, free_combobox_entry);
1066 d->entries = NULL;
1067 d->num_labels = 0;
1068 d->active = -1;
1069 }
1070
dt_bauhaus_combobox_new(dt_iop_module_t * self)1071 GtkWidget *dt_bauhaus_combobox_new(dt_iop_module_t *self)
1072 {
1073 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(g_object_new(DT_BAUHAUS_WIDGET_TYPE, NULL));
1074 dt_bauhaus_combobox_from_widget(w,self);
1075 return GTK_WIDGET(w);
1076 }
1077
dt_bauhaus_combobox_new_action(dt_action_t * self)1078 GtkWidget *dt_bauhaus_combobox_new_action(dt_action_t *self)
1079 {
1080 return dt_bauhaus_combobox_new((dt_iop_module_t *)self);
1081 }
1082
dt_bauhaus_combobox_new_full(dt_action_t * action,const char * section,const char * label,const char * tip,int pos,GtkCallback callback,gpointer data,const char ** texts)1083 GtkWidget *dt_bauhaus_combobox_new_full(dt_action_t *action, const char *section, const char *label, const char *tip,
1084 int pos, GtkCallback callback, gpointer data, const char **texts)
1085 {
1086 GtkWidget *combo = dt_bauhaus_combobox_new_action(action);
1087 dt_bauhaus_widget_set_label(combo, section, label);
1088 dt_bauhaus_combobox_add_list(combo, (dt_action_t *)(DT_BAUHAUS_WIDGET(combo)->module), texts);
1089 dt_bauhaus_combobox_set(combo, pos);
1090 gtk_widget_set_tooltip_text(combo, tip ? tip : _(label));
1091 if(callback) g_signal_connect(G_OBJECT(combo), "value-changed", G_CALLBACK(callback), data);
1092
1093 return combo;
1094 }
1095
dt_bauhaus_combobox_from_widget(dt_bauhaus_widget_t * w,dt_iop_module_t * self)1096 void dt_bauhaus_combobox_from_widget(dt_bauhaus_widget_t* w,dt_iop_module_t *self)
1097 {
1098 w->type = DT_BAUHAUS_COMBOBOX;
1099 dt_bauhaus_widget_init(w, self);
1100 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1101 d->entries = NULL;
1102 d->num_labels = 0;
1103 d->defpos = 0;
1104 d->active = -1;
1105 d->editable = 0;
1106 d->scale = 1;
1107 d->text_align = DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT;
1108 d->entries_ellipsis = PANGO_ELLIPSIZE_END;
1109 d->mute_scrolling = FALSE;
1110 memset(d->text, 0, sizeof(d->text));
1111
1112 gtk_widget_add_events(GTK_WIDGET(w), GDK_KEY_PRESS_MASK);
1113 gtk_widget_set_can_focus(GTK_WIDGET(w), TRUE);
1114
1115 g_signal_connect(G_OBJECT(w), "button-press-event", G_CALLBACK(dt_bauhaus_combobox_button_press),
1116 (gpointer)NULL);
1117 g_signal_connect(G_OBJECT(w), "button-release-event", G_CALLBACK(dt_bauhaus_popup_button_release),
1118 (gpointer)NULL);
1119 g_signal_connect(G_OBJECT(w), "scroll-event", G_CALLBACK(dt_bauhaus_combobox_scroll), (gpointer)NULL);
1120 g_signal_connect(G_OBJECT(w), "key-press-event", G_CALLBACK(dt_bauhaus_combobox_key_press), (gpointer)NULL);
1121 g_signal_connect(G_OBJECT(w), "motion-notify-event", G_CALLBACK(dt_bauhaus_combobox_motion_notify),
1122 (gpointer)NULL);
1123 g_signal_connect(G_OBJECT(w), "destroy", G_CALLBACK(dt_bauhaus_combobox_destroy), (gpointer)NULL);
1124 }
1125
dt_bauhaus_combobox_add_populate_fct(GtkWidget * widget,void (* fct)(GtkWidget * w,struct dt_iop_module_t ** module))1126 void dt_bauhaus_combobox_add_populate_fct(GtkWidget *widget, void (*fct)(GtkWidget *w, struct dt_iop_module_t **module))
1127 {
1128 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1129 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1130 w->combo_populate = fct;
1131 }
1132
dt_bauhaus_combobox_add_list(GtkWidget * widget,dt_action_t * action,const char ** texts)1133 void dt_bauhaus_combobox_add_list(GtkWidget *widget, dt_action_t *action, const char **texts)
1134 {
1135 if(action)
1136 g_hash_table_insert(darktable.control->combo_list, action, texts);
1137
1138 while(texts && *texts)
1139 dt_bauhaus_combobox_add_full(widget, _(*(texts++)), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, NULL, NULL, TRUE);
1140 }
1141
dt_bauhaus_combobox_add(GtkWidget * widget,const char * text)1142 void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
1143 {
1144 dt_bauhaus_combobox_add_full(widget, text, DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, NULL, NULL, TRUE);
1145 }
1146
dt_bauhaus_combobox_add_section(GtkWidget * widget,const char * text)1147 void dt_bauhaus_combobox_add_section(GtkWidget *widget, const char *text)
1148 {
1149 dt_bauhaus_combobox_add_full(widget, text, DT_BAUHAUS_COMBOBOX_ALIGN_LEFT, NULL, NULL, FALSE);
1150 }
1151
dt_bauhaus_combobox_add_aligned(GtkWidget * widget,const char * text,dt_bauhaus_combobox_alignment_t align)1152 void dt_bauhaus_combobox_add_aligned(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align)
1153 {
1154 dt_bauhaus_combobox_add_full(widget, text, align, NULL, NULL, TRUE);
1155 }
1156
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)1157 void dt_bauhaus_combobox_add_full(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align,
1158 gpointer data, void (free_func)(void *data), gboolean sensitive)
1159 {
1160 if(darktable.control->accel_initialising) return;
1161
1162 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1163 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1164 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1165 d->num_labels++;
1166 dt_bauhaus_combobox_entry_t *entry = new_combobox_entry(text, align, sensitive, data, free_func);
1167 d->entries = g_list_append(d->entries, entry);
1168 if(d->active < 0) d->active = 0;
1169 }
1170
dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget * widget,PangoEllipsizeMode ellipis)1171 void dt_bauhaus_combobox_set_entries_ellipsis(GtkWidget *widget, PangoEllipsizeMode ellipis)
1172 {
1173 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1174 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1175 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1176 d->entries_ellipsis = ellipis;
1177 }
1178
dt_bauhaus_combobox_get_entries_ellipsis(GtkWidget * widget)1179 PangoEllipsizeMode dt_bauhaus_combobox_get_entries_ellipsis(GtkWidget *widget)
1180 {
1181 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1182 if(w->type != DT_BAUHAUS_COMBOBOX) return PANGO_ELLIPSIZE_END;
1183 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1184 return d->entries_ellipsis;
1185 }
1186
dt_bauhaus_combobox_set_editable(GtkWidget * widget,int editable)1187 void dt_bauhaus_combobox_set_editable(GtkWidget *widget, int editable)
1188 {
1189 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1190 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1191 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1192 d->editable = editable ? 1 : 0;
1193 }
1194
dt_bauhaus_combobox_get_editable(GtkWidget * widget)1195 int dt_bauhaus_combobox_get_editable(GtkWidget *widget)
1196 {
1197 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1198 if(w->type != DT_BAUHAUS_COMBOBOX) return 0;
1199 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1200 return d->editable;
1201 }
1202
dt_bauhaus_combobox_set_popup_scale(GtkWidget * widget,const int scale)1203 void dt_bauhaus_combobox_set_popup_scale(GtkWidget *widget, const int scale)
1204 {
1205 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1206 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1207 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1208 d->scale = scale;
1209 }
1210
dt_bauhaus_combobox_set_selected_text_align(GtkWidget * widget,const dt_bauhaus_combobox_alignment_t text_align)1211 void dt_bauhaus_combobox_set_selected_text_align(GtkWidget *widget, const dt_bauhaus_combobox_alignment_t text_align)
1212 {
1213 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1214 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1215 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1216 d->text_align = text_align;
1217 }
1218
dt_bauhaus_combobox_remove_at(GtkWidget * widget,int pos)1219 void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
1220 {
1221 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1222 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1223 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1224
1225 if(pos < 0 || pos >= d->num_labels) return;
1226
1227 // move active position up if removing anything before it
1228 // or when removing last position that is currently active.
1229 // this also sets active to -1 when removing the last remaining entry in a combobox.
1230 if(d->active > pos)
1231 d->active--;
1232 else if((d->active == pos) && (d->active >= d->num_labels-1))
1233 d->active = d->num_labels-2;
1234
1235 GList *rm = g_list_nth(d->entries, pos);
1236 free_combobox_entry(rm->data);
1237 d->entries = g_list_delete_link(d->entries, rm);
1238
1239 d->num_labels--;
1240 }
1241
dt_bauhaus_combobox_insert(GtkWidget * widget,const char * text,int pos)1242 void dt_bauhaus_combobox_insert(GtkWidget *widget, const char *text,int pos)
1243 {
1244 dt_bauhaus_combobox_insert_full(widget, text, DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, NULL, NULL, pos);
1245 }
1246
dt_bauhaus_combobox_insert_full(GtkWidget * widget,const char * text,dt_bauhaus_combobox_alignment_t align,gpointer data,void (* free_func)(void *),int pos)1247 void dt_bauhaus_combobox_insert_full(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align,
1248 gpointer data, void (*free_func)(void *), int pos)
1249 {
1250 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1251 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1252 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1253 d->num_labels++;
1254 d->entries = g_list_insert(d->entries, new_combobox_entry(text, align, TRUE, data, free_func), pos);
1255 if(d->active < 0) d->active = 0;
1256 }
1257
dt_bauhaus_combobox_length(GtkWidget * widget)1258 int dt_bauhaus_combobox_length(GtkWidget *widget)
1259 {
1260 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1261 if(w->type != DT_BAUHAUS_COMBOBOX) return 0;
1262 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1263
1264 return d->num_labels;
1265 }
1266
dt_bauhaus_combobox_get_text(GtkWidget * widget)1267 const char *dt_bauhaus_combobox_get_text(GtkWidget *widget)
1268 {
1269 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1270 if(w->type != DT_BAUHAUS_COMBOBOX) return NULL;
1271 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1272
1273 if(d->editable && d->active < 0)
1274 {
1275 return d->text;
1276 }
1277 else
1278 {
1279 if(d->active < 0 || d->active >= d->num_labels) return NULL;
1280 const dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)g_list_nth_data(d->entries, d->active);
1281 return entry->label;
1282 }
1283 return NULL;
1284 }
1285
dt_bauhaus_combobox_get_data(GtkWidget * widget)1286 gpointer dt_bauhaus_combobox_get_data(GtkWidget *widget)
1287 {
1288 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1289 if(w->type != DT_BAUHAUS_COMBOBOX) return NULL;
1290 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1291 const dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)g_list_nth_data(d->entries, d->active);
1292 return entry ? entry->data : NULL;
1293 }
1294
dt_bauhaus_combobox_clear(GtkWidget * widget)1295 void dt_bauhaus_combobox_clear(GtkWidget *widget)
1296 {
1297 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1298 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1299 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1300 d->active = -1;
1301 g_list_free_full(d->entries, free_combobox_entry);
1302 d->entries = NULL;
1303 d->num_labels = 0;
1304 }
1305
dt_bauhaus_combobox_get_entries(GtkWidget * widget)1306 const GList *dt_bauhaus_combobox_get_entries(GtkWidget *widget)
1307 {
1308 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1309 if(w->type != DT_BAUHAUS_COMBOBOX) return 0;
1310 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1311 return d->entries;
1312 }
1313
dt_bauhaus_combobox_set_text(GtkWidget * widget,const char * text)1314 void dt_bauhaus_combobox_set_text(GtkWidget *widget, const char *text)
1315 {
1316 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1317 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1318 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1319 if(!d->editable) return;
1320 g_strlcpy(d->text, text, sizeof(d->text));
1321 }
1322
_bauhaus_combobox_set(GtkWidget * widget,const int pos,const gboolean mute)1323 static void _bauhaus_combobox_set(GtkWidget *widget, const int pos, const gboolean mute)
1324 {
1325 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1326 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1327 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1328 d->active = CLAMP(pos, -1, d->num_labels - 1);
1329 gtk_widget_queue_draw(GTK_WIDGET(w));
1330 if(!darktable.gui->reset && !mute) g_signal_emit_by_name(G_OBJECT(w), "value-changed");
1331 }
1332
dt_bauhaus_combobox_set(GtkWidget * widget,const int pos)1333 void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
1334 {
1335 _bauhaus_combobox_set(widget, pos, FALSE);
1336 }
1337
dt_bauhaus_combobox_set_from_text(GtkWidget * widget,const char * text)1338 gboolean dt_bauhaus_combobox_set_from_text(GtkWidget *widget, const char *text)
1339 {
1340 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1341 if(w->type != DT_BAUHAUS_COMBOBOX) return FALSE;
1342 if(!text) return FALSE;
1343 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1344 int i = 0;
1345 for(GList *iter = d->entries; iter; iter = g_list_next(iter), i++)
1346 {
1347 const dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)iter->data;
1348 if(!g_strcmp0(entry->label, text))
1349 {
1350 dt_bauhaus_combobox_set(widget, i);
1351 return TRUE;
1352 }
1353 }
1354 return FALSE;
1355 }
1356
dt_bauhaus_combobox_set_from_value(GtkWidget * widget,int value)1357 gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
1358 {
1359 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1360 if(w->type != DT_BAUHAUS_COMBOBOX) return FALSE;
1361 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1362 int i = 0;
1363 for(GList *iter = d->entries; iter; iter = g_list_next(iter), i++)
1364 {
1365 const dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)iter->data;
1366 if(GPOINTER_TO_INT(entry->data) == value)
1367 {
1368 dt_bauhaus_combobox_set(widget, i);
1369 return TRUE;
1370 }
1371 }
1372 return FALSE;
1373 }
1374
dt_bauhaus_combobox_get(GtkWidget * widget)1375 int dt_bauhaus_combobox_get(GtkWidget *widget)
1376 {
1377 const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1378 if(w->type != DT_BAUHAUS_COMBOBOX) return -1;
1379 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1380 return d->active;
1381 }
1382
dt_bauhaus_combobox_entry_set_sensitive(GtkWidget * widget,int pos,gboolean sensitive)1383 void dt_bauhaus_combobox_entry_set_sensitive(GtkWidget *widget, int pos, gboolean sensitive)
1384 {
1385 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1386 if(w->type != DT_BAUHAUS_COMBOBOX) return;
1387 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1388 dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)g_list_nth_data(d->entries, pos);
1389 if(entry)
1390 entry->sensitive = sensitive;
1391 }
1392
dt_bauhaus_slider_clear_stops(GtkWidget * widget)1393 void dt_bauhaus_slider_clear_stops(GtkWidget *widget)
1394 {
1395 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1396 if(w->type != DT_BAUHAUS_SLIDER) return;
1397 dt_bauhaus_slider_data_t *d = &w->data.slider;
1398 d->grad_cnt = 0;
1399 }
1400
dt_bauhaus_slider_set_stop(GtkWidget * widget,float stop,float r,float g,float b)1401 void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
1402 {
1403 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1404 if(w->type != DT_BAUHAUS_SLIDER) return;
1405 dt_bauhaus_slider_data_t *d = &w->data.slider;
1406 // need to replace stop?
1407 for(int k = 0; k < d->grad_cnt; k++)
1408 {
1409 if(d->grad_pos[k] == stop)
1410 {
1411 d->grad_col[k][0] = r;
1412 d->grad_col[k][1] = g;
1413 d->grad_col[k][2] = b;
1414 return;
1415 }
1416 }
1417 // new stop:
1418 if(d->grad_cnt < DT_BAUHAUS_SLIDER_MAX_STOPS)
1419 {
1420 int k = d->grad_cnt++;
1421 d->grad_pos[k] = stop;
1422 d->grad_col[k][0] = r;
1423 d->grad_col[k][1] = g;
1424 d->grad_col[k][2] = b;
1425 }
1426 else
1427 {
1428 fprintf(stderr, "[bauhaus_slider_set_stop] only %d stops allowed.\n", DT_BAUHAUS_SLIDER_MAX_STOPS);
1429 }
1430 }
1431
1432
draw_equilateral_triangle(cairo_t * cr,float radius)1433 static void draw_equilateral_triangle(cairo_t *cr, float radius)
1434 {
1435 const float sin = 0.866025404 * radius;
1436 const float cos = 0.5f * radius;
1437 cairo_move_to(cr, 0.0, radius);
1438 cairo_line_to(cr, -sin, -cos);
1439 cairo_line_to(cr, sin, -cos);
1440 cairo_line_to(cr, 0.0, radius);
1441 }
1442
1443
dt_bauhaus_draw_indicator(dt_bauhaus_widget_t * w,float pos,cairo_t * cr,float wd,const GdkRGBA fg_color,const GdkRGBA border_color)1444 static void dt_bauhaus_draw_indicator(dt_bauhaus_widget_t *w, float pos, cairo_t *cr, float wd, const GdkRGBA fg_color, const GdkRGBA border_color)
1445 {
1446 // draw scale indicator (the tiny triangle)
1447 if(w->type != DT_BAUHAUS_SLIDER) return;
1448
1449 const float border_width = darktable.bauhaus->border_width;
1450 const float size = darktable.bauhaus->marker_size;
1451
1452 cairo_save(cr);
1453 cairo_translate(cr, slider_coordinate(pos, wd), get_line_height() + INNER_PADDING - border_width);
1454 cairo_scale(cr, 1.0f, -1.0f);
1455 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1456
1457 // draw the outer triangle
1458 draw_equilateral_triangle(cr, size);
1459 cairo_set_line_width(cr, border_width);
1460 set_color(cr, border_color);
1461 cairo_stroke(cr);
1462
1463 draw_equilateral_triangle(cr, size - border_width);
1464 cairo_clip(cr);
1465
1466 // draw the inner triangle
1467 draw_equilateral_triangle(cr, size - border_width);
1468 set_color(cr, fg_color);
1469 cairo_set_line_width(cr, border_width);
1470
1471 const dt_bauhaus_slider_data_t *d = &w->data.slider;
1472
1473 if(d->fill_feedback)
1474 cairo_fill(cr); // Plain indicator (regular sliders)
1475 else
1476 cairo_stroke(cr); // Hollow indicator to see a color through it (gradient sliders)
1477
1478 cairo_restore(cr);
1479 }
1480
dt_bauhaus_draw_quad(dt_bauhaus_widget_t * w,cairo_t * cr)1481 static void dt_bauhaus_draw_quad(dt_bauhaus_widget_t *w, cairo_t *cr)
1482 {
1483 GtkWidget *widget = GTK_WIDGET(w);
1484 const gboolean sensitive = gtk_widget_is_sensitive(GTK_WIDGET(w));
1485 GtkAllocation allocation;
1486 gtk_widget_get_allocation(widget, &allocation);
1487 const int width = allocation.width;
1488 const int height = inner_height(allocation);
1489
1490 if(w->quad_paint)
1491 {
1492 cairo_save(cr);
1493
1494 if(sensitive && (w->quad_paint_flags & CPF_ACTIVE))
1495 set_color(cr, darktable.bauhaus->color_fg);
1496 else
1497 set_color(cr, darktable.bauhaus->color_fg_insensitive);
1498
1499 w->quad_paint(cr, width - darktable.bauhaus->quad_width, // x
1500 0.0, // y
1501 darktable.bauhaus->quad_width, // width
1502 darktable.bauhaus->quad_width, // height
1503 w->quad_paint_flags, w->quad_paint_data);
1504
1505 cairo_restore(cr);
1506 }
1507 else
1508 {
1509 // draw active area square:
1510 cairo_save(cr);
1511 if(sensitive)
1512 set_color(cr, darktable.bauhaus->color_fg);
1513 else
1514 set_color(cr, darktable.bauhaus->color_fg_insensitive);
1515 switch(w->type)
1516 {
1517 case DT_BAUHAUS_COMBOBOX:
1518 cairo_translate(cr, width - darktable.bauhaus->quad_width * .5f, height * .33f);
1519 draw_equilateral_triangle(cr, darktable.bauhaus->quad_width * .25f);
1520 cairo_fill_preserve(cr);
1521 cairo_set_line_width(cr, 0.5);
1522 set_color(cr, darktable.bauhaus->color_border);
1523 cairo_stroke(cr);
1524 break;
1525 case DT_BAUHAUS_SLIDER:
1526 break;
1527 default:
1528 cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
1529 cairo_rectangle(cr, width - darktable.bauhaus->quad_width, 0.0, darktable.bauhaus->quad_width, darktable.bauhaus->quad_width);
1530 cairo_fill(cr);
1531 break;
1532 }
1533 cairo_restore(cr);
1534 }
1535 }
1536
dt_bauhaus_draw_baseline(dt_bauhaus_widget_t * w,cairo_t * cr,float width)1537 static void dt_bauhaus_draw_baseline(dt_bauhaus_widget_t *w, cairo_t *cr, float width)
1538 {
1539 // draw line for orientation in slider
1540 if(w->type != DT_BAUHAUS_SLIDER) return;
1541
1542 const float slider_width = width - darktable.bauhaus->quad_width - INNER_PADDING;
1543 cairo_save(cr);
1544 dt_bauhaus_slider_data_t *d = &w->data.slider;
1545
1546 // pos of baseline
1547 const float htm = darktable.bauhaus->line_height + INNER_PADDING;
1548
1549 // thickness of baseline
1550 const float htM = darktable.bauhaus->baseline_size - darktable.bauhaus->border_width;
1551
1552 // the background of the line
1553 cairo_pattern_t *gradient = NULL;
1554 cairo_rectangle(cr, 0, htm, slider_width, htM);
1555
1556 if(d->grad_cnt > 0)
1557 {
1558 // gradient line as used in some modules
1559 gradient = cairo_pattern_create_linear(0, 0, slider_width, htM);
1560 for(int k = 0; k < d->grad_cnt; k++)
1561 cairo_pattern_add_color_stop_rgba(gradient, d->grad_pos[k], d->grad_col[k][0], d->grad_col[k][1],
1562 d->grad_col[k][2], 0.4f);
1563 cairo_set_source(cr, gradient);
1564 }
1565 else
1566 {
1567 // regular baseline
1568 set_color(cr, darktable.bauhaus->color_bg);
1569 }
1570
1571 cairo_fill(cr);
1572
1573 // get the reference of the slider aka the position of the 0 value
1574 const float origin = fmaxf(fminf((d->factor > 0 ? -d->min - d->offset/d->factor
1575 : d->max + d->offset/d->factor)
1576 / (d->max - d->min), 1.0f) * slider_width, 0.0f);
1577 const float position = d->pos * slider_width;
1578 const float delta = position - origin;
1579
1580 // have a `fill ratio feel' from zero to current position
1581 // - but only if set
1582 if(d->fill_feedback)
1583 {
1584 // only brighten, useful for colored sliders to not get too faint:
1585 cairo_set_operator(cr, CAIRO_OPERATOR_SCREEN);
1586 set_color(cr, darktable.bauhaus->color_fill);
1587 cairo_rectangle(cr, origin, htm, delta, htM);
1588 cairo_fill(cr);
1589
1590 // change back to default cairo operator:
1591 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1592 }
1593
1594 // draw the 0 reference graduation if it's different than the bounds of the slider
1595 const float graduation_top = htm + htM + 2.0f * darktable.bauhaus->border_width;
1596 const float graduation_height = darktable.bauhaus->border_width / 2.0f;
1597 set_color(cr, darktable.bauhaus->color_fg);
1598
1599 // If the max of the slider is 180 or 360, it is likely a hue slider in degrees
1600 // a zero in periodic stuff has not much meaning so we skip it
1601 if(d->hard_max != 180.0f && d->hard_max != 360.0f)
1602 {
1603 // translate the dot if it overflows the widget frame
1604 if(origin < graduation_height)
1605 cairo_arc(cr, graduation_height, graduation_top, graduation_height, 0, 2 * M_PI);
1606 else if(origin > slider_width - graduation_height)
1607 cairo_arc(cr, slider_width - graduation_height, graduation_top, graduation_height, 0, 2 * M_PI);
1608 else
1609 cairo_arc(cr, origin, graduation_top, graduation_height, 0, 2 * M_PI);
1610 }
1611
1612 cairo_fill(cr);
1613 cairo_restore(cr);
1614
1615 if(d->grad_cnt > 0) cairo_pattern_destroy(gradient);
1616 }
1617
dt_bauhaus_widget_reject(dt_bauhaus_widget_t * w)1618 static void dt_bauhaus_widget_reject(dt_bauhaus_widget_t *w)
1619 {
1620 switch(w->type)
1621 {
1622 case DT_BAUHAUS_COMBOBOX:
1623 break;
1624 case DT_BAUHAUS_SLIDER:
1625 {
1626 dt_bauhaus_slider_data_t *d = &w->data.slider;
1627 dt_bauhaus_slider_set_normalized(w, d->oldpos);
1628 }
1629 break;
1630 default:
1631 break;
1632 }
1633 }
1634
dt_bauhaus_widget_accept(dt_bauhaus_widget_t * w)1635 static void dt_bauhaus_widget_accept(dt_bauhaus_widget_t *w)
1636 {
1637 GtkWidget *widget = GTK_WIDGET(w);
1638
1639 GtkAllocation allocation_popup_window;
1640 gtk_widget_get_allocation(darktable.bauhaus->popup_window, &allocation_popup_window);
1641
1642 const int width = allocation_popup_window.width, height = inner_height(allocation_popup_window);
1643 const int base_width = width - darktable.bauhaus->widget_space;
1644 const int base_height = darktable.bauhaus->line_height + darktable.bauhaus->widget_space * 2.0f + INNER_PADDING * 2.0f;
1645
1646 switch(w->type)
1647 {
1648 case DT_BAUHAUS_COMBOBOX:
1649 {
1650 // only set to what's in the filtered list.
1651 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1652 const int active = darktable.bauhaus->end_mouse_y >= 0
1653 ? ((darktable.bauhaus->end_mouse_y - darktable.bauhaus->widget_space) / (darktable.bauhaus->line_height))
1654 : d->active;
1655
1656 int k = 0, i = 0, kk = 0, match = 1;
1657
1658 gchar *keys = g_utf8_casefold(darktable.bauhaus->keys, -1);
1659 for(GList *it = d->entries; it; it = g_list_next(it))
1660 {
1661 const dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)it->data;
1662 gchar *text_cmp = g_utf8_casefold(entry->label, -1);
1663 if(!strncmp(text_cmp, keys, darktable.bauhaus->keys_cnt))
1664 {
1665 if(active == k)
1666 {
1667 if(entry->sensitive)
1668 dt_bauhaus_combobox_set(widget, i);
1669 g_free(keys);
1670 g_free(text_cmp);
1671 return;
1672 }
1673 kk = i; // remember for down there
1674 // editable should only snap to perfect matches, not prefixes:
1675 if(d->editable && strcmp(entry->label, darktable.bauhaus->keys)) match = 0;
1676 k++;
1677 }
1678 i++;
1679 g_free(text_cmp);
1680 }
1681 // didn't find it, but had only one matching choice?
1682 if(k == 1 && match)
1683 dt_bauhaus_combobox_set(widget, kk);
1684 else if(d->editable)
1685 {
1686 // otherwise, if combobox is editable, assume it is a custom input
1687 memset(d->text, 0, sizeof(d->text));
1688 g_strlcpy(d->text, darktable.bauhaus->keys, sizeof(d->text));
1689 // select custom entry
1690 dt_bauhaus_combobox_set(widget, -1);
1691 }
1692 g_free(keys);
1693 break;
1694 }
1695 case DT_BAUHAUS_SLIDER:
1696 {
1697 dt_bauhaus_slider_data_t *d = &w->data.slider;
1698 const float mouse_off = get_slider_line_offset(
1699 d->oldpos, d->scale, darktable.bauhaus->end_mouse_x / width,
1700 darktable.bauhaus->end_mouse_y / height, base_height / (float)height, base_width);
1701 dt_bauhaus_slider_set_normalized(w, d->oldpos + mouse_off);
1702 d->oldpos = d->pos;
1703 break;
1704 }
1705 default:
1706 break;
1707 }
1708 }
1709
_build_label(const dt_bauhaus_widget_t * w)1710 static gchar *_build_label(const dt_bauhaus_widget_t *w)
1711 {
1712 if(w->show_extended_label && w->section)
1713 return g_strdup_printf("%s - %s", w->section, w->label);
1714 else
1715 return g_strdup(w->label);
1716 }
1717
dt_bauhaus_popup_draw(GtkWidget * widget,cairo_t * crf,gpointer user_data)1718 static gboolean dt_bauhaus_popup_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1719 {
1720 dt_bauhaus_widget_t *w = darktable.bauhaus->current;
1721
1722 // dimensions of the popup
1723 GtkAllocation allocation;
1724 gtk_widget_get_allocation(widget, &allocation);
1725 const int width = allocation.width - INNER_PADDING;
1726 const int height = inner_height(allocation);
1727
1728 // dimensions of the original line
1729 int wd = width - darktable.bauhaus->widget_space;
1730 int ht = darktable.bauhaus->line_height + darktable.bauhaus->widget_space * 2.0f + INNER_PADDING * 2.0f;
1731
1732 const int popwin_wd = allocation.width + darktable.bauhaus->widget_space * 2.0f;
1733 const int popwin_ht = allocation.height + darktable.bauhaus->widget_space * 2.0f;
1734
1735 // get area properties
1736 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1737 popwin_wd, popwin_ht);
1738
1739 cairo_t *cr = cairo_create(cst);
1740 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1741
1742 // look up some colors once
1743 GdkRGBA text_color, text_color_selected, text_color_hover, text_color_insensitive;
1744 gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &text_color);
1745 gtk_style_context_get_color(context, GTK_STATE_FLAG_SELECTED, &text_color_selected);
1746 gtk_style_context_get_color(context, GTK_STATE_FLAG_PRELIGHT, &text_color_hover);
1747 gtk_style_context_get_color(context, GTK_STATE_FLAG_INSENSITIVE, &text_color_insensitive);
1748
1749 GdkRGBA *fg_color = default_color_assign();
1750 GdkRGBA *bg_color;
1751 GtkStateFlags state = gtk_widget_get_state_flags(widget);
1752
1753 gtk_style_context_get(context, state, "background-color", &bg_color, NULL);
1754 gtk_style_context_get_color(context, state, fg_color);
1755
1756 // draw background
1757 gtk_render_background(context, cr, 0, 0, popwin_wd, popwin_ht);
1758
1759 // draw border
1760 cairo_save(cr);
1761 set_color(cr, *fg_color);
1762 cairo_set_line_width(cr, darktable.bauhaus->widget_space);
1763 cairo_rectangle(cr, 0, 0, popwin_wd - 2, popwin_ht - 2);
1764 cairo_stroke(cr);
1765 cairo_restore(cr);
1766
1767 // translate to account for the widget spacing
1768 cairo_translate(cr, darktable.bauhaus->widget_space, darktable.bauhaus->widget_space);
1769
1770 // switch on bauhaus widget type (so we only need one static window)
1771 switch(w->type)
1772 {
1773 case DT_BAUHAUS_SLIDER:
1774 {
1775 const dt_bauhaus_slider_data_t *d = &w->data.slider;
1776
1777 cairo_translate(cr, INNER_PADDING, 0);
1778
1779 dt_bauhaus_draw_baseline(w, cr, wd);
1780
1781 cairo_save(cr);
1782 cairo_set_line_width(cr, 0.5);
1783 const int num_scales = 1.f / d->scale;
1784
1785 cairo_rectangle(cr, - INNER_PADDING, ht, width + INNER_PADDING, height);
1786 cairo_clip(cr);
1787
1788 for(int k = 0; k < num_scales; k++)
1789 {
1790 const float off = k * d->scale - d->oldpos;
1791 GdkRGBA fg_copy = *fg_color;
1792 fg_copy.alpha = d->scale / fabsf(off);
1793 set_color(cr, fg_copy);
1794 draw_slider_line(cr, d->oldpos, off, d->scale, width, height, ht);
1795 cairo_stroke(cr);
1796 }
1797 cairo_restore(cr);
1798 set_color(cr, *fg_color);
1799
1800 // draw mouse over indicator line
1801 cairo_save(cr);
1802 cairo_set_line_width(cr, 2.);
1803 const float mouse_off
1804 = darktable.bauhaus->change_active
1805 ? get_slider_line_offset(d->oldpos, d->scale, (darktable.bauhaus->mouse_x - INNER_PADDING)/ width,
1806 darktable.bauhaus->mouse_y / height, ht / (float)height, width)
1807 : 0.0f;
1808 draw_slider_line(cr, d->oldpos, mouse_off, d->scale, width, height, ht);
1809 cairo_stroke(cr);
1810 cairo_restore(cr);
1811
1812 // draw indicator
1813 dt_bauhaus_draw_indicator(w, d->oldpos + mouse_off, cr, wd, *fg_color, *bg_color);
1814
1815 // draw numerical value:
1816 cairo_save(cr);
1817
1818 char *text = dt_bauhaus_slider_get_text(GTK_WIDGET(w));
1819 set_color(cr, *fg_color);
1820 const float value_width =
1821 show_pango_text(w, context, cr, text, wd - darktable.bauhaus->quad_width - INNER_PADDING,
1822 0, 0, TRUE, FALSE, PANGO_ELLIPSIZE_END, FALSE, FALSE);
1823 g_free(text);
1824
1825 const float label_width = width - darktable.bauhaus->quad_width - INNER_PADDING * 2.0 - value_width;
1826 if(label_width > 0)
1827 {
1828 gchar *lb = _build_label(w);
1829 show_pango_text(w, context, cr, lb, 0, 0, label_width, FALSE, FALSE, PANGO_ELLIPSIZE_END, FALSE, FALSE);
1830 g_free(lb);
1831 }
1832 cairo_restore(cr);
1833 }
1834 break;
1835 case DT_BAUHAUS_COMBOBOX:
1836 {
1837 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1838 cairo_save(cr);
1839 float first_label_width = 0.0;
1840 gboolean first_label = TRUE;
1841 gboolean show_box_label = TRUE;
1842 int k = 0, i = 0;
1843 const int hovered = (darktable.bauhaus->mouse_y - darktable.bauhaus->widget_space) / darktable.bauhaus->line_height;
1844 gchar *keys = g_utf8_casefold(darktable.bauhaus->keys, -1);
1845 const PangoEllipsizeMode ellipsis = d->entries_ellipsis;
1846 ht = darktable.bauhaus->line_height;
1847
1848 for(GList *it = d->entries; it; it = g_list_next(it))
1849 {
1850 const dt_bauhaus_combobox_entry_t *entry = (dt_bauhaus_combobox_entry_t *)it->data;
1851 gchar *text_cmp = g_utf8_casefold(entry->label, -1);
1852 if(!strncmp(text_cmp, keys, darktable.bauhaus->keys_cnt))
1853 {
1854 float max_width = wd - INNER_PADDING - darktable.bauhaus->quad_width;
1855 if(first_label) max_width *= 0.8; // give the label at least some room
1856 float label_width = 0.0f;
1857 if(!entry->sensitive)
1858 set_color(cr, text_color_insensitive);
1859 else if(i == hovered)
1860 set_color(cr, text_color_hover);
1861 else if(i == d->active)
1862 set_color(cr, text_color_selected);
1863 else
1864 set_color(cr, text_color);
1865
1866 if(entry->alignment == DT_BAUHAUS_COMBOBOX_ALIGN_LEFT)
1867 {
1868 gchar *esc_label = g_markup_escape_text(entry->label, -1);
1869 gchar *label = g_strdup_printf("<b>%s</b>", esc_label);
1870 label_width = show_pango_text(w, context, cr, label, INNER_PADDING, ht * k + darktable.bauhaus->widget_space,
1871 max_width, FALSE, FALSE, ellipsis, TRUE, FALSE);
1872 g_free(label);
1873 g_free(esc_label);
1874 }
1875 else
1876 label_width
1877 = show_pango_text(w, context, cr, entry->label, wd - darktable.bauhaus->quad_width,
1878 ht * k + darktable.bauhaus->widget_space, max_width, TRUE, FALSE, ellipsis, FALSE, FALSE);
1879
1880 // prefer the entry over the label wrt. ellipsization when expanded
1881 if(first_label)
1882 {
1883 show_box_label = entry->alignment == DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT;
1884 first_label_width = label_width;
1885 first_label = FALSE;
1886 }
1887
1888 k++;
1889 }
1890 i++;
1891 g_free(text_cmp);
1892 }
1893 cairo_restore(cr);
1894
1895 // left aligned box label. add it to the gui after the entries so we can ellipsize it if needed
1896 if(show_box_label)
1897 {
1898 set_color(cr, text_color);
1899 gchar *lb = _build_label(w);
1900 show_pango_text(w, context, cr, lb, INNER_PADDING, darktable.bauhaus->widget_space,
1901 wd - INNER_PADDING - darktable.bauhaus->quad_width - first_label_width, FALSE, FALSE,
1902 PANGO_ELLIPSIZE_END, FALSE, TRUE);
1903 g_free(lb);
1904 }
1905 g_free(keys);
1906 }
1907 break;
1908 default:
1909 // yell
1910 break;
1911 }
1912
1913 // draw currently typed text. if a type doesn't want this, it should not
1914 // allow stuff to be written here in the key callback.
1915 const int line_height = get_line_height();
1916 const int size = MIN(3 * line_height, .2 * height);
1917 if(darktable.bauhaus->keys_cnt)
1918 {
1919 cairo_save(cr);
1920 PangoLayout *layout = pango_cairo_create_layout(cr);
1921 PangoRectangle ink;
1922 pango_cairo_context_set_resolution(pango_layout_get_context(layout), darktable.gui->dpi);
1923 set_color(cr, text_color);
1924
1925 // make extra large, but without dependency on popup window height
1926 // (that might differ for comboboxes for example). only fall back
1927 // to height dependency if the popup is really small.
1928 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
1929 pango_font_description_set_absolute_size(desc, size * PANGO_SCALE);
1930 pango_layout_set_font_description(layout, desc);
1931
1932 pango_layout_set_text(layout, darktable.bauhaus->keys, -1);
1933 pango_layout_get_pixel_extents(layout, &ink, NULL);
1934 cairo_move_to(cr, wd - INNER_PADDING - darktable.bauhaus->quad_width - ink.width, height * 0.5 - size);
1935 pango_cairo_show_layout(cr, layout);
1936 cairo_restore(cr);
1937 pango_font_description_free(desc);
1938 g_object_unref(layout);
1939 }
1940 if(darktable.bauhaus->cursor_visible)
1941 {
1942 // show the blinking cursor
1943 cairo_save(cr);
1944 set_color(cr, text_color);
1945 cairo_move_to(cr, wd - darktable.bauhaus->quad_width + 3, height * 0.5 + size/3);
1946 cairo_line_to(cr, wd - darktable.bauhaus->quad_width + 3, height * 0.5 - size);
1947 cairo_set_line_width(cr, 2.);
1948 cairo_stroke(cr);
1949 cairo_restore(cr);
1950 }
1951
1952 cairo_destroy(cr);
1953 cairo_set_source_surface(crf, cst, 0, 0);
1954 cairo_paint(crf);
1955 cairo_surface_destroy(cst);
1956
1957 gdk_rgba_free(bg_color);
1958 gdk_rgba_free(fg_color);
1959
1960 return TRUE;
1961 }
1962
dt_bauhaus_draw(GtkWidget * widget,cairo_t * crf,gpointer user_data)1963 static gboolean dt_bauhaus_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1964 {
1965 GtkAllocation allocation;
1966 gtk_widget_get_allocation(widget, &allocation);
1967 dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget);
1968 const int width = allocation.width, height = allocation.height;
1969 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1970 cairo_t *cr = cairo_create(cst);
1971 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1972
1973 // translate to account for the widget spacing
1974 cairo_translate(cr, 0, darktable.bauhaus->widget_space);
1975
1976 GdkRGBA *fg_color = default_color_assign();
1977 GdkRGBA *bg_color;
1978 GdkRGBA *text_color = default_color_assign();
1979 const GtkStateFlags state = gtk_widget_get_state_flags(widget);
1980 gtk_style_context_get_color(context, state, text_color);
1981 gtk_render_background(context, cr, 0, 0, width, height + INNER_PADDING);
1982 gtk_style_context_get_color(context, state, fg_color);
1983 gtk_style_context_get(context, state, "background-color", &bg_color, NULL);
1984
1985 // draw type specific content:
1986 cairo_save(cr);
1987 cairo_set_line_width(cr, 1.0);
1988 switch(w->type)
1989 {
1990 case DT_BAUHAUS_COMBOBOX:
1991 {
1992 // draw label and quad area at right end
1993 set_color(cr, *text_color);
1994 dt_bauhaus_draw_quad(w, cr);
1995
1996 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
1997 const PangoEllipsizeMode combo_ellipsis = d->entries_ellipsis;
1998 gchar *text = d->text;
1999 if(d->active >= 0)
2000 {
2001 const dt_bauhaus_combobox_entry_t *entry = g_list_nth_data(d->entries, d->active);
2002 text = entry->label;
2003 }
2004 set_color(cr, *text_color);
2005
2006 const float available_width = width - darktable.bauhaus->quad_width - INNER_PADDING;
2007
2008 //calculate total widths of label and combobox
2009 gchar *label_text = _build_label(w);
2010 const float label_width
2011 = show_pango_text(w, context, cr, label_text, 0, 0, 0, FALSE, TRUE, PANGO_ELLIPSIZE_END, FALSE, TRUE);
2012 const float combo_width
2013 = show_pango_text(w, context, cr, text, width - darktable.bauhaus->quad_width - INNER_PADDING, 0, 0,
2014 TRUE, TRUE, combo_ellipsis, FALSE, FALSE);
2015
2016 //check if they fit
2017 if((label_width + combo_width) > available_width)
2018 {
2019 //they don't fit: evenly divide the available width between the two in proportion
2020 const float ratio = label_width / (label_width + combo_width);
2021 show_pango_text(w, context, cr, label_text, 0, darktable.bauhaus->widget_space,
2022 available_width * ratio - INNER_PADDING * 2, FALSE, FALSE, PANGO_ELLIPSIZE_END, FALSE,
2023 TRUE);
2024 if(d->text_align == DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT)
2025 show_pango_text(w, context, cr, text, width - darktable.bauhaus->quad_width - INNER_PADDING, darktable.bauhaus->widget_space,
2026 available_width * (1.0f - ratio),
2027 TRUE, FALSE, combo_ellipsis, FALSE, FALSE);
2028 else
2029 show_pango_text(w, context, cr, text, INNER_PADDING, darktable.bauhaus->widget_space,
2030 available_width * (1.0f - ratio),
2031 FALSE, FALSE, combo_ellipsis, FALSE, FALSE);
2032 }
2033 else
2034 {
2035 show_pango_text(w, context, cr, label_text, 0, darktable.bauhaus->widget_space, 0, FALSE, FALSE,
2036 PANGO_ELLIPSIZE_END, FALSE, TRUE);
2037 if(d->text_align == DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT)
2038 show_pango_text(w, context, cr, text, width - darktable.bauhaus->quad_width - INNER_PADDING, darktable.bauhaus->widget_space, 0,
2039 TRUE, FALSE, combo_ellipsis, FALSE, FALSE);
2040 else
2041 show_pango_text(w, context, cr, text, INNER_PADDING, darktable.bauhaus->widget_space, 0,
2042 FALSE, FALSE, combo_ellipsis, FALSE, FALSE);
2043 }
2044 g_free(label_text);
2045 break;
2046 }
2047 case DT_BAUHAUS_SLIDER:
2048 {
2049 const dt_bauhaus_slider_data_t *d = &w->data.slider;
2050
2051 // line for orientation
2052 dt_bauhaus_draw_baseline(w, cr, width);
2053 dt_bauhaus_draw_quad(w, cr);
2054
2055 float value_width = 0;
2056 if(gtk_widget_is_sensitive(widget))
2057 {
2058 cairo_save(cr);
2059 cairo_rectangle(cr, 0, 0, width - darktable.bauhaus->quad_width - INNER_PADDING, height + INNER_PADDING);
2060 cairo_clip(cr);
2061 dt_bauhaus_draw_indicator(w, d->pos, cr, width, *fg_color, *bg_color);
2062 cairo_restore(cr);
2063
2064 // TODO: merge that text with combo
2065
2066 char *text = dt_bauhaus_slider_get_text(widget);
2067 set_color(cr, *text_color);
2068 value_width = show_pango_text(w, context, cr, text, width - darktable.bauhaus->quad_width - INNER_PADDING,
2069 0, 0, TRUE, FALSE, PANGO_ELLIPSIZE_END, FALSE, FALSE);
2070 g_free(text);
2071 }
2072 // label on top of marker:
2073 gchar *label_text = _build_label(w);
2074 set_color(cr, *text_color);
2075 const float label_width = width - darktable.bauhaus->quad_width - INNER_PADDING - value_width;
2076 if(label_width > 0)
2077 show_pango_text(w, context, cr, label_text, 0, 0, label_width, FALSE, FALSE, PANGO_ELLIPSIZE_END, FALSE,
2078 TRUE);
2079 g_free(label_text);
2080 }
2081 break;
2082 default:
2083 break;
2084 }
2085 cairo_restore(cr);
2086 cairo_destroy(cr);
2087 cairo_set_source_surface(crf, cst, 0, 0);
2088 cairo_paint(crf);
2089 cairo_surface_destroy(cst);
2090
2091 gdk_rgba_free(text_color);
2092 gdk_rgba_free(fg_color);
2093 gdk_rgba_free(bg_color);
2094
2095 return TRUE;
2096 }
2097
dt_bauhaus_hide_popup()2098 void dt_bauhaus_hide_popup()
2099 {
2100 if(darktable.bauhaus->current)
2101 {
2102 gtk_grab_remove(darktable.bauhaus->popup_window);
2103 gtk_widget_hide(darktable.bauhaus->popup_window);
2104 gtk_window_set_attached_to(GTK_WINDOW(darktable.bauhaus->popup_window), NULL);
2105 darktable.bauhaus->current = NULL;
2106 // TODO: give focus to center view? do in accept() as well?
2107 }
2108 _stop_cursor();
2109 }
2110
dt_bauhaus_show_popup(dt_bauhaus_widget_t * w)2111 void dt_bauhaus_show_popup(dt_bauhaus_widget_t *w)
2112 {
2113 if(darktable.bauhaus->current) dt_bauhaus_hide_popup();
2114 darktable.bauhaus->current = w;
2115 darktable.bauhaus->keys_cnt = 0;
2116 memset(darktable.bauhaus->keys, 0, sizeof(darktable.bauhaus->keys));
2117 darktable.bauhaus->change_active = 0;
2118 darktable.bauhaus->mouse_line_distance = 0.0f;
2119 darktable.bauhaus->hiding = FALSE;
2120 _stop_cursor();
2121
2122 bauhaus_request_focus(w);
2123
2124 gtk_widget_realize(darktable.bauhaus->popup_window);
2125
2126 GtkAllocation tmp;
2127 gtk_widget_get_allocation(GTK_WIDGET(w), &tmp);
2128 if(tmp.width == 1)
2129 {
2130 if(dt_ui_panel_ancestor(darktable.gui->ui, DT_UI_PANEL_RIGHT, GTK_WIDGET(w)))
2131 tmp.width = dt_ui_panel_get_size(darktable.gui->ui, DT_UI_PANEL_RIGHT);
2132 else if(dt_ui_panel_ancestor(darktable.gui->ui, DT_UI_PANEL_LEFT, GTK_WIDGET(w)))
2133 tmp.width = dt_ui_panel_get_size(darktable.gui->ui, DT_UI_PANEL_LEFT);
2134 else
2135 tmp.width = 300;
2136 tmp.width -= INNER_PADDING * 2;
2137 }
2138
2139 GdkWindow *widget_window = gtk_widget_get_window(GTK_WIDGET(w));
2140
2141 gint wx, wy;
2142 GdkDevice *pointer = gdk_seat_get_pointer(gdk_display_get_default_seat(gdk_display_get_default()));
2143 if(widget_window == gdk_device_get_window_at_position(pointer, NULL, NULL))
2144 gdk_window_get_origin(widget_window, &wx, &wy);
2145 else
2146 {
2147 gdk_device_get_position(pointer, NULL, &wx, &wy);
2148 wx -= (tmp.width - darktable.bauhaus->quad_width) / 2;
2149 wy -= darktable.bauhaus->line_height / 2;
2150 }
2151
2152 switch(darktable.bauhaus->current->type)
2153 {
2154 case DT_BAUHAUS_SLIDER:
2155 {
2156 dt_bauhaus_slider_data_t *d = &w->data.slider;
2157 d->oldpos = d->pos;
2158 tmp.height = tmp.width;
2159 _start_cursor(6);
2160 break;
2161 }
2162 case DT_BAUHAUS_COMBOBOX:
2163 {
2164 // we launch the dynamic populate fct if any
2165 dt_iop_module_t *module = (dt_iop_module_t *)(w->module);
2166 if(w->combo_populate) w->combo_populate(GTK_WIDGET(w), &module);
2167 // comboboxes change immediately
2168 darktable.bauhaus->change_active = 1;
2169 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
2170 if(!d->num_labels) return;
2171 tmp.height = darktable.bauhaus->line_height * d->num_labels + 5 * darktable.bauhaus->widget_space;
2172 tmp.width *= d->scale;
2173
2174 GtkAllocation allocation_w;
2175 gtk_widget_get_allocation(GTK_WIDGET(w), &allocation_w);
2176 const int ht = allocation_w.height;
2177 const int skip = darktable.bauhaus->line_height;
2178 wy -= d->active * darktable.bauhaus->line_height;
2179 darktable.bauhaus->mouse_x = 0;
2180 darktable.bauhaus->mouse_y = d->active * skip + ht / 2;
2181 break;
2182 }
2183 default:
2184 break;
2185 }
2186
2187 wx -= darktable.bauhaus->widget_space + INNER_PADDING;
2188 tmp.width += darktable.bauhaus->widget_space + INNER_PADDING;
2189
2190 // gtk_widget_get_window will return null if not shown yet.
2191 // it is needed for gdk_window_move, and gtk_window move will
2192 // sometimes be ignored. this is why we always call both...
2193 // we also don't want to show before move, as this results in noticeable flickering.
2194 GdkWindow *window = gtk_widget_get_window(darktable.bauhaus->popup_window);
2195 if(window) gdk_window_move(window, wx, wy);
2196 gtk_window_move(GTK_WINDOW(darktable.bauhaus->popup_window), wx, wy);
2197 gtk_widget_set_size_request(darktable.bauhaus->popup_area, tmp.width, tmp.height);
2198 gtk_widget_set_size_request(darktable.bauhaus->popup_window, tmp.width, tmp.height);
2199 // gtk_window_set_keep_above isn't enough on macOS
2200 gtk_window_set_attached_to(GTK_WINDOW(darktable.bauhaus->popup_window), GTK_WIDGET(darktable.bauhaus->current));
2201 gtk_widget_show_all(darktable.bauhaus->popup_window);
2202 gtk_widget_grab_focus(darktable.bauhaus->popup_area);
2203 }
2204
dt_bauhaus_slider_add_delta_internal(GtkWidget * widget,float delta,guint state)2205 static gboolean dt_bauhaus_slider_add_delta_internal(GtkWidget *widget, float delta, guint state)
2206 {
2207 if (delta == 0) return TRUE;
2208
2209 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2210 const dt_bauhaus_slider_data_t *d = &w->data.slider;
2211
2212 float multiplier = 0.0f;
2213
2214 if(dt_modifier_is(state, GDK_SHIFT_MASK))
2215 {
2216 multiplier = dt_conf_get_float("darkroom/ui/scale_rough_step_multiplier");
2217 }
2218 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
2219 {
2220 multiplier = dt_conf_get_float("darkroom/ui/scale_precise_step_multiplier");
2221 }
2222 else
2223 {
2224 multiplier = dt_conf_get_float("darkroom/ui/scale_step_multiplier");
2225 }
2226
2227 const float min_visible = powf(10.0f, -d->digits) / (d->max - d->min);
2228 if(fabsf(delta*multiplier) < min_visible)
2229 multiplier = min_visible / fabsf(delta);
2230
2231 delta *= multiplier;
2232
2233 bauhaus_request_focus(w);
2234
2235 dt_bauhaus_slider_set_normalized(w, d->pos + delta);
2236
2237 return TRUE;
2238 }
2239
dt_bauhaus_slider_scroll(GtkWidget * widget,GdkEventScroll * event,gpointer user_data)2240 static gboolean dt_bauhaus_slider_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
2241 {
2242 const dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2243 if(w->type != DT_BAUHAUS_SLIDER) return FALSE;
2244
2245 if(dt_gui_ignore_scroll(event)) return FALSE;
2246
2247 gtk_widget_grab_focus(widget);
2248
2249 int delta_y;
2250 if(dt_gui_get_scroll_unit_delta(event, &delta_y))
2251 {
2252 if(delta_y == 0) return TRUE;
2253 const gdouble delta = delta_y * -w->data.slider.scale / 5.0;
2254 gtk_widget_set_state_flags(GTK_WIDGET(w), GTK_STATE_FLAG_FOCUSED, TRUE);
2255 return dt_bauhaus_slider_add_delta_internal(widget, delta, event->state);
2256 }
2257
2258 return TRUE; // Ensure that scrolling the slider cannot move side panel
2259 }
2260
dt_bauhaus_slider_key_press(GtkWidget * widget,GdkEventKey * event,gpointer user_data)2261 static gboolean dt_bauhaus_slider_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
2262 {
2263 const dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2264 if(w->type != DT_BAUHAUS_SLIDER) return FALSE;
2265 const dt_bauhaus_slider_data_t *d = &w->data.slider;
2266
2267 int handled = 0;
2268 float delta = 0.0f;
2269 if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up || event->keyval == GDK_KEY_Right
2270 || event->keyval == GDK_KEY_KP_Right)
2271 {
2272 handled = 1;
2273 delta = d->scale / 5.0f;
2274 }
2275 else if(event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down || event->keyval == GDK_KEY_Left
2276 || event->keyval == GDK_KEY_KP_Left)
2277 {
2278 handled = 1;
2279 delta = -d->scale / 5.0f;
2280 }
2281
2282 if(handled) return dt_bauhaus_slider_add_delta_internal(widget, delta, event->state);
2283
2284 return FALSE;
2285 }
2286
2287
dt_bauhaus_combobox_scroll(GtkWidget * widget,GdkEventScroll * event,gpointer user_data)2288 static gboolean dt_bauhaus_combobox_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
2289 {
2290 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2291 if(w->type != DT_BAUHAUS_COMBOBOX) return FALSE;
2292
2293 if(dt_gui_ignore_scroll(event)) return FALSE;
2294
2295 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
2296 gtk_widget_grab_focus(widget);
2297
2298 int delta_y = 0;
2299 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
2300 {
2301 bauhaus_request_focus(w);
2302
2303 // go to next sensitive one
2304 int new_pos = CLAMP(d->active + delta_y, 0, d->num_labels - 1);
2305 if(_combobox_next_entry(d->entries, &new_pos, delta_y))
2306 dt_bauhaus_combobox_set(widget, new_pos);
2307 return TRUE;
2308 }
2309 return TRUE; // Ensure that scrolling the combobox cannot move side panel
2310 }
2311
dt_bauhaus_combobox_key_press(GtkWidget * widget,GdkEventKey * event,gpointer user_data)2312 static gboolean dt_bauhaus_combobox_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
2313 {
2314 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2315 if(w->type != DT_BAUHAUS_COMBOBOX) return FALSE;
2316 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
2317 if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up || event->keyval == GDK_KEY_Left
2318 || event->keyval == GDK_KEY_KP_Left)
2319 {
2320 bauhaus_request_focus(w);
2321
2322 // skip insensitive ones
2323 int new_pos = CLAMP(d->active - 1, 0, d->num_labels - 1);
2324 if(_combobox_next_entry(d->entries, &new_pos, -1))
2325 dt_bauhaus_combobox_set(widget, new_pos);
2326 return TRUE;
2327 }
2328 else if(event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down || event->keyval == GDK_KEY_Right
2329 || event->keyval == GDK_KEY_KP_Right)
2330 {
2331 bauhaus_request_focus(w);
2332
2333 // skip insensitive ones
2334 int new_pos = CLAMP(d->active + 1, 0, d->num_labels - 1);
2335 if(_combobox_next_entry(d->entries, &new_pos, 1))
2336 dt_bauhaus_combobox_set(widget, new_pos);
2337 return TRUE;
2338 }
2339 return FALSE;
2340 }
2341
dt_bauhaus_combobox_button_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)2342 static gboolean dt_bauhaus_combobox_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2343 {
2344 GtkAllocation allocation;
2345 gtk_widget_get_allocation(widget, &allocation);
2346 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2347
2348 if(w->type != DT_BAUHAUS_COMBOBOX) return FALSE;
2349 bauhaus_request_focus(w);
2350 gtk_widget_grab_focus(GTK_WIDGET(w));
2351
2352 GtkAllocation tmp;
2353 gtk_widget_get_allocation(GTK_WIDGET(w), &tmp);
2354 const dt_bauhaus_combobox_data_t *d = &w->data.combobox;
2355 if(w->quad_paint && (event->x > allocation.width - darktable.bauhaus->quad_width - INNER_PADDING))
2356 {
2357 dt_bauhaus_widget_press_quad(widget);
2358 return TRUE;
2359 }
2360 else if(event->button == 3)
2361 {
2362 darktable.bauhaus->mouse_x = event->x;
2363 darktable.bauhaus->mouse_y = event->y;
2364 dt_bauhaus_show_popup(w);
2365 return TRUE;
2366 }
2367 else if(event->button == 1)
2368 {
2369 // reset to default.
2370 if(event->type == GDK_2BUTTON_PRESS)
2371 {
2372 // never called, as we popup the other window under your cursor before.
2373 // (except in weird corner cases where the popup is under the -1st entry
2374 dt_bauhaus_combobox_set(widget, d->defpos);
2375 dt_bauhaus_hide_popup();
2376 }
2377 else
2378 {
2379 // single click, show options
2380 darktable.bauhaus->opentime = dt_get_wtime();
2381 darktable.bauhaus->mouse_x = event->x;
2382 darktable.bauhaus->mouse_y = event->y;
2383 dt_bauhaus_show_popup(w);
2384 }
2385 return TRUE;
2386 }
2387 return FALSE;
2388 }
2389
dt_bauhaus_slider_get(GtkWidget * widget)2390 float dt_bauhaus_slider_get(GtkWidget *widget)
2391 {
2392 // first cast to bh widget, to check that type:
2393 const dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2394 if(w->type != DT_BAUHAUS_SLIDER) return -1.0f;
2395 const dt_bauhaus_slider_data_t *d = &w->data.slider;
2396 if(d->max == d->min)
2397 {
2398 return d->max;
2399 }
2400 const float rawval = d->curve(widget, d->pos, DT_BAUHAUS_GET);
2401 return d->min + rawval * (d->max - d->min);
2402 }
2403
dt_bauhaus_slider_get_val(GtkWidget * widget)2404 float dt_bauhaus_slider_get_val(GtkWidget *widget)
2405 {
2406 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(widget)->data.slider;
2407 return dt_bauhaus_slider_get(widget) * d->factor + d->offset;
2408 }
2409
dt_bauhaus_slider_get_text(GtkWidget * w)2410 char *dt_bauhaus_slider_get_text(GtkWidget *w)
2411 {
2412 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(w)->data.slider;
2413 return g_strdup_printf(d->format, dt_bauhaus_slider_get_val(w));
2414 }
2415
dt_bauhaus_slider_set(GtkWidget * widget,float pos)2416 void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
2417 {
2418 // this is the public interface function, translate by bounds and call set_normalized
2419 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2420 if(w->type != DT_BAUHAUS_SLIDER) return;
2421 const dt_bauhaus_slider_data_t *d = &w->data.slider;
2422 const float rawval = (pos - d->min) / (d->max - d->min);
2423 dt_bauhaus_slider_set_normalized(w, d->curve(widget, rawval, DT_BAUHAUS_SET));
2424 }
2425
dt_bauhaus_slider_set_val(GtkWidget * widget,float val)2426 void dt_bauhaus_slider_set_val(GtkWidget *widget, float val)
2427 {
2428 const dt_bauhaus_slider_data_t *d = &DT_BAUHAUS_WIDGET(widget)->data.slider;
2429 dt_bauhaus_slider_set_soft(widget, (val - d->offset) / d->factor);
2430 }
2431
dt_bauhaus_slider_set_digits(GtkWidget * widget,int val)2432 void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
2433 {
2434 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2435
2436 if(w->type != DT_BAUHAUS_SLIDER) return;
2437
2438 dt_bauhaus_slider_data_t *d = &w->data.slider;
2439
2440 d->digits = val;
2441 snprintf(d->format, sizeof(d->format), "%%.0%df", val);
2442 }
2443
dt_bauhaus_slider_get_digits(GtkWidget * widget)2444 int dt_bauhaus_slider_get_digits(GtkWidget *widget)
2445 {
2446 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2447
2448 if(w->type != DT_BAUHAUS_SLIDER) return 0;
2449
2450 const dt_bauhaus_slider_data_t *d = &w->data.slider;
2451
2452 return d->digits;
2453 }
2454
dt_bauhaus_slider_set_step(GtkWidget * widget,float val)2455 void dt_bauhaus_slider_set_step(GtkWidget *widget, float val)
2456 {
2457 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2458
2459 if(w->type != DT_BAUHAUS_SLIDER) return;
2460
2461 dt_bauhaus_slider_data_t *d = &w->data.slider;
2462
2463 d->step = val;
2464 d->scale = 5.0f * d->step / (d->max - d->min);
2465 }
2466
dt_bauhaus_slider_get_step(GtkWidget * widget)2467 float dt_bauhaus_slider_get_step(GtkWidget *widget)
2468 {
2469 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2470
2471 if(w->type != DT_BAUHAUS_SLIDER) return 0;
2472
2473 const dt_bauhaus_slider_data_t *d = &w->data.slider;
2474
2475 return d->factor < 0 ? - d->step : d->step;
2476 }
2477
dt_bauhaus_slider_set_feedback(GtkWidget * widget,int feedback)2478 void dt_bauhaus_slider_set_feedback(GtkWidget *widget, int feedback)
2479 {
2480 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2481
2482 if(w->type != DT_BAUHAUS_SLIDER) return;
2483
2484 dt_bauhaus_slider_data_t *d = &w->data.slider;
2485
2486 d->fill_feedback = feedback;
2487
2488 gtk_widget_queue_draw(widget);
2489 }
2490
dt_bauhaus_slider_get_feedback(GtkWidget * widget)2491 int dt_bauhaus_slider_get_feedback(GtkWidget *widget)
2492 {
2493 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2494
2495 if(w->type != DT_BAUHAUS_SLIDER) return 0;
2496
2497 dt_bauhaus_slider_data_t *d = &w->data.slider;
2498
2499 return d->fill_feedback;
2500 }
2501
dt_bauhaus_slider_reset(GtkWidget * widget)2502 void dt_bauhaus_slider_reset(GtkWidget *widget)
2503 {
2504 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2505
2506 if(w->type != DT_BAUHAUS_SLIDER) return;
2507 dt_bauhaus_slider_data_t *d = &w->data.slider;
2508
2509 d->min = d->soft_min;
2510 d->max = d->soft_max;
2511 d->scale = 5.0f * d->step / (d->max - d->min);
2512
2513 dt_bauhaus_slider_set_soft(widget, d->defpos);
2514
2515 return;
2516 }
2517
dt_bauhaus_slider_set_format(GtkWidget * widget,const char * format)2518 void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
2519 {
2520 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2521 if(w->type != DT_BAUHAUS_SLIDER) return;
2522 dt_bauhaus_slider_data_t *d = &w->data.slider;
2523 g_strlcpy(d->format, format, sizeof(d->format));
2524 }
2525
dt_bauhaus_slider_set_factor(GtkWidget * widget,float factor)2526 void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
2527 {
2528 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2529 if(w->type != DT_BAUHAUS_SLIDER) return;
2530 dt_bauhaus_slider_data_t *d = &w->data.slider;
2531 d->factor = factor;
2532 if(factor < 0) d->curve = _reverse_linear_curve;
2533 }
2534
dt_bauhaus_slider_set_offset(GtkWidget * widget,float offset)2535 void dt_bauhaus_slider_set_offset(GtkWidget *widget, float offset)
2536 {
2537 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2538 if(w->type != DT_BAUHAUS_SLIDER) return;
2539 dt_bauhaus_slider_data_t *d = &w->data.slider;
2540 d->offset = offset;
2541 }
2542
dt_bauhaus_slider_set_curve(GtkWidget * widget,float (* curve)(GtkWidget * self,float value,dt_bauhaus_curve_t dir))2543 void dt_bauhaus_slider_set_curve(GtkWidget *widget, float (*curve)(GtkWidget *self, float value, dt_bauhaus_curve_t dir))
2544 {
2545 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2546 if(w->type != DT_BAUHAUS_SLIDER) return;
2547 dt_bauhaus_slider_data_t *d = &w->data.slider;
2548 if(curve == NULL) curve = _default_linear_curve;
2549
2550 d->pos = curve(widget, d->curve(widget, d->pos , DT_BAUHAUS_GET), DT_BAUHAUS_SET);
2551
2552 d->curve = curve;
2553 }
2554
dt_bauhaus_slider_set_soft(GtkWidget * widget,float pos)2555 void dt_bauhaus_slider_set_soft(GtkWidget *widget, float pos)
2556 {
2557 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
2558 if(w->type != DT_BAUHAUS_SLIDER) return;
2559 dt_bauhaus_slider_data_t *d = &w->data.slider;
2560 const float rpos = CLAMP(pos, d->hard_min, d->hard_max);
2561 d->min = MIN(d->min, rpos);
2562 d->max = MAX(d->max, rpos);
2563 d->scale = 5.0f * d->step / (d->max - d->min);
2564 dt_bauhaus_slider_set(widget, rpos);
2565 }
2566
dt_bauhaus_slider_postponed_value_change(gpointer data)2567 static gboolean dt_bauhaus_slider_postponed_value_change(gpointer data)
2568 {
2569 if(!GTK_IS_WIDGET(data)) return 0;
2570
2571 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)data;
2572 dt_bauhaus_slider_data_t *d = &w->data.slider;
2573 if(d->is_changed)
2574 {
2575 g_signal_emit_by_name(G_OBJECT(w), "value-changed");
2576 d->is_changed = 0;
2577 return TRUE;
2578 }
2579 else
2580 {
2581 d->timeout_handle = 0;
2582 return FALSE;
2583 }
2584 }
2585
dt_bauhaus_slider_set_normalized(dt_bauhaus_widget_t * w,float pos)2586 static void dt_bauhaus_slider_set_normalized(dt_bauhaus_widget_t *w, float pos)
2587 {
2588 dt_bauhaus_slider_data_t *d = &w->data.slider;
2589 float rpos = CLAMP(pos, 0.0f, 1.0f);
2590 rpos = d->curve(GTK_WIDGET(w), rpos, DT_BAUHAUS_GET);
2591 rpos = d->min + (d->max - d->min) * rpos;
2592 const float base = powf(10.0f, d->digits);
2593 rpos = roundf(base * rpos) / base;
2594 rpos = (rpos - d->min) / (d->max - d->min);
2595 d->pos = d->curve(GTK_WIDGET(w), rpos, DT_BAUHAUS_SET);
2596 gtk_widget_queue_draw(GTK_WIDGET(w));
2597 d->is_changed = 1;
2598 if(!darktable.gui->reset)
2599 {
2600 if(!d->is_dragging)
2601 {
2602 g_signal_emit_by_name(G_OBJECT(w), "value-changed");
2603 d->is_changed = 0;
2604 }
2605 else
2606 {
2607 if(!d->timeout_handle)
2608 {
2609 const int delay = CLAMP(darktable.develop->average_delay * 3 / 2, DT_BAUHAUS_SLIDER_VALUE_CHANGED_DELAY_MIN,
2610 DT_BAUHAUS_SLIDER_VALUE_CHANGED_DELAY_MAX);
2611 d->timeout_handle = g_timeout_add(delay, dt_bauhaus_slider_postponed_value_change, w);
2612 }
2613 }
2614 }
2615 }
2616
dt_bauhaus_popup_key_press(GtkWidget * widget,GdkEventKey * event,gpointer user_data)2617 static gboolean dt_bauhaus_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
2618 {
2619 switch(darktable.bauhaus->current->type)
2620 {
2621 case DT_BAUHAUS_SLIDER:
2622 {
2623 // hack to do screenshots from popup:
2624 // if(event->string[0] == 'p') return system("scrot");
2625 // else
2626 if(darktable.bauhaus->keys_cnt + 2 < 64
2627 && (event->keyval == GDK_KEY_space || event->keyval == GDK_KEY_KP_Space || // SPACE
2628 event->keyval == GDK_KEY_percent || // %
2629 (event->string[0] >= 40 && event->string[0] <= 57) || // ()+-*/.,0-9
2630 event->keyval == GDK_KEY_asciicircum || event->keyval == GDK_KEY_dead_circumflex || // ^
2631 event->keyval == GDK_KEY_X || event->keyval == GDK_KEY_x)) // Xx
2632 {
2633 if(event->keyval == GDK_KEY_dead_circumflex)
2634 darktable.bauhaus->keys[darktable.bauhaus->keys_cnt++] = '^';
2635 else
2636 darktable.bauhaus->keys[darktable.bauhaus->keys_cnt++] = event->string[0];
2637 gtk_widget_queue_draw(darktable.bauhaus->popup_area);
2638 }
2639 else if(darktable.bauhaus->keys_cnt > 0
2640 && (event->keyval == GDK_KEY_BackSpace || event->keyval == GDK_KEY_Delete))
2641 {
2642 darktable.bauhaus->keys[--darktable.bauhaus->keys_cnt] = 0;
2643 gtk_widget_queue_draw(darktable.bauhaus->popup_area);
2644 }
2645 else if(darktable.bauhaus->keys_cnt > 0 && darktable.bauhaus->keys_cnt + 1 < 64
2646 && (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter))
2647 {
2648 // accept input
2649 darktable.bauhaus->keys[darktable.bauhaus->keys_cnt] = 0;
2650 // unnormalized input, user was typing this:
2651 const float old_value = dt_bauhaus_slider_get_val(GTK_WIDGET(darktable.bauhaus->current));
2652 const float new_value = dt_calculator_solve(old_value, darktable.bauhaus->keys);
2653 if(isfinite(new_value)) dt_bauhaus_slider_set_val(GTK_WIDGET(darktable.bauhaus->current), new_value);
2654 darktable.bauhaus->keys_cnt = 0;
2655 memset(darktable.bauhaus->keys, 0, sizeof(darktable.bauhaus->keys));
2656 dt_bauhaus_hide_popup();
2657 }
2658 else if(event->keyval == GDK_KEY_Escape)
2659 {
2660 // discard input and close popup
2661 darktable.bauhaus->keys_cnt = 0;
2662 memset(darktable.bauhaus->keys, 0, sizeof(darktable.bauhaus->keys));
2663 dt_bauhaus_hide_popup();
2664 }
2665 else
2666 return FALSE;
2667 if(darktable.bauhaus->keys_cnt > 0) _start_cursor(-1);
2668 return TRUE;
2669 }
2670 case DT_BAUHAUS_COMBOBOX:
2671 {
2672 if(!g_utf8_validate(event->string, -1, NULL)) return FALSE;
2673 const gunichar c = g_utf8_get_char(event->string);
2674 const long int char_width = g_utf8_next_char(event->string) - event->string;
2675 // if(event->string[0] == 'p') return system("scrot");
2676 // else
2677 if(darktable.bauhaus->keys_cnt + 1 + char_width < 64 && g_unichar_isprint(c))
2678 {
2679 // only accept key input if still valid or editable?
2680 g_utf8_strncpy(darktable.bauhaus->keys + darktable.bauhaus->keys_cnt, event->string, 1);
2681 darktable.bauhaus->keys_cnt += char_width;
2682 gtk_widget_queue_draw(darktable.bauhaus->popup_area);
2683 }
2684 else if(darktable.bauhaus->keys_cnt > 0
2685 && (event->keyval == GDK_KEY_BackSpace || event->keyval == GDK_KEY_Delete))
2686 {
2687 darktable.bauhaus->keys_cnt
2688 -= (darktable.bauhaus->keys + darktable.bauhaus->keys_cnt)
2689 - g_utf8_prev_char(darktable.bauhaus->keys + darktable.bauhaus->keys_cnt);
2690 darktable.bauhaus->keys[darktable.bauhaus->keys_cnt] = 0;
2691 gtk_widget_queue_draw(darktable.bauhaus->popup_area);
2692 }
2693 else if(darktable.bauhaus->keys_cnt > 0 && darktable.bauhaus->keys_cnt + 1 < 64
2694 && (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter))
2695 {
2696 // accept unique matches only for editable:
2697 if(darktable.bauhaus->current->data.combobox.editable)
2698 darktable.bauhaus->end_mouse_y = FLT_MAX;
2699 else
2700 darktable.bauhaus->end_mouse_y = 0;
2701 darktable.bauhaus->keys[darktable.bauhaus->keys_cnt] = 0;
2702 dt_bauhaus_widget_accept(darktable.bauhaus->current);
2703 darktable.bauhaus->keys_cnt = 0;
2704 memset(darktable.bauhaus->keys, 0, sizeof(darktable.bauhaus->keys));
2705 dt_bauhaus_hide_popup();
2706 }
2707 else if(event->keyval == GDK_KEY_Escape)
2708 {
2709 // discard input and close popup
2710 darktable.bauhaus->keys_cnt = 0;
2711 memset(darktable.bauhaus->keys, 0, sizeof(darktable.bauhaus->keys));
2712 dt_bauhaus_hide_popup();
2713 }
2714 else if(event->keyval == GDK_KEY_Up)
2715 {
2716 combobox_popup_scroll(-1);
2717 }
2718 else if(event->keyval == GDK_KEY_Down)
2719 {
2720 combobox_popup_scroll(1);
2721 }
2722 else if(event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter)
2723 {
2724 // return pressed, but didn't type anything
2725 darktable.bauhaus->end_mouse_y = -1; // negative will use currently highlighted instead.
2726 darktable.bauhaus->keys[darktable.bauhaus->keys_cnt] = 0;
2727 darktable.bauhaus->keys_cnt = 0;
2728 memset(darktable.bauhaus->keys, 0, sizeof(darktable.bauhaus->keys));
2729 dt_bauhaus_widget_accept(darktable.bauhaus->current);
2730 dt_bauhaus_hide_popup();
2731 }
2732 else
2733 return FALSE;
2734 return TRUE;
2735 }
2736 default:
2737 return FALSE;
2738 }
2739 }
2740
dt_bauhaus_slider_button_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)2741 static gboolean dt_bauhaus_slider_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2742 {
2743 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2744 bauhaus_request_focus(w);
2745 gtk_widget_grab_focus(GTK_WIDGET(w));
2746
2747 GtkAllocation allocation;
2748 gtk_widget_get_allocation(widget, &allocation);
2749 if(event->x > allocation.width - darktable.bauhaus->quad_width - INNER_PADDING)
2750 {
2751 dt_bauhaus_widget_press_quad(widget);
2752 return TRUE;
2753 }
2754 else if(event->button == 3)
2755 {
2756 dt_bauhaus_show_popup(w);
2757 return TRUE;
2758 }
2759 else if(event->button == 1)
2760 {
2761 // reset to default.
2762 if(event->type == GDK_2BUTTON_PRESS)
2763 {
2764 dt_bauhaus_slider_data_t *d = &w->data.slider;
2765 d->is_dragging = 0;
2766 dt_bauhaus_slider_reset(GTK_WIDGET(w));
2767 }
2768 else
2769 {
2770 const float l = 0.0f;
2771 const float r = slider_right_pos((float)allocation.width);
2772 dt_bauhaus_slider_set_normalized(w, (event->x / allocation.width - l) / (r - l));
2773 dt_bauhaus_slider_data_t *d = &w->data.slider;
2774 d->is_dragging = 1;
2775 }
2776 return TRUE;
2777 }
2778 return FALSE;
2779 }
2780
dt_bauhaus_slider_button_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)2781 static gboolean dt_bauhaus_slider_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2782 {
2783 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2784 dt_bauhaus_slider_data_t *d = &w->data.slider;
2785
2786 dt_bauhaus_widget_release_quad(widget);
2787 if((event->button == 1) && (d->is_dragging))
2788 {
2789 bauhaus_request_focus(w);
2790
2791 GtkAllocation tmp;
2792 gtk_widget_get_allocation(GTK_WIDGET(w), &tmp);
2793 d->is_dragging = 0;
2794 if(d->timeout_handle) g_source_remove(d->timeout_handle);
2795 d->timeout_handle = 0;
2796 const float l = 0.0f;
2797 const float r = slider_right_pos((float)tmp.width);
2798 dt_bauhaus_slider_set_normalized(w, (event->x / tmp.width - l) / (r - l));
2799
2800 return TRUE;
2801 }
2802 return FALSE;
2803 }
2804
dt_bauhaus_slider_motion_notify(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)2805 static gboolean dt_bauhaus_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
2806 {
2807 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2808 dt_bauhaus_slider_data_t *d = &w->data.slider;
2809
2810 GtkAllocation allocation;
2811 gtk_widget_get_allocation(widget, &allocation);
2812 if(d->is_dragging || event->x <= allocation.width - darktable.bauhaus->quad_width)
2813 {
2814 // remember mouse position for motion effects in draw
2815 if(event->state & GDK_BUTTON1_MASK && event->type != GDK_2BUTTON_PRESS)
2816 {
2817 bauhaus_request_focus(w);
2818 const float l = 0.0f;
2819 const float r = slider_right_pos((float)allocation.width);
2820 dt_bauhaus_slider_set_normalized(w, (event->x / allocation.width - l) / (r - l));
2821 }
2822 darktable.control->element = event->x > (0.1 * (allocation.width - darktable.bauhaus->quad_width)) &&
2823 event->x < (0.9 * (allocation.width - darktable.bauhaus->quad_width))
2824 ? DT_ACTION_ELEMENT_VALUE
2825 : DT_ACTION_ELEMENT_FORCE;
2826 }
2827 else
2828 darktable.control->element = DT_ACTION_ELEMENT_BUTTON;
2829
2830 return TRUE;
2831 }
2832
dt_bauhaus_combobox_motion_notify(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)2833 static gboolean dt_bauhaus_combobox_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
2834 {
2835 GtkAllocation allocation;
2836 gtk_widget_get_allocation(widget, &allocation);
2837
2838 darktable.control->element = event->x <= allocation.width - darktable.bauhaus->quad_width
2839 ? DT_ACTION_ELEMENT_SELECTION
2840 : DT_ACTION_ELEMENT_BUTTON;
2841
2842 return TRUE;
2843 }
2844
2845
dt_bauhaus_vimkey_exec(const char * input)2846 void dt_bauhaus_vimkey_exec(const char *input)
2847 {
2848 dt_action_t *ac = darktable.control->actions_iops.target;
2849 input += 5; // skip ":set "
2850
2851 while(ac)
2852 {
2853 const int prefix = strcspn(input, ".=");
2854
2855 if(ac->type >= DT_ACTION_TYPE_WIDGET ||
2856 ac->type <= DT_ACTION_TYPE_SECTION)
2857 {
2858 if(!strncasecmp(ac->label, input, prefix))
2859 {
2860 if(!ac->label[prefix])
2861 {
2862 input += prefix;
2863 if(*input) input++; // skip . or =
2864
2865 if(ac->type <= DT_ACTION_TYPE_SECTION)
2866 {
2867 ac = ac->target;
2868 continue;
2869 }
2870 else
2871 break;
2872 }
2873 }
2874 }
2875
2876 ac = ac->next;
2877 }
2878
2879 if(!ac || ac->type != DT_ACTION_TYPE_WIDGET || !ac->target || !DT_IS_BAUHAUS_WIDGET(ac->target))
2880 return;
2881
2882 float old_value = .0f, new_value = .0f;
2883
2884 GtkWidget *w = ac->target;
2885
2886 switch(DT_BAUHAUS_WIDGET(w)->type)
2887 {
2888 case DT_BAUHAUS_SLIDER:
2889 old_value = dt_bauhaus_slider_get(w);
2890 new_value = dt_calculator_solve(old_value, input);
2891 fprintf(stderr, " = %f\n", new_value);
2892 if(isfinite(new_value)) dt_bauhaus_slider_set_soft(w, new_value);
2893 break;
2894 case DT_BAUHAUS_COMBOBOX:
2895 // TODO: what about text as entry?
2896 old_value = dt_bauhaus_combobox_get(w);
2897 new_value = dt_calculator_solve(old_value, input);
2898 fprintf(stderr, " = %f\n", new_value);
2899 if(isfinite(new_value)) dt_bauhaus_combobox_set(w, new_value);
2900 break;
2901 default:
2902 break;
2903 }
2904 }
2905
2906 // give autocomplete suggestions
dt_bauhaus_vimkey_complete(const char * input)2907 GList *dt_bauhaus_vimkey_complete(const char *input)
2908 {
2909 GList *res = NULL;
2910
2911 dt_action_t *ac = darktable.control->actions_iops.target;
2912
2913 while(ac)
2914 {
2915 const int prefix = strcspn(input, ".");
2916
2917 if(ac->type >= DT_ACTION_TYPE_WIDGET ||
2918 ac->type <= DT_ACTION_TYPE_SECTION)
2919 {
2920 if(!prefix || !strncasecmp(ac->label, input, prefix))
2921 {
2922 if(!ac->label[prefix] && input[prefix] == '.')
2923 {
2924 input += prefix + 1;
2925 if(ac->type <= DT_ACTION_TYPE_SECTION) ac = ac->target;
2926 continue;
2927 }
2928 else
2929 res = g_list_append(res, (gchar *)ac->label + prefix);
2930 }
2931 }
2932
2933 ac = ac->next;
2934 }
2935 return res;
2936 }
2937
dt_bauhaus_combobox_mute_scrolling(GtkWidget * widget)2938 void dt_bauhaus_combobox_mute_scrolling(GtkWidget *widget)
2939 {
2940 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
2941 dt_bauhaus_combobox_data_t *d = &w->data.combobox;
2942 d->mute_scrolling = TRUE;
2943 }
2944
_action_process_slider(gpointer target,dt_action_element_t element,dt_action_effect_t effect,float move_size)2945 static float _action_process_slider(gpointer target, dt_action_element_t element, dt_action_effect_t effect, float move_size)
2946 {
2947 GtkWidget *widget = GTK_WIDGET(target);
2948 dt_bauhaus_widget_t *bhw = DT_BAUHAUS_WIDGET(widget);
2949 dt_bauhaus_slider_data_t *d = &bhw->data.slider;
2950 const float value = dt_bauhaus_slider_get(widget);
2951 const float min_visible = powf(10.0f, -dt_bauhaus_slider_get_digits(widget));
2952
2953 if(!isnan(move_size))
2954 {
2955 switch(element)
2956 {
2957 case DT_ACTION_ELEMENT_VALUE:
2958 case DT_ACTION_ELEMENT_FORCE:
2959 switch(effect)
2960 {
2961 case DT_ACTION_EFFECT_POPUP:
2962 dt_bauhaus_show_popup(DT_BAUHAUS_WIDGET(widget));
2963 break;
2964 case DT_ACTION_EFFECT_DOWN:
2965 move_size *= -1;
2966 case DT_ACTION_EFFECT_UP:
2967 d->is_dragging = 1;
2968 const float step = dt_bauhaus_slider_get_step(widget);
2969 float multiplier = dt_accel_get_slider_scale_multiplier();
2970
2971 if(move_size && fabsf(move_size * step * multiplier) < min_visible)
2972 multiplier = min_visible / fabsf(move_size * step);
2973
2974 if(element == DT_ACTION_ELEMENT_FORCE)
2975 {
2976 if(d->pos < 0.0001) d->min = d->soft_min;
2977 if(d->pos > 0.9999) d->max = d->soft_max;
2978 dt_bauhaus_slider_set_soft(widget, value + move_size * step * multiplier);
2979 }
2980 else
2981 dt_bauhaus_slider_set(widget, value + move_size * step * multiplier);
2982 d->is_dragging = 0;
2983 break;
2984 case DT_ACTION_EFFECT_RESET:
2985 dt_bauhaus_slider_reset(widget);
2986 break;
2987 case DT_ACTION_EFFECT_TOP:
2988 dt_bauhaus_slider_set_soft(widget, element == DT_ACTION_ELEMENT_FORCE ? d->hard_max: d->max);
2989 break;
2990 case DT_ACTION_EFFECT_BOTTOM:
2991 dt_bauhaus_slider_set_soft(widget, element == DT_ACTION_ELEMENT_FORCE ? d->hard_min: d->min);
2992 break;
2993 case DT_ACTION_EFFECT_SET:
2994 dt_bauhaus_slider_set_soft(widget, move_size);
2995 break;
2996 default:
2997 fprintf(stderr, "[_action_process_slider] unknown shortcut effect (%d) for slider\n", effect);
2998 break;
2999 }
3000
3001 gchar *text = dt_bauhaus_slider_get_text(widget);
3002 dt_action_widget_toast(bhw->module, widget, text);
3003 g_free(text);
3004
3005 break;
3006 case DT_ACTION_ELEMENT_BUTTON:
3007 dt_bauhaus_widget_press_quad(widget);
3008 break;
3009 case DT_ACTION_ELEMENT_ZOOM:
3010 switch(effect)
3011 {
3012 case DT_ACTION_EFFECT_POPUP:
3013 dt_bauhaus_show_popup(DT_BAUHAUS_WIDGET(widget));
3014 break;
3015 case DT_ACTION_EFFECT_DOWN:
3016 move_size *= -1;
3017 case DT_ACTION_EFFECT_UP:
3018 if(d->soft_min != d->hard_min || d->soft_max != d->hard_max)
3019 {
3020 const float multiplier = powf(2.0f, move_size/2);
3021 const float new_min = value - multiplier * (value - d->min);
3022 const float new_max = value + multiplier * (d->max - value);
3023 if(new_min >= d->hard_min
3024 && new_max <= d->hard_max
3025 && new_max - new_min >= min_visible * 10)
3026 {
3027 d->min = new_min;
3028 d->max = new_max;
3029 }
3030 }
3031 break;
3032 case DT_ACTION_EFFECT_RESET:
3033 d->min = d->soft_min;
3034 d->max = d->soft_max;
3035 break;
3036 case DT_ACTION_EFFECT_TOP:
3037 d->max = d->hard_max;
3038 break;
3039 case DT_ACTION_EFFECT_BOTTOM:
3040 d->min = d->hard_min;
3041 break;
3042 default:
3043 fprintf(stderr, "[_action_process_slider] unknown shortcut effect (%d) for slider\n", effect);
3044 break;
3045 }
3046 dt_bauhaus_slider_set_soft(widget, value); // restore value (and move min/max again if needed)
3047
3048 gtk_widget_queue_draw(GTK_WIDGET(widget));
3049 dt_toast_log(("[%f , %f]"), d->min, d->max);
3050
3051 break;
3052 default:
3053 fprintf(stderr, "[_action_process_slider] unknown shortcut element (%d) for slider\n", element);
3054 break;
3055 }
3056 }
3057
3058 if(effect == DT_ACTION_EFFECT_SET)
3059 return dt_bauhaus_slider_get(widget);
3060
3061 return d->pos +
3062 ( d->min == -d->max ? DT_VALUE_PATTERN_PLUS_MINUS :
3063 ( d->min == 0 && (d->max == 1 || d->max == 100) ? DT_VALUE_PATTERN_PERCENTAGE : 0 ));
3064 }
3065
combobox_idle_value_changed(gpointer widget)3066 gboolean combobox_idle_value_changed(gpointer widget)
3067 {
3068 g_signal_emit_by_name(G_OBJECT(widget), "value-changed");
3069
3070 while(g_idle_remove_by_data(widget));
3071
3072 return FALSE;
3073 }
3074
_action_process_combo(gpointer target,dt_action_element_t element,dt_action_effect_t effect,float move_size)3075 static float _action_process_combo(gpointer target, dt_action_element_t element, dt_action_effect_t effect, float move_size)
3076 {
3077 GtkWidget *widget = GTK_WIDGET(target);
3078 dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget;
3079 int value = dt_bauhaus_combobox_get(widget);
3080
3081 if(!isnan(move_size))
3082 {
3083 if(element == DT_ACTION_ELEMENT_BUTTON)
3084 dt_bauhaus_widget_press_quad(widget);
3085 else switch(effect)
3086 {
3087 case DT_ACTION_EFFECT_POPUP:
3088 dt_bauhaus_show_popup(DT_BAUHAUS_WIDGET(widget));
3089 break;
3090 case DT_ACTION_EFFECT_LAST:
3091 move_size *= - 1; // reversed in effect_previous
3092 case DT_ACTION_EFFECT_FIRST:
3093 move_size *= 1e3; // reversed in effect_previous
3094 case DT_ACTION_EFFECT_PREVIOUS:
3095 move_size *= - 1;
3096 case DT_ACTION_EFFECT_NEXT:
3097 value = CLAMP(value + move_size, 0, dt_bauhaus_combobox_length(widget) - 1);
3098
3099 if(_combobox_next_entry(w->data.combobox.entries, &value, move_size > 0 ? 1 : -1))
3100 {
3101 ++darktable.gui->reset;
3102 dt_bauhaus_combobox_set(widget, value);
3103 --darktable.gui->reset;
3104 }
3105
3106 g_idle_add(combobox_idle_value_changed, widget);
3107 break;
3108 case DT_ACTION_EFFECT_RESET:
3109 value = dt_bauhaus_combobox_get_default(widget);
3110 dt_bauhaus_combobox_set(widget, value);
3111 break;
3112 default:
3113 value = effect - DT_ACTION_EFFECT_COMBO_SEPARATOR - 1;
3114 dt_bauhaus_combobox_set(widget, value);
3115 break;
3116 }
3117
3118 gchar *text = g_strdup_printf("\n%s", dt_bauhaus_combobox_get_text(widget));
3119 dt_action_widget_toast(w->module, widget, text);
3120 g_free(text);
3121 }
3122
3123 GList *e = w->data.combobox.entries;
3124 for(int above = value; above && e; above--, e = e->next)
3125 {
3126 dt_bauhaus_combobox_entry_t *entry = e->data;
3127 if(entry && ! entry->sensitive) value--; // don't count unselectable combo items in value
3128 }
3129 return - 1 - value + (value == effect - DT_ACTION_EFFECT_COMBO_SEPARATOR - 1 ? DT_VALUE_PATTERN_ACTIVE : 0);
3130 }
3131
3132 const dt_action_element_def_t _action_elements_slider[]
3133 = { { N_("value"), dt_action_effect_value },
3134 { N_("button"), dt_action_effect_toggle },
3135 { N_("force"), dt_action_effect_value },
3136 { N_("zoom"), dt_action_effect_value },
3137 { NULL } };
3138 const dt_action_element_def_t _action_elements_combo[]
3139 = { { N_("selection"), dt_action_effect_selection },
3140 { N_("button"), dt_action_effect_toggle },
3141 { NULL } };
3142
3143 static const dt_shortcut_fallback_t _action_fallbacks_slider[]
3144 = { { .element = DT_ACTION_ELEMENT_BUTTON, .button = DT_SHORTCUT_LEFT },
3145 { .element = DT_ACTION_ELEMENT_BUTTON, .effect = DT_ACTION_EFFECT_TOGGLE_CTRL, .button = DT_SHORTCUT_LEFT, .mods = GDK_CONTROL_MASK },
3146 { .element = DT_ACTION_ELEMENT_FORCE, .mods = GDK_CONTROL_MASK | GDK_SHIFT_MASK, .speed = 10.0 },
3147 { .element = DT_ACTION_ELEMENT_ZOOM, .effect = DT_ACTION_EFFECT_DEFAULT_MOVE, .button = DT_SHORTCUT_RIGHT, .move = DT_SHORTCUT_MOVE_VERTICAL },
3148 { } };
3149 static const dt_shortcut_fallback_t _action_fallbacks_combo[]
3150 = { { .element = DT_ACTION_ELEMENT_SELECTION, .effect = DT_ACTION_EFFECT_RESET, .button = DT_SHORTCUT_LEFT, .click = DT_SHORTCUT_DOUBLE },
3151 { .element = DT_ACTION_ELEMENT_BUTTON, .button = DT_SHORTCUT_LEFT },
3152 { .element = DT_ACTION_ELEMENT_BUTTON, .effect = DT_ACTION_EFFECT_TOGGLE_CTRL, .button = DT_SHORTCUT_LEFT, .mods = GDK_CONTROL_MASK },
3153 { .move = DT_SHORTCUT_MOVE_SCROLL, .effect = DT_ACTION_EFFECT_DEFAULT_MOVE, .speed = -1 },
3154 { .move = DT_SHORTCUT_MOVE_VERTICAL, .effect = DT_ACTION_EFFECT_DEFAULT_MOVE, .speed = -1 },
3155 { } };
3156
3157 const dt_action_def_t dt_action_def_slider
3158 = { N_("slider"),
3159 _action_process_slider,
3160 _action_elements_slider,
3161 _action_fallbacks_slider };
3162 const dt_action_def_t dt_action_def_combo
3163 = { N_("dropdown"),
3164 _action_process_combo,
3165 _action_elements_combo,
3166 _action_fallbacks_combo };
3167
3168 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
3169 // vim: shiftwidth=2 expandtab tabstop=2 cindent
3170 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3171