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