1 /*
2     This file is part of darktable,
3     Copyright (C) 2009-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 /** this is the view for the darkroom module.  */
19 #include "bauhaus/bauhaus.h"
20 #include "common/collection.h"
21 #include "common/colorspaces.h"
22 #include "common/darktable.h"
23 #include "common/debug.h"
24 #include "common/file_location.h"
25 #include "common/focus_peaking.h"
26 #include "common/history.h"
27 #include "common/image_cache.h"
28 #include "common/imageio.h"
29 #include "common/imageio_module.h"
30 #include "common/selection.h"
31 #include "common/styles.h"
32 #include "common/tags.h"
33 #include "common/undo.h"
34 #include "control/conf.h"
35 #include "control/control.h"
36 #include "control/jobs.h"
37 #include "develop/blend.h"
38 #include "develop/develop.h"
39 #include "develop/imageop.h"
40 #include "develop/masks.h"
41 #include "dtgtk/button.h"
42 #include "dtgtk/thumbtable.h"
43 #include "gui/accelerators.h"
44 #include "gui/gtk.h"
45 #include "gui/presets.h"
46 #include "libs/colorpicker.h"
47 #include "libs/modulegroups.h"
48 #include "views/view.h"
49 #include "views/view_api.h"
50 #ifdef GDK_WINDOWING_QUARTZ
51 #include "osx/osx.h"
52 #endif
53 
54 #ifdef USE_LUA
55 #include "lua/image.h"
56 #endif
57 
58 #include <gdk/gdkkeysyms.h>
59 #include <glib.h>
60 #include <math.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <unistd.h>
64 #include <sys/types.h>
65 #include <sys/stat.h>
66 #include <fcntl.h>
67 
68 #ifndef G_SOURCE_FUNC // Defined for glib >= 2.58
69 #define G_SOURCE_FUNC(f) ((GSourceFunc) (void (*)(void)) (f))
70 #endif
71 
72 DT_MODULE(1)
73 
74 static gboolean zoom_key_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
75                                GdkModifierType modifier, gpointer data);
76 
77 static gboolean skip_f_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
78                                           GdkModifierType modifier, gpointer data);
79 static gboolean skip_b_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
80                                           GdkModifierType modifier, gpointer data);
81 static gboolean _toolbox_toggle_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
82                                          GdkModifierType modifier, gpointer data);
83 static gboolean _brush_size_up_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
84                                         GdkModifierType modifier, gpointer data);
85 static gboolean _brush_size_down_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
86                                           GdkModifierType modifier, gpointer data);
87 static gboolean _brush_hardness_up_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
88                                             GdkModifierType modifier, gpointer data);
89 static gboolean _brush_hardness_down_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
90                                               GdkModifierType modifier, gpointer data);
91 static gboolean _brush_opacity_up_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
92                                            GdkModifierType modifier, gpointer data);
93 static gboolean _brush_opacity_down_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
94                                              GdkModifierType modifier, gpointer data);
95 
96 static void _update_softproof_gamut_checking(dt_develop_t *d);
97 
98 /* signal handler for filmstrip image switching */
99 static void _view_darkroom_filmstrip_activate_callback(gpointer instance, int imgid, gpointer user_data);
100 
101 static void dt_dev_change_image(dt_develop_t *dev, const int32_t imgid);
102 
103 static void _darkroom_display_second_window(dt_develop_t *dev);
104 static void _darkroom_ui_second_window_write_config(GtkWidget *widget);
105 
name(const dt_view_t * self)106 const char *name(const dt_view_t *self)
107 {
108   return _("darkroom");
109 }
110 
111 #ifdef USE_LUA
112 
display_image_cb(lua_State * L)113 static int display_image_cb(lua_State *L)
114 {
115   dt_develop_t *dev = darktable.develop;
116   dt_lua_image_t imgid = -1;
117   if(luaL_testudata(L, 1, "dt_lua_image_t"))
118   {
119     luaA_to(L, dt_lua_image_t, &imgid, 1);
120     dt_dev_change_image(dev, imgid);
121   }
122   else
123   {
124     // ensure the image infos in db is up to date
125     dt_dev_write_history(dev);
126   }
127   luaA_push(L, dt_lua_image_t, &dev->image_storage.id);
128   return 1;
129 }
130 
131 #endif
132 
133 
init(dt_view_t * self)134 void init(dt_view_t *self)
135 {
136   self->data = malloc(sizeof(dt_develop_t));
137   dt_dev_init((dt_develop_t *)self->data, 1);
138 
139 #ifdef USE_LUA
140   lua_State *L = darktable.lua_state.state;
141   const int my_type = dt_lua_module_entry_get_type(L, "view", self->module_name);
142   lua_pushlightuserdata(L, self);
143   lua_pushcclosure(L, display_image_cb, 1);
144   dt_lua_gtk_wrap(L);
145   lua_pushcclosure(L, dt_lua_type_member_common, 1);
146   dt_lua_type_register_const_type(L, my_type, "display_image");
147 #endif
148 }
149 
view(const dt_view_t * self)150 uint32_t view(const dt_view_t *self)
151 {
152   return DT_VIEW_DARKROOM;
153 }
154 
cleanup(dt_view_t * self)155 void cleanup(dt_view_t *self)
156 {
157   dt_develop_t *dev = (dt_develop_t *)self->data;
158 
159   if(dev->second_window.second_wnd)
160   {
161     if(gtk_widget_is_visible(dev->second_window.second_wnd))
162     {
163       dt_conf_set_bool("second_window/last_visible", TRUE);
164       _darkroom_ui_second_window_write_config(dev->second_window.second_wnd);
165     }
166     else
167       dt_conf_set_bool("second_window/last_visible", FALSE);
168 
169     gtk_widget_destroy(dev->second_window.second_wnd);
170     dev->second_window.second_wnd = NULL;
171     dev->second_window.widget = NULL;
172   }
173   else
174   {
175     dt_conf_set_bool("second_window/last_visible", FALSE);
176   }
177 
178   dt_dev_cleanup(dev);
179   free(dev);
180 }
181 
_write_snapshot_data(void * closure,const unsigned char * data,unsigned int length)182 static cairo_status_t _write_snapshot_data(void *closure, const unsigned char *data, unsigned int length)
183 {
184   const int fd = GPOINTER_TO_INT(closure);
185   ssize_t res = write(fd, data, length);
186   if(res != length)
187     return CAIRO_STATUS_WRITE_ERROR;
188   return CAIRO_STATUS_SUCCESS;
189 }
190 
_lib_darkroom_get_layout(dt_view_t * self)191 static dt_darkroom_layout_t _lib_darkroom_get_layout(dt_view_t *self)
192 {
193   dt_develop_t *dev = (dt_develop_t *)self->data;
194   if(dev->iso_12646.enabled)
195     return DT_DARKROOM_LAYOUT_EDITING;
196   else
197     return DT_DARKROOM_LAYOUT_EDITING;
198 }
199 
_get_filtering_level(dt_develop_t * dev)200 static cairo_filter_t _get_filtering_level(dt_develop_t *dev)
201 {
202   const dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
203   const int closeup = dt_control_get_dev_closeup();
204   const float scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 0);
205 
206   // for pixel representation above 1:1, that is when a single pixel on the image
207   // is represented on screen by multiple pixels we want to disable any cairo filter
208   // which could only blur or smooth the output.
209 
210   if(scale / darktable.gui->ppd > 1.0)
211     return CAIRO_FILTER_FAST;
212   else
213     return darktable.gui->dr_filter_image;
214 }
215 
_display_module_trouble_message_callback(gpointer instance,dt_iop_module_t * module,const char * const trouble_msg,const char * const trouble_tooltip)216 void _display_module_trouble_message_callback(gpointer instance,
217                                               dt_iop_module_t *module,
218                                               const char *const trouble_msg,
219                                               const char *const trouble_tooltip)
220 {
221   GtkWidget *label_widget = NULL;
222 
223   if(module && module->has_trouble && module->widget)
224   {
225     label_widget = dt_gui_container_first_child(GTK_CONTAINER(gtk_widget_get_parent(module->widget)));
226     if(strcmp(gtk_widget_get_name(label_widget), "iop-plugin-warning"))
227       label_widget = NULL;
228   }
229 
230   if(trouble_msg && *trouble_msg)
231   {
232     if(module && module->widget)
233     {
234       if(label_widget)
235       {
236         // set the warning message in the module's message area just below the header
237         gtk_label_set_text(GTK_LABEL(label_widget), trouble_msg);
238       }
239       else
240       {
241         label_widget = gtk_label_new(trouble_msg);;
242         gtk_label_set_line_wrap(GTK_LABEL(label_widget), TRUE);
243         gtk_label_set_xalign(GTK_LABEL(label_widget), 0.0);
244         gtk_widget_set_name(label_widget, "iop-plugin-warning");
245 
246         GtkWidget *iopw = gtk_widget_get_parent(module->widget);
247         gtk_box_pack_start(GTK_BOX(iopw), label_widget, TRUE, TRUE, 0);
248         gtk_box_reorder_child(GTK_BOX(iopw), label_widget, 0);
249         gtk_widget_show(label_widget);
250       }
251 
252       gtk_widget_set_tooltip_text(GTK_WIDGET(label_widget), trouble_tooltip);
253 
254       // set the module's trouble flag
255       module->has_trouble = TRUE;
256 
257       dt_iop_gui_update_header(module);
258     }
259   }
260   else if(module && module->has_trouble)
261   {
262     // no more trouble, so clear the trouble flag and remove the message area
263     module->has_trouble = FALSE;
264 
265     dt_iop_gui_update_header(module);
266 
267     if(label_widget) gtk_widget_destroy(label_widget);
268   }
269 }
270 
expose(dt_view_t * self,cairo_t * cri,int32_t width,int32_t height,int32_t pointerx,int32_t pointery)271 void expose(
272     dt_view_t *self,
273     cairo_t *cri,
274     int32_t width,
275     int32_t height,
276     int32_t pointerx,
277     int32_t pointery)
278 {
279   cairo_set_source_rgb(cri, .2, .2, .2);
280   cairo_save(cri);
281 
282   dt_develop_t *dev = (dt_develop_t *)self->data;
283   const int32_t tb = dev->border_size;
284   // account for border, make it transparent for other modules called below:
285   pointerx -= tb;
286   pointery -= tb;
287 
288   if(dev->gui_synch && !dev->image_loading)
289   {
290     // synch module guis from gtk thread:
291     ++darktable.gui->reset;
292     for(const GList *modules = dev->iop; modules; modules = g_list_next(modules))
293     {
294       dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
295       dt_iop_gui_update(module);
296     }
297     --darktable.gui->reset;
298     dev->gui_synch = 0;
299   }
300 
301   if(dev->image_status == DT_DEV_PIXELPIPE_DIRTY || dev->image_status == DT_DEV_PIXELPIPE_INVALID
302      || dev->pipe->input_timestamp < dev->preview_pipe->input_timestamp)
303   {
304     dt_dev_process_image(dev);
305   }
306 
307   if(dev->preview_status == DT_DEV_PIXELPIPE_DIRTY || dev->preview_status == DT_DEV_PIXELPIPE_INVALID
308      || dev->pipe->input_timestamp > dev->preview_pipe->input_timestamp)
309   {
310     dt_dev_process_preview(dev);
311   }
312 
313   if(dev->preview2_status == DT_DEV_PIXELPIPE_DIRTY || dev->preview2_status == DT_DEV_PIXELPIPE_INVALID
314      || dev->pipe->input_timestamp > dev->preview2_pipe->input_timestamp)
315   {
316     dt_dev_process_preview2(dev);
317   }
318 
319   dt_pthread_mutex_t *mutex = NULL;
320   int stride;
321   const float zoom_y = dt_control_get_dev_zoom_y();
322   const float zoom_x = dt_control_get_dev_zoom_x();
323   const dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
324   const int closeup = dt_control_get_dev_closeup();
325   const float backbuf_scale = dt_dev_get_zoom_scale(dev, zoom, 1.0f, 0) * darktable.gui->ppd;
326 
327   static cairo_surface_t *image_surface = NULL;
328   static int image_surface_width = 0, image_surface_height = 0, image_surface_imgid = -1;
329 
330   if(image_surface_width != width || image_surface_height != height || image_surface == NULL)
331   {
332     // create double-buffered image to draw on, to make modules draw more fluently.
333     image_surface_width = width;
334     image_surface_height = height;
335     if(image_surface) cairo_surface_destroy(image_surface);
336     image_surface = dt_cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
337     image_surface_imgid = -1; // invalidate old stuff
338   }
339   cairo_surface_t *surface;
340   cairo_t *cr = cairo_create(image_surface);
341 
342   // adjust scroll bars
343   {
344     float zx = zoom_x, zy = zoom_y, boxw = 1., boxh = 1.;
345     dt_dev_check_zoom_bounds(dev, &zx, &zy, zoom, closeup, &boxw, &boxh);
346 
347     /* If boxw and boxh very closely match the zoomed size in the darktable window we might have resizing with
348        every expose because adding a slider will change the image area and might force a resizing in next expose.
349        So we disable in cases close to full.
350     */
351     if(boxw > 0.95f)
352     {
353       zx = .0f;
354       boxw = 1.01f;
355     }
356     if(boxh > 0.95f)
357     {
358       zy = .0f;
359       boxh = 1.01f;
360     }
361 
362     dt_view_set_scrollbar(self, zx, -0.5 + boxw/2, 0.5, boxw/2, zy, -0.5+ boxh/2, 0.5, boxh/2);
363   }
364 
365   if(dev->pipe->output_backbuf && // do we have an image?
366     dev->pipe->output_imgid == dev->image_storage.id && // is the right image?
367     dev->pipe->backbuf_scale == backbuf_scale && // is this the zoom scale we want to display?
368     dev->pipe->backbuf_zoom_x == zoom_x && dev->pipe->backbuf_zoom_y == zoom_y)
369   {
370     // draw image
371     mutex = &dev->pipe->backbuf_mutex;
372     dt_pthread_mutex_lock(mutex);
373     float wd = dev->pipe->output_backbuf_width;
374     float ht = dev->pipe->output_backbuf_height;
375     stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, wd);
376     surface = dt_cairo_image_surface_create_for_data(dev->pipe->output_backbuf, CAIRO_FORMAT_RGB24, wd, ht, stride);
377     wd /= darktable.gui->ppd;
378     ht /= darktable.gui->ppd;
379 
380     if(dev->iso_12646.enabled)
381     {
382       // force middle grey in background
383       cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
384     }
385     else
386     {
387       if(dev->full_preview)
388         dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_DARKROOM_PREVIEW_BG);
389       else
390         dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_DARKROOM_BG);
391     }
392     cairo_paint(cr);
393 
394     cairo_translate(cr, ceilf(.5f * (width - wd)), ceilf(.5f * (height - ht)));
395     if(closeup)
396     {
397       const double scale = 1<<closeup;
398       cairo_scale(cr, scale, scale);
399       cairo_translate(cr, -(.5 - 0.5/scale) * wd, -(.5 - 0.5/scale) * ht);
400     }
401 
402     if(dev->iso_12646.enabled)
403     {
404       // draw the white frame around picture
405       cairo_rectangle(cr, -tb / 3., -tb / 3.0, wd + 2. * tb / 3., ht + 2. * tb / 3.);
406       cairo_set_source_rgb(cr, 1., 1., 1.);
407       cairo_fill(cr);
408     }
409 
410     cairo_rectangle(cr, 0, 0, wd, ht);
411     cairo_set_source_surface(cr, surface, 0, 0);
412     cairo_pattern_set_filter(cairo_get_source(cr), _get_filtering_level(dev));
413     cairo_paint(cr);
414 
415     if(darktable.gui->show_focus_peaking)
416     {
417       cairo_save(cr);
418       cairo_scale(cr, 1./ darktable.gui->ppd, 1. / darktable.gui->ppd);
419       dt_focuspeaking(cr, wd, ht, cairo_image_surface_get_data(surface),
420                                   cairo_image_surface_get_width(surface),
421                                   cairo_image_surface_get_height(surface));
422       cairo_restore(cr);
423     }
424 
425     cairo_surface_destroy(surface);
426     dt_pthread_mutex_unlock(mutex);
427     image_surface_imgid = dev->image_storage.id;
428   }
429   else if(dev->preview_pipe->output_backbuf && dev->preview_pipe->output_imgid == dev->image_storage.id)
430   {
431     // draw preview
432     mutex = &dev->preview_pipe->backbuf_mutex;
433     dt_pthread_mutex_lock(mutex);
434 
435     const float wd = dev->preview_pipe->output_backbuf_width;
436     const float ht = dev->preview_pipe->output_backbuf_height;
437     const float zoom_scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 1);
438 
439     if(dev->iso_12646.enabled)
440     {
441       // force middle grey in background
442       cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
443     }
444     else
445     {
446       dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_DARKROOM_BG);
447     }
448 
449     cairo_paint(cr);
450 
451     if(dev->iso_12646.enabled)
452     {
453       // draw the white frame around picture
454       cairo_rectangle(cr, 2 * tb / 3., 2 * tb / 3.0, width - 4. * tb / 3., height - 4. * tb / 3.);
455       cairo_set_source_rgb(cr, 1., 1., 1.);
456       cairo_fill(cr);
457     }
458 
459     cairo_rectangle(cr, tb, tb, width-2*tb, height-2*tb);
460     cairo_clip(cr);
461     stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, wd);
462     surface
463         = cairo_image_surface_create_for_data(dev->preview_pipe->output_backbuf, CAIRO_FORMAT_RGB24, wd, ht, stride);
464     cairo_translate(cr, width / 2.0, height / 2.0f);
465     cairo_scale(cr, zoom_scale, zoom_scale);
466     cairo_translate(cr, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht);
467 
468     cairo_rectangle(cr, 0, 0, wd, ht);
469     cairo_set_source_surface(cr, surface, 0, 0);
470     cairo_pattern_set_filter(cairo_get_source(cr), _get_filtering_level(dev));
471     cairo_fill(cr);
472     cairo_surface_destroy(surface);
473     dt_pthread_mutex_unlock(mutex);
474     image_surface_imgid = dev->image_storage.id;
475   }
476   else if(dev->preview_pipe->output_imgid != dev->image_storage.id)
477   {
478     gchar *load_txt;
479     float fontsize;
480 
481     if(dev->image_invalid_cnt)
482     {
483       fontsize = DT_PIXEL_APPLY_DPI(16);
484       load_txt = dt_util_dstrcat(
485           NULL,
486           _("darktable could not load `%s', switching to lighttable now.\n\n"
487             "please check that the camera model that produced the image is supported in darktable\n"
488             "(list of supported cameras is at https://www.darktable.org/resources/camera-support/).\n"
489             "if you are sure that the camera model is supported, please consider opening an issue\n"
490             "at https://github.com/darktable-org/darktable"),
491           dev->image_storage.filename);
492       if(dev->image_invalid_cnt > 400)
493       {
494         dev->image_invalid_cnt = 0;
495         dt_view_manager_switch(darktable.view_manager, "lighttable");
496       }
497     }
498     else
499     {
500       fontsize = DT_PIXEL_APPLY_DPI(14);
501       if(dt_conf_get_bool("darkroom/ui/loading_screen"))
502         load_txt = dt_util_dstrcat(NULL, C_("darkroom", "loading `%s' ..."), dev->image_storage.filename);
503       else
504         load_txt = g_strdup(dev->image_storage.filename);
505     }
506 
507     if(dt_conf_get_bool("darkroom/ui/loading_screen"))
508     {
509       dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_DARKROOM_BG);
510       cairo_paint(cr);
511 
512       // waiting message
513       PangoRectangle ink;
514       PangoLayout *layout;
515       PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
516       pango_font_description_set_absolute_size(desc, fontsize * PANGO_SCALE);
517       pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
518       layout = pango_cairo_create_layout(cr);
519       pango_layout_set_font_description(layout, desc);
520       pango_layout_set_text(layout, load_txt, -1);
521       pango_layout_get_pixel_extents(layout, &ink, NULL);
522       const float xc = width / 2.0, yc = height * 0.85 - DT_PIXEL_APPLY_DPI(10), wd = ink.width * .5f;
523       cairo_move_to(cr, xc - wd, yc + 1. / 3. * fontsize - fontsize);
524       pango_cairo_layout_path(cr, layout);
525       cairo_set_line_width(cr, 2.0);
526       dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_LOG_BG);
527       cairo_stroke_preserve(cr);
528       dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_LOG_FG);
529       cairo_fill(cr);
530       pango_font_description_free(desc);
531       g_object_unref(layout);
532       image_surface_imgid = dev->image_storage.id;
533     }
534     else
535     {
536       dt_toast_log("%s", load_txt);
537     }
538     g_free(load_txt);
539   }
540   cairo_restore(cri);
541 
542   if(image_surface_imgid == dev->image_storage.id)
543   {
544     cairo_destroy(cr);
545     cairo_set_source_surface(cri, image_surface, 0, 0);
546     cairo_paint(cri);
547   }
548 
549   /* if we are in full preview mode, we don"t want anything else than the image */
550   if(dev->full_preview) return;
551 
552   /* check if we should create a snapshot of view */
553   if(darktable.develop->proxy.snapshot.request && !darktable.develop->image_loading)
554   {
555     /* reset the request */
556     darktable.develop->proxy.snapshot.request = FALSE;
557 
558     /* validation of snapshot filename */
559     g_assert(darktable.develop->proxy.snapshot.filename != NULL);
560 
561     /* Store current image surface to snapshot file.
562        FIXME: add checks so that we don't make snapshots of preview pipe image surface.
563     */
564     const int fd = g_open(darktable.develop->proxy.snapshot.filename, O_CREAT | O_WRONLY | O_BINARY, 0600);
565     cairo_surface_write_to_png_stream(image_surface, _write_snapshot_data, GINT_TO_POINTER(fd));
566     close(fd);
567   }
568 
569   // Displaying sample areas if enabled
570   if(darktable.lib->proxy.colorpicker.live_samples
571      && (darktable.lib->proxy.colorpicker.display_samples
572          || darktable.lib->proxy.colorpicker.selected_sample))
573   {
574     GSList *samples = darktable.lib->proxy.colorpicker.live_samples;
575     dt_colorpicker_sample_t *sample = NULL;
576 
577     const gboolean only_selected_sample =
578       darktable.lib->proxy.colorpicker.selected_sample
579       && !darktable.lib->proxy.colorpicker.display_samples;
580 
581     cairo_save(cri);
582     // The colorpicker samples bounding rectangle should only be displayed inside the visible image
583     const int pwidth = (dev->pipe->output_backbuf_width<<closeup) / darktable.gui->ppd;
584     const int pheight = (dev->pipe->output_backbuf_height<<closeup) / darktable.gui->ppd;
585 
586     const float hbar = (self->width - pwidth) * .5f;
587     const float tbar = (self->height - pheight) * .5f;
588     cairo_rectangle(cri, hbar, tbar, (double)pwidth, (double)pheight);
589     cairo_clip(cri);
590 
591     const float wd = dev->preview_pipe->backbuf_width;
592     const float ht = dev->preview_pipe->backbuf_height;
593     const float zoom_scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 1);
594     const float lw = 1.0 / zoom_scale;
595 
596     cairo_translate(cri, width / 2.0, height / 2.0f);
597     cairo_scale(cri, zoom_scale, zoom_scale);
598     cairo_translate(cri, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht);
599 
600     for( ; samples; samples = g_slist_next(samples))
601     {
602       sample = samples->data;
603 
604       // only display selected sample, skip if not the selected sample
605       if(only_selected_sample
606          && sample != darktable.lib->proxy.colorpicker.selected_sample)
607       {
608         continue;
609       }
610 
611       cairo_set_line_width(cri, lw);
612       if(sample == darktable.lib->proxy.colorpicker.selected_sample)
613         cairo_set_source_rgb(cri, .2, 0, 0);
614       else
615         cairo_set_source_rgb(cri, 0, 0, .2);
616 
617       const float *box = sample->box;
618       const float *point = sample->point;
619       if(sample->size == DT_COLORPICKER_SIZE_BOX)
620       {
621         cairo_rectangle(cri, box[0] * wd + lw, box[1] * ht + lw, (box[2] - box[0]) * wd, (box[3] - box[1]) * ht);
622         cairo_stroke(cri);
623 
624         if(sample == darktable.lib->proxy.colorpicker.selected_sample)
625           cairo_set_source_rgb(cri, .8, 0, 0);
626         else
627           cairo_set_source_rgb(cri, 0, 0, .8);
628         cairo_rectangle(cri, box[0] * wd + 2. * lw, box[1] * ht + 2. * lw, (box[2] - box[0]) * wd - 2. * lw, (box[3] - box[1]) * ht - 2. * lw);
629         cairo_stroke(cri);
630       }
631       else
632       {
633         cairo_rectangle(cri, point[0] * wd - .01 * wd, point[1] * ht - .01 * wd, .02 * wd, .02 * wd);
634         cairo_stroke(cri);
635 
636         if(sample == darktable.lib->proxy.colorpicker.selected_sample)
637           cairo_set_source_rgb(cri, .8, 0, 0);
638         else
639           cairo_set_source_rgb(cri, 0, 0, .8);
640         cairo_rectangle(cri, (point[0] - 0.01) * wd + lw,
641                         point[1] * ht - 0.01 * wd + lw, .02 * wd - 2. * lw,
642                         .02 * wd - 2. * lw);
643         cairo_move_to(cri, point[0] * wd, point[1] * ht - .01 * wd + lw);
644         cairo_line_to(cri, point[0] * wd, point[1] * ht + .01 * wd - lw);
645         cairo_move_to(cri, point[0] * wd - .01 * wd + lw, point[1] * ht);
646         cairo_line_to(cri, point[0] * wd + .01 * wd - lw, point[1] * ht);
647         cairo_stroke(cri);
648       }
649     }
650 
651     cairo_restore(cri);
652   }
653 
654   // display mask if we have a current module activated or if the masks manager module is expanded
655 
656   const gboolean display_masks = (dev->gui_module && dev->gui_module->enabled
657                                   && dt_dev_modulegroups_get_activated(darktable.develop) != DT_MODULEGROUP_BASICS)
658                                  || dt_lib_gui_get_expanded(dt_lib_get_module("masks"));
659 
660   // execute module callback hook.
661   if(dev->gui_module && dev->gui_module->request_color_pick != DT_REQUEST_COLORPICK_OFF && dev->gui_module->enabled)
662   {
663     // The colorpicker bounding rectangle should only be displayed inside the visible image
664     const int pwidth = (dev->pipe->output_backbuf_width<<closeup) / darktable.gui->ppd;
665     const int pheight = (dev->pipe->output_backbuf_height<<closeup) / darktable.gui->ppd;
666 
667     const float hbar = (self->width - pwidth) * .5f;
668     const float tbar = (self->height - pheight) * .5f;
669     cairo_save(cri);
670     cairo_rectangle(cri, hbar, tbar, (double)pwidth, (double)pheight);
671     cairo_clip(cri);
672 
673     const float wd = dev->preview_pipe->backbuf_width;
674     const float ht = dev->preview_pipe->backbuf_height;
675     const float zoom_scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 1);
676 
677     cairo_translate(cri, width / 2.0, height / 2.0f);
678     cairo_scale(cri, zoom_scale, zoom_scale);
679     cairo_translate(cri, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht);
680 
681     cairo_set_line_width(cri, 1.0 / zoom_scale);
682     cairo_set_source_rgb(cri, .2, .2, .2);
683 
684     const float *box = dev->gui_module->color_picker_box;
685     const float *point = dev->gui_module->color_picker_point;
686     if(darktable.lib->proxy.colorpicker.size)
687     {
688       cairo_translate(cri, 1.0 / zoom_scale, 1.0 / zoom_scale);
689 
690       double x = box[0] * wd, y = box[1] * ht;
691 
692       double d = 1. / zoom_scale;
693       cairo_set_source_rgb(cri, .0, .0, .0);
694       for(int blackwhite = 2; blackwhite; blackwhite--)
695       {
696         double w = 5. / zoom_scale - d;
697 
698         cairo_rectangle(cri, x + d, y + d, (box[2] - box[0]) * wd - 2. * d, (box[3] - box[1]) * ht - 2. * d);
699 
700         cairo_rectangle(cri, x - w, y - w, 2. * w, 2. * w);
701         cairo_rectangle(cri, x - w, box[3] * ht - w, 2. * w, 2. * w);
702         cairo_rectangle(cri, box[2] * wd - w, y - w, 2. * w, 2. * w);
703         cairo_rectangle(cri, box[2] * wd - w, box[3] * ht - w, 2. * w, 2. *w);
704         cairo_stroke(cri);
705 
706         d = 0;
707         cairo_set_source_rgb(cri, .8, .8, .8);
708       }
709     }
710     else if(point[0] >= 0.0f && point[0] <= 1.0f && point[1] >= 0.0f && point[1] <= 1.0f)
711     {
712       const float size = (wd + ht) / 2.0;
713       cairo_rectangle(cri,
714                       point[0] * wd - .01 * size,
715                       point[1] * ht - .01 * size,
716                       .02 * size, .02 * size);
717       cairo_stroke(cri);
718 
719       cairo_set_source_rgb(cri, .8, .8, .8);
720       cairo_rectangle(cri,
721                       point[0] * wd - .01 * size + 1.0 / zoom_scale,
722                       point[1] * ht - .01 * size + 1.0 / zoom_scale,
723                       .02 * size - 2. / zoom_scale,
724                       .02 * size - 2. / zoom_scale);
725       cairo_move_to(cri, point[0] * wd, point[1] * ht - .01 * size + 1. / zoom_scale);
726       cairo_line_to(cri, point[0] * wd, point[1] * ht + .01 * size - 1. / zoom_scale);
727       cairo_move_to(cri, point[0] * wd - .01 * size + 1. / zoom_scale, point[1] * ht);
728       cairo_line_to(cri, point[0] * wd + .01 * size - 1. / zoom_scale, point[1] * ht);
729       cairo_stroke(cri);
730     }
731     cairo_restore(cri);
732   }
733   else
734   {
735     if(dev->form_visible && display_masks)
736       dt_masks_events_post_expose(dev->gui_module, cri, width, height, pointerx, pointery);
737     // module
738     if(dev->gui_module && dev->gui_module->gui_post_expose
739        && dt_dev_modulegroups_get_activated(darktable.develop) != DT_MODULEGROUP_BASICS)
740       dev->gui_module->gui_post_expose(dev->gui_module, cri, width, height, pointerx, pointery);
741   }
742 
743   // indicate if we are in gamut check or softproof mode
744   if(darktable.color_profiles->mode != DT_PROFILE_NORMAL)
745   {
746     gchar *label = darktable.color_profiles->mode == DT_PROFILE_GAMUTCHECK ? _("gamut check") : _("soft proof");
747     cairo_set_source_rgba(cri, 0.5, 0.5, 0.5, 0.5);
748     PangoLayout *layout;
749     PangoRectangle ink;
750     PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
751     pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
752     layout = pango_cairo_create_layout(cri);
753     pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(20) * PANGO_SCALE);
754     pango_layout_set_font_description(layout, desc);
755     pango_layout_set_text(layout, label, -1);
756     pango_layout_get_pixel_extents(layout, &ink, NULL);
757     cairo_move_to(cri, ink.height * 2, height - (ink.height * 3));
758     pango_cairo_layout_path(cri, layout);
759     cairo_set_source_rgb(cri, 0.7, 0.7, 0.7);
760     cairo_fill_preserve(cri);
761     cairo_set_line_width(cri, 0.7);
762     cairo_set_source_rgb(cri, 0.3, 0.3, 0.3);
763     cairo_stroke(cri);
764     pango_font_description_free(desc);
765     g_object_unref(layout);
766   }
767 }
768 
reset(dt_view_t * self)769 void reset(dt_view_t *self)
770 {
771   dt_control_set_dev_zoom(DT_ZOOM_FIT);
772   dt_control_set_dev_zoom_x(0);
773   dt_control_set_dev_zoom_y(0);
774   dt_control_set_dev_closeup(0);
775 }
776 
try_enter(dt_view_t * self)777 int try_enter(dt_view_t *self)
778 {
779   int32_t imgid = dt_view_get_image_to_act_on();
780 
781   if(imgid < 0)
782   {
783     // fail :(
784     dt_control_log(_("no image to open !"));
785     return 1;
786   }
787 
788   // this loads the image from db if needed:
789   const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
790   // get image and check if it has been deleted from disk first!
791 
792   char imgfilename[PATH_MAX] = { 0 };
793   gboolean from_cache = TRUE;
794   dt_image_full_path(img->id, imgfilename, sizeof(imgfilename), &from_cache);
795   if(!g_file_test(imgfilename, G_FILE_TEST_IS_REGULAR))
796   {
797     dt_control_log(_("image `%s' is currently unavailable"), img->filename);
798     dt_image_cache_read_release(darktable.image_cache, img);
799     return 1;
800   }
801   // and drop the lock again.
802   dt_image_cache_read_release(darktable.image_cache, img);
803   darktable.develop->image_storage.id = imgid;
804   return 0;
805 }
806 
dt_dev_change_image(dt_develop_t * dev,const int32_t imgid)807 static void dt_dev_change_image(dt_develop_t *dev, const int32_t imgid)
808 {
809   // stop crazy users from sleeping on key-repeat spacebar:
810   if(dev->image_loading) return;
811 
812   // Pipe reset needed when changing image
813   // FIXME: synch with dev_init() and dev_cleanup() instead of redoing it
814   dev->proxy.chroma_adaptation = NULL;
815   dev->proxy.wb_is_D65 = TRUE;
816   dev->proxy.wb_coeffs[0] = 0.f;
817 
818   // change active image
819   g_slist_free(darktable.view_manager->active_images);
820   darktable.view_manager->active_images = g_slist_prepend(NULL, GINT_TO_POINTER(imgid));
821   DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_ACTIVE_IMAGES_CHANGE);
822 
823   // if the previous shown image is selected and the selection is unique
824   // then we change the selected image to the new one
825   if(dev->image_storage.id > 0)
826   {
827     sqlite3_stmt *stmt;
828     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
829                                 "SELECT m.imgid FROM memory.collected_images as m, main.selected_images as s "
830                                 "WHERE m.imgid=s.imgid",
831                                 -1, &stmt, NULL);
832     gboolean follow = FALSE;
833     if(sqlite3_step(stmt) == SQLITE_ROW)
834     {
835       if(sqlite3_column_int(stmt, 0) == dev->image_storage.id && sqlite3_step(stmt) != SQLITE_ROW)
836       {
837         follow = TRUE;
838       }
839     }
840     sqlite3_finalize(stmt);
841     if(follow)
842     {
843       dt_selection_select_single(darktable.selection, imgid);
844     }
845   }
846 
847   // disable color picker when changing image
848   if(dev->gui_module)
849   {
850     dev->gui_module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
851   }
852 
853   // update aspect ratio
854   if(dev->preview_pipe->backbuf && dev->preview_status == DT_DEV_PIXELPIPE_VALID)
855   {
856     double aspect_ratio = (double)dev->preview_pipe->backbuf_width / (double)dev->preview_pipe->backbuf_height;
857     dt_image_set_aspect_ratio_to(dev->preview_pipe->image.id, aspect_ratio, TRUE);
858   }
859   else
860   {
861     dt_image_set_aspect_ratio(dev->image_storage.id, TRUE);
862   }
863 
864   // clean the undo list
865   dt_undo_clear(darktable.undo, DT_UNDO_DEVELOP);
866 
867   // prevent accels_window to refresh
868   darktable.view_manager->accels_window.prevent_refresh = TRUE;
869 
870   // make sure we can destroy and re-setup the pixel pipes.
871   // we acquire the pipe locks, which will block the processing threads
872   // in darkroom mode before they touch the pipes (init buffers etc).
873   // we don't block here, since we hold the gdk lock, which will
874   // result in circular locking when background threads emit signals
875   // which in turn try to acquire the gdk lock.
876   //
877   // worst case, it'll drop some change image events. sorry.
878   if(dt_pthread_mutex_BAD_trylock(&dev->preview_pipe_mutex)) return;
879   if(dt_pthread_mutex_BAD_trylock(&dev->pipe_mutex))
880   {
881     dt_pthread_mutex_BAD_unlock(&dev->preview_pipe_mutex);
882     return;
883   }
884   if(dt_pthread_mutex_BAD_trylock(&dev->preview2_pipe_mutex))
885   {
886     dt_pthread_mutex_BAD_unlock(&dev->pipe_mutex);
887     dt_pthread_mutex_BAD_unlock(&dev->preview_pipe_mutex);
888     return;
889   }
890 
891   // get current plugin in focus before defocus
892   gchar *active_plugin = NULL;
893   if(darktable.develop->gui_module)
894   {
895     active_plugin = g_strdup(darktable.develop->gui_module->op);
896   }
897 
898   // store last active group
899   dt_conf_set_int("plugins/darkroom/groups", dt_dev_modulegroups_get(dev));
900 
901   dt_iop_request_focus(NULL);
902 
903   g_assert(dev->gui_attached);
904 
905   // commit image ops to db
906   dt_dev_write_history(dev);
907 
908   // be sure light table will update the thumbnail
909   if (!dt_history_hash_is_mipmap_synced(dev->image_storage.id))
910   {
911     dt_mipmap_cache_remove(darktable.mipmap_cache, dev->image_storage.id);
912     dt_image_update_final_size(dev->image_storage.id);
913     dt_image_synch_xmp(dev->image_storage.id);
914     dt_history_hash_set_mipmap(dev->image_storage.id);
915   }
916 
917   // cleanup visible masks
918   if(!dev->form_gui)
919   {
920     dev->form_gui = (dt_masks_form_gui_t *)calloc(1, sizeof(dt_masks_form_gui_t));
921     dt_masks_init_form_gui(dev->form_gui);
922   }
923   dt_masks_change_form_gui(NULL);
924 
925   while(dev->history)
926   {
927     // clear history of old image
928     dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(dev->history->data);
929     dt_dev_free_history_item(hist);
930     dev->history = g_list_delete_link(dev->history, dev->history);
931   }
932 
933   // get new image:
934   dt_dev_reload_image(dev, imgid);
935 
936   // make sure no signals propagate here:
937   ++darktable.gui->reset;
938 
939   const guint nb_iop = g_list_length(dev->iop);
940   dt_dev_pixelpipe_cleanup_nodes(dev->pipe);
941   dt_dev_pixelpipe_cleanup_nodes(dev->preview_pipe);
942   dt_dev_pixelpipe_cleanup_nodes(dev->preview2_pipe);
943   for(int i = nb_iop - 1; i >= 0; i--)
944   {
945     dt_iop_module_t *module = (dt_iop_module_t *)(g_list_nth_data(dev->iop, i));
946 
947     // the base module is the one with the lowest multi_priority
948     int base_multi_priority = 0;
949     for(const GList *l = dev->iop; l; l = g_list_next(l))
950     {
951       dt_iop_module_t *mod = (dt_iop_module_t *)l->data;
952       if(strcmp(module->op, mod->op) == 0) base_multi_priority = MIN(base_multi_priority, mod->multi_priority);
953     }
954 
955     if(module->multi_priority == base_multi_priority) // if the module is the "base" instance, we keep it
956     {
957       module->iop_order = dt_ioppr_get_iop_order(dev->iop_order_list, module->op, module->multi_priority);
958       module->multi_priority = 0;
959       module->multi_name[0] = '\0';
960       dt_iop_reload_defaults(module);
961       dt_iop_gui_update(module);
962     }
963     else // else we delete it and remove it from the panel
964     {
965       if(!dt_iop_is_hidden(module))
966       {
967         dt_iop_gui_cleanup_module(module);
968         gtk_widget_destroy(module->expander);
969       }
970 
971       // we remove the module from the list
972       dev->iop = g_list_remove_link(dev->iop, g_list_nth(dev->iop, i));
973 
974       // we cleanup the module
975       dt_accel_cleanup_closures_iop(module);
976 
977       free(module);
978     }
979   }
980   dev->iop = g_list_sort(dev->iop, dt_sort_iop_by_order);
981 
982   // we also clear the saved modules
983   while(dev->alliop)
984   {
985     dt_iop_cleanup_module((dt_iop_module_t *)dev->alliop->data);
986     free(dev->alliop->data);
987     dev->alliop = g_list_delete_link(dev->alliop, dev->alliop);
988   }
989   // and masks
990   g_list_free_full(dev->forms, (void (*)(void *))dt_masks_free_form);
991   dev->forms = NULL;
992   g_list_free_full(dev->allforms, (void (*)(void *))dt_masks_free_form);
993   dev->allforms = NULL;
994 
995   dt_dev_pixelpipe_create_nodes(dev->pipe, dev);
996   dt_dev_pixelpipe_create_nodes(dev->preview_pipe, dev);
997   if(dev->second_window.widget && GTK_IS_WIDGET(dev->second_window.widget))
998     dt_dev_pixelpipe_create_nodes(dev->preview2_pipe, dev);
999   dt_dev_read_history(dev);
1000 
1001   // we have to init all module instances other than "base" instance
1002   char option[1024];
1003   for(const GList *modules = g_list_last(dev->iop); modules; modules = g_list_previous(modules))
1004   {
1005     dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
1006     if(module->multi_priority > 0)
1007     {
1008       if(!dt_iop_is_hidden(module))
1009       {
1010         module->gui_init(module);
1011 
1012         /* add module to right panel */
1013         dt_iop_gui_set_expander(module);
1014         dt_iop_gui_update_blending(module);
1015       }
1016     }
1017     else
1018     {
1019       //  update the module header to ensure proper multi-name display
1020       if(!dt_iop_is_hidden(module))
1021       {
1022         snprintf(option, sizeof(option), "plugins/darkroom/%s/expanded", module->op);
1023         module->expanded = dt_conf_get_bool(option);
1024         dt_iop_gui_update_expanded(module);
1025         if(module->change_image) module->change_image(module);
1026         dt_iop_gui_update_header(module);
1027       }
1028     }
1029   }
1030 
1031   dt_dev_pop_history_items(dev, dev->history_end);
1032 
1033   // set the module list order
1034   dt_dev_reorder_gui_module_list(dev);
1035 
1036   dt_dev_masks_list_change(dev);
1037 
1038   /* cleanup histograms */
1039   g_list_foreach(dev->iop, (GFunc)dt_iop_cleanup_histogram, (gpointer)NULL);
1040 
1041   /* make signals work again, we can't restore the active_plugin while signals
1042      are blocked due to implementation of dt_iop_request_focus so we do it now
1043      A double history entry is not generated.
1044   */
1045   --darktable.gui->reset;
1046 
1047   /* Now we can request focus again and write a safe plugins/darkroom/active */
1048   if(active_plugin)
1049   {
1050     gboolean valid = FALSE;
1051     for(const GList *modules = dev->iop; modules; modules = g_list_next(modules))
1052     {
1053       dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
1054       if(!strcmp(module->op, active_plugin))
1055       {
1056         valid = TRUE;
1057         dt_conf_set_string("plugins/darkroom/active", active_plugin);
1058         dt_iop_request_focus(module);
1059       }
1060     }
1061     if(!valid)
1062     {
1063       dt_conf_set_string("plugins/darkroom/active", "");
1064     }
1065     g_free(active_plugin);
1066   }
1067 
1068   // Signal develop initialize
1069   DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_DEVELOP_IMAGE_CHANGED);
1070 
1071   // release pixel pipe mutices
1072   dt_pthread_mutex_BAD_unlock(&dev->preview2_pipe_mutex);
1073   dt_pthread_mutex_BAD_unlock(&dev->preview_pipe_mutex);
1074   dt_pthread_mutex_BAD_unlock(&dev->pipe_mutex);
1075 
1076   // update hint message
1077   dt_collection_hint_message(darktable.collection);
1078 
1079   // update accels_window
1080   darktable.view_manager->accels_window.prevent_refresh = FALSE;
1081   if(darktable.view_manager->accels_window.window && darktable.view_manager->accels_window.sticky)
1082     dt_view_accels_refresh(darktable.view_manager);
1083 
1084   // just make sure at this stage we have only history info into the undo, all automatic
1085   // tagging should be ignored.
1086   dt_undo_clear(darktable.undo, DT_UNDO_TAGS);
1087 
1088   //connect iop accelerators
1089   dt_iop_connect_accels_all();
1090 
1091   /* last set the group to update visibility of iop modules for new pipe */
1092   dt_dev_modulegroups_set(dev, dt_conf_get_int("plugins/darkroom/groups"));
1093 }
1094 
_view_darkroom_filmstrip_activate_callback(gpointer instance,int32_t imgid,gpointer user_data)1095 static void _view_darkroom_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
1096 {
1097   if(imgid > 0)
1098   {
1099     // switch images in darkroom mode:
1100     const dt_view_t *self = (dt_view_t *)user_data;
1101     dt_develop_t *dev = (dt_develop_t *)self->data;
1102 
1103     dt_dev_change_image(dev, imgid);
1104     // move filmstrip
1105     dt_thumbtable_set_offset_image(dt_ui_thumbtable(darktable.gui->ui), imgid, TRUE);
1106     // force redraw
1107     dt_control_queue_redraw();
1108   }
1109 }
1110 
dt_dev_jump_image(dt_develop_t * dev,int diff,gboolean by_key)1111 static void dt_dev_jump_image(dt_develop_t *dev, int diff, gboolean by_key)
1112 {
1113   if(dev->image_loading) return;
1114 
1115   const int32_t imgid = dev->image_storage.id;
1116   int new_offset = 1;
1117   int new_id = -1;
1118 
1119   // we new offset and imgid after the jump
1120   sqlite3_stmt *stmt;
1121   gchar *query = dt_util_dstrcat(NULL, "SELECT rowid, imgid "
1122                                           "FROM memory.collected_images "
1123                                           "WHERE rowid=(SELECT rowid FROM memory.collected_images WHERE imgid=%d)+%d",
1124                                  imgid, diff);
1125   DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1126   if(sqlite3_step(stmt) == SQLITE_ROW)
1127   {
1128     new_offset = sqlite3_column_int(stmt, 0);
1129     new_id = sqlite3_column_int(stmt, 1);
1130   }
1131   else if(diff > 0)
1132   {
1133     // if we are here, that means that the current is not anymore in the list
1134     // in this case, let's use the current offset image
1135     new_id = dt_ui_thumbtable(darktable.gui->ui)->offset_imgid;
1136     new_offset = dt_ui_thumbtable(darktable.gui->ui)->offset;
1137   }
1138   else
1139   {
1140     // if we are here, that means that the current is not anymore in the list
1141     // in this case, let's use the image before current offset
1142     new_offset = MAX(1, dt_ui_thumbtable(darktable.gui->ui)->offset - 1);
1143     sqlite3_stmt *stmt2;
1144     gchar *query2 = dt_util_dstrcat(NULL, "SELECT imgid FROM memory.collected_images WHERE rowid=%d", new_offset);
1145     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query2, -1, &stmt2, NULL);
1146     if(sqlite3_step(stmt2) == SQLITE_ROW)
1147     {
1148       new_id = sqlite3_column_int(stmt2, 0);
1149     }
1150     else
1151     {
1152       new_id = dt_ui_thumbtable(darktable.gui->ui)->offset_imgid;
1153       new_offset = dt_ui_thumbtable(darktable.gui->ui)->offset;
1154     }
1155     g_free(query2);
1156     sqlite3_finalize(stmt2);
1157   }
1158   g_free(query);
1159   sqlite3_finalize(stmt);
1160 
1161   if(new_id < 0 || new_id == imgid) return;
1162 
1163   // if id seems valid, we change the image and move filmstrip
1164   dt_dev_change_image(dev, new_id);
1165   dt_thumbtable_set_offset(dt_ui_thumbtable(darktable.gui->ui), new_offset, TRUE);
1166 
1167   // if it's a change by key_press, we set mouse_over to the active image
1168   if(by_key) dt_control_set_mouse_over_id(new_id);
1169 }
1170 
zoom_key_accel(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)1171 static gboolean zoom_key_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
1172                                GdkModifierType modifier, gpointer data)
1173 {
1174   dt_develop_t *dev = darktable.develop;
1175   int zoom, closeup;
1176   float zoom_x, zoom_y;
1177   switch(GPOINTER_TO_INT(data))
1178   {
1179     case 1:
1180       zoom = dt_control_get_dev_zoom();
1181       zoom_x = dt_control_get_dev_zoom_x();
1182       zoom_y = dt_control_get_dev_zoom_y();
1183       closeup = dt_control_get_dev_closeup();
1184       if(zoom == DT_ZOOM_1) closeup = (closeup > 0) ^ 1; // flip closeup/no closeup, no difference whether it was 1 or larger
1185       dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, DT_ZOOM_1, closeup, NULL, NULL);
1186       dt_control_set_dev_zoom(DT_ZOOM_1);
1187       dt_control_set_dev_zoom_x(zoom_x);
1188       dt_control_set_dev_zoom_y(zoom_y);
1189       dt_control_set_dev_closeup(closeup);
1190       break;
1191     case 2:
1192       zoom_x = zoom_y = 0.0f;
1193       dt_control_set_dev_zoom(DT_ZOOM_FILL);
1194       dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, DT_ZOOM_FILL, 0, NULL, NULL);
1195       dt_control_set_dev_zoom_x(zoom_x);
1196       dt_control_set_dev_zoom_y(zoom_y);
1197       dt_control_set_dev_closeup(0);
1198       break;
1199     case 3:
1200       dt_control_set_dev_zoom(DT_ZOOM_FIT);
1201       dt_control_set_dev_zoom_x(0);
1202       dt_control_set_dev_zoom_y(0);
1203       dt_control_set_dev_closeup(0);
1204       break;
1205     default:
1206       break;
1207   }
1208   dt_dev_invalidate(dev);
1209   dt_control_queue_redraw_center();
1210   dt_control_navigation_redraw();
1211   return TRUE;
1212 }
1213 
skip_f_key_accel_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)1214 static gboolean skip_f_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
1215                                           GdkModifierType modifier, gpointer data)
1216 {
1217   dt_dev_jump_image((dt_develop_t *)data, 1, TRUE);
1218   return TRUE;
1219 }
1220 
skip_b_key_accel_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)1221 static gboolean skip_b_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
1222                                           GdkModifierType modifier, gpointer data)
1223 {
1224   dt_dev_jump_image((dt_develop_t *)data, -1, TRUE);
1225   return TRUE;
1226 }
1227 
_darkroom_ui_pipe_finish_signal_callback(gpointer instance,gpointer data)1228 static void _darkroom_ui_pipe_finish_signal_callback(gpointer instance, gpointer data)
1229 {
1230   dt_control_queue_redraw_center();
1231 }
1232 
_darkroom_ui_preview2_pipe_finish_signal_callback(gpointer instance,gpointer user_data)1233 static void _darkroom_ui_preview2_pipe_finish_signal_callback(gpointer instance, gpointer user_data)
1234 {
1235   dt_view_t *self = (dt_view_t *)user_data;
1236   dt_develop_t *dev = (dt_develop_t *)self->data;
1237   if(dev->second_window.widget)
1238     gtk_widget_queue_draw(dev->second_window.widget);
1239 }
1240 
_darkroom_ui_favorite_presets_popupmenu(GtkWidget * w,gpointer user_data)1241 static void _darkroom_ui_favorite_presets_popupmenu(GtkWidget *w, gpointer user_data)
1242 {
1243   /* create favorites menu and popup */
1244   dt_gui_favorite_presets_menu_show();
1245 
1246   /* if we got any styles, lets popup menu for selection */
1247   if(darktable.gui->presets_popup_menu)
1248   {
1249     gtk_widget_show_all(GTK_WIDGET(darktable.gui->presets_popup_menu));
1250 
1251 #if GTK_CHECK_VERSION(3, 22, 0)
1252     gtk_menu_popup_at_pointer(darktable.gui->presets_popup_menu, NULL);
1253 #else
1254     gtk_menu_popup(darktable.gui->presets_popup_menu, NULL, NULL, NULL, NULL, 0, 0);
1255 #endif
1256   }
1257   else
1258     dt_control_log(_("no userdefined presets for favorite modules were found"));
1259 }
1260 
_darkroom_ui_apply_style_activate_callback(gchar * name)1261 static void _darkroom_ui_apply_style_activate_callback(gchar *name)
1262 {
1263   dt_control_log(_("applied style `%s' on current image"), name);
1264 
1265   /* write current history changes so nothing gets lost */
1266   dt_dev_write_history(darktable.develop);
1267 
1268   dt_dev_undo_start_record(darktable.develop);
1269 
1270   /* apply style on image and reload*/
1271   dt_styles_apply_to_image(name, FALSE, FALSE, darktable.develop->image_storage.id);
1272   dt_dev_reload_image(darktable.develop, darktable.develop->image_storage.id);
1273 
1274   DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_TAG_CHANGED);
1275 
1276   /* record current history state : after change (needed for undo) */
1277   dt_dev_undo_end_record(darktable.develop);
1278 
1279   // rebuild the accelerators (style might have changed order)
1280   dt_iop_connect_accels_all();
1281 }
1282 
_darkroom_ui_apply_style_popupmenu(GtkWidget * w,gpointer user_data)1283 static void _darkroom_ui_apply_style_popupmenu(GtkWidget *w, gpointer user_data)
1284 {
1285   /* show styles popup menu */
1286   GList *styles = dt_styles_get_list("");
1287   GtkMenuShell *menu = NULL;
1288   if(styles)
1289   {
1290     menu = GTK_MENU_SHELL(gtk_menu_new());
1291     for(const GList *st_iter = styles; st_iter; st_iter = g_list_next(st_iter))
1292     {
1293       dt_style_t *style = (dt_style_t *)st_iter->data;
1294 
1295       char *items_string = dt_styles_get_item_list_as_string(style->name);
1296       gchar *tooltip = NULL;
1297 
1298       if(style->description && *style->description)
1299       {
1300         tooltip = g_strconcat("<b>", g_markup_escape_text(style->description, -1), "</b>\n", items_string, NULL);
1301       }
1302       else
1303       {
1304         tooltip = g_strdup(items_string);
1305       }
1306 
1307       gchar **split = g_strsplit(style->name, "|", 0);
1308 
1309       // if sub-menu, do not put leading group in final name
1310 
1311       gchar *mi_name = NULL;
1312 
1313       if(split[1])
1314       {
1315         gsize mi_len = 1 + strlen(split[1]);
1316         for(int i=2; split[i]; i++)
1317           mi_len += strlen(split[i]) + strlen(" | ");
1318 
1319         mi_name = g_new0(gchar, mi_len);
1320         gchar* tmp_ptr = g_stpcpy(mi_name, split[1]);
1321         for(int i=2; split[i]; i++)
1322         {
1323           tmp_ptr = g_stpcpy(tmp_ptr, " | ");
1324           tmp_ptr = g_stpcpy(tmp_ptr, split[i]);
1325         }
1326       }
1327       else
1328         mi_name = g_strdup(split[0]);
1329 
1330       GtkWidget *mi = gtk_menu_item_new_with_label(mi_name);
1331       gtk_widget_set_tooltip_markup(mi, tooltip);
1332       g_free(mi_name);
1333 
1334       // check if we already have a sub-menu with this name
1335       GtkMenu *sm = NULL;
1336 
1337       GList *children = gtk_container_get_children(GTK_CONTAINER(menu));
1338       for(const GList *child = children; child; child = g_list_next(child))
1339       {
1340         GtkMenuItem *smi = (GtkMenuItem *)child->data;
1341         if(!g_strcmp0(split[0],gtk_menu_item_get_label(smi)))
1342         {
1343           sm = (GtkMenu *)gtk_menu_item_get_submenu(smi);
1344           break;
1345         }
1346       }
1347       g_list_free(children);
1348 
1349       GtkMenuItem *smi = NULL;
1350 
1351       // no sub-menu, but we need one
1352       if(!sm && split[1])
1353       {
1354         smi = (GtkMenuItem *)gtk_menu_item_new_with_label(split[0]);
1355         sm = (GtkMenu *)gtk_menu_new();
1356         gtk_menu_item_set_submenu(smi, GTK_WIDGET(sm));
1357       }
1358 
1359       if(sm)
1360         gtk_menu_shell_append(GTK_MENU_SHELL(sm), mi);
1361       else
1362         gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1363 
1364       if(smi)
1365       {
1366         gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(smi));
1367         gtk_widget_show(GTK_WIDGET(smi));
1368       }
1369 
1370       g_signal_connect_swapped(G_OBJECT(mi), "activate",
1371                                G_CALLBACK(_darkroom_ui_apply_style_activate_callback),
1372                                (gpointer)g_strdup(style->name));
1373       gtk_widget_show(mi);
1374 
1375       g_free(items_string);
1376       g_free(tooltip);
1377       g_strfreev(split);
1378     }
1379     g_list_free_full(styles, dt_style_free);
1380   }
1381 
1382   /* if we got any styles, lets popup menu for selection */
1383   if(menu)
1384   {
1385 #if GTK_CHECK_VERSION(3, 22, 0)
1386     gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
1387 #else
1388     gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, 0);
1389 #endif
1390   }
1391   else
1392     dt_control_log(_("no styles have been created yet"));
1393 }
1394 
_second_window_quickbutton_clicked(GtkWidget * w,dt_develop_t * dev)1395 static void _second_window_quickbutton_clicked(GtkWidget *w, dt_develop_t *dev)
1396 {
1397   if(dev->second_window.second_wnd && !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)))
1398   {
1399     _darkroom_ui_second_window_write_config(dev->second_window.second_wnd);
1400 
1401     gtk_widget_destroy(dev->second_window.second_wnd);
1402     dev->second_window.second_wnd = NULL;
1403     dev->second_window.widget = NULL;
1404   }
1405   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)))
1406     _darkroom_display_second_window(dev);
1407 }
1408 
1409 /** toolbar buttons */
1410 
_toolbar_show_popup(gpointer user_data)1411 static gboolean _toolbar_show_popup(gpointer user_data)
1412 {
1413   gtk_widget_show_all(GTK_WIDGET(user_data));
1414 
1415   // cancel glib timeout if invoked by long button press
1416   return FALSE;
1417 }
1418 
1419 /* colour assessment */
_iso_12646_quickbutton_clicked(GtkWidget * w,gpointer user_data)1420 static void _iso_12646_quickbutton_clicked(GtkWidget *w, gpointer user_data)
1421 {
1422   dt_develop_t *d = (dt_develop_t *)user_data;
1423   if (!d->gui_attached) return;
1424 
1425   d->iso_12646.enabled = !d->iso_12646.enabled;
1426   d->width = d->orig_width;
1427   d->height = d->orig_height;
1428 
1429   if(d->iso_12646.enabled)
1430   {
1431     d->border_size = 0.125 * d->width;
1432   }
1433   else
1434   {
1435     // Reset border size from config
1436     d->border_size = DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
1437   }
1438 
1439   dt_dev_configure(d, d->width, d->height);
1440 
1441   dt_ui_restore_panels(darktable.gui->ui);
1442   dt_dev_reprocess_center(d);
1443 }
1444 
1445 /* overlay color */
_overlay_color_quickbutton_clicked(GtkWidget * w,gpointer user_data)1446 static void _overlay_color_quickbutton_clicked(GtkWidget *w, gpointer user_data)
1447 {
1448   dt_develop_t *d = (dt_develop_t *)user_data;
1449   d->overlay_color.enabled = !d->overlay_color.enabled;
1450   dt_dev_reprocess_center(d);
1451 }
1452 
_overlay_color_quickbutton_pressed(GtkWidget * widget,GdkEvent * event,gpointer user_data)1453 static gboolean _overlay_color_quickbutton_pressed(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1454 {
1455   dt_develop_t *d = (dt_develop_t *)user_data;
1456   _toolbar_show_popup(d->overlay_color.floating_window);
1457   return TRUE;
1458 }
1459 
_overlay_color_quickbutton_released(GtkWidget * widget,GdkEvent * event,gpointer user_data)1460 static gboolean _overlay_color_quickbutton_released(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1461 {
1462   dt_develop_t *d = (dt_develop_t *)user_data;
1463   if(d->overlay_color.timeout > 0) g_source_remove(d->overlay_color.timeout);
1464   d->overlay_color.timeout = 0;
1465   return FALSE;
1466 }
1467 
overlay_colors_callback(GtkWidget * combo,gpointer user_data)1468 static void overlay_colors_callback(GtkWidget *combo, gpointer user_data)
1469 {
1470   dt_develop_t *d = (dt_develop_t *)user_data;
1471   d->overlay_color.color = dt_bauhaus_combobox_get(combo);
1472   dt_conf_set_int("darkroom/ui/overlay_color", d->overlay_color.color);
1473   dt_dev_reprocess_center(d);
1474 }
1475 
1476 /* overexposed */
_overexposed_quickbutton_clicked(GtkWidget * w,gpointer user_data)1477 static void _overexposed_quickbutton_clicked(GtkWidget *w, gpointer user_data)
1478 {
1479   dt_develop_t *d = (dt_develop_t *)user_data;
1480   d->overexposed.enabled = !d->overexposed.enabled;
1481   dt_dev_reprocess_center(d);
1482 }
1483 
_overexposed_quickbutton_pressed(GtkWidget * widget,GdkEvent * event,gpointer user_data)1484 static gboolean _overexposed_quickbutton_pressed(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1485 {
1486   dt_develop_t *d = (dt_develop_t *)user_data;
1487   const GdkEventButton *e = (GdkEventButton *)event;
1488   if(e->button == 3)
1489   {
1490     _toolbar_show_popup(d->overexposed.floating_window);
1491     return TRUE;
1492   }
1493   else
1494   {
1495     d->overexposed.timeout = g_timeout_add_seconds(1, _toolbar_show_popup, d->overexposed.floating_window);
1496     return FALSE;
1497   }
1498 }
1499 
_overexposed_quickbutton_released(GtkWidget * widget,GdkEvent * event,gpointer user_data)1500 static gboolean _overexposed_quickbutton_released(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1501 {
1502   dt_develop_t *d = (dt_develop_t *)user_data;
1503   if(d->overexposed.timeout > 0) g_source_remove(d->overexposed.timeout);
1504   d->overexposed.timeout = 0;
1505   return FALSE;
1506 }
1507 
colorscheme_callback(GtkWidget * combo,gpointer user_data)1508 static void colorscheme_callback(GtkWidget *combo, gpointer user_data)
1509 {
1510   dt_develop_t *d = (dt_develop_t *)user_data;
1511   d->overexposed.colorscheme = dt_bauhaus_combobox_get(combo);
1512   if(d->overexposed.enabled == FALSE)
1513     gtk_button_clicked(GTK_BUTTON(d->overexposed.button));
1514   else
1515     dt_dev_reprocess_center(d);
1516 }
1517 
lower_callback(GtkWidget * slider,gpointer user_data)1518 static void lower_callback(GtkWidget *slider, gpointer user_data)
1519 {
1520   dt_develop_t *d = (dt_develop_t *)user_data;
1521   d->overexposed.lower = dt_bauhaus_slider_get(slider);
1522   if(d->overexposed.enabled == FALSE)
1523     gtk_button_clicked(GTK_BUTTON(d->overexposed.button));
1524   else
1525     dt_dev_reprocess_center(d);
1526 }
1527 
upper_callback(GtkWidget * slider,gpointer user_data)1528 static void upper_callback(GtkWidget *slider, gpointer user_data)
1529 {
1530   dt_develop_t *d = (dt_develop_t *)user_data;
1531   d->overexposed.upper = dt_bauhaus_slider_get(slider);
1532   if(d->overexposed.enabled == FALSE)
1533     gtk_button_clicked(GTK_BUTTON(d->overexposed.button));
1534   else
1535     dt_dev_reprocess_center(d);
1536 }
1537 
mode_callback(GtkWidget * slider,gpointer user_data)1538 static void mode_callback(GtkWidget *slider, gpointer user_data)
1539 {
1540   dt_develop_t *d = (dt_develop_t *)user_data;
1541   d->overexposed.mode = dt_bauhaus_combobox_get(slider);
1542   if(d->overexposed.enabled == FALSE)
1543     gtk_button_clicked(GTK_BUTTON(d->overexposed.button));
1544   else
1545     dt_dev_reprocess_center(d);
1546 }
1547 
1548 /* rawoverexposed */
_rawoverexposed_quickbutton_clicked(GtkWidget * w,gpointer user_data)1549 static void _rawoverexposed_quickbutton_clicked(GtkWidget *w, gpointer user_data)
1550 {
1551   dt_develop_t *d = (dt_develop_t *)user_data;
1552   d->rawoverexposed.enabled = !d->rawoverexposed.enabled;
1553   dt_dev_reprocess_center(d);
1554 }
1555 
_rawoverexposed_quickbutton_pressed(GtkWidget * widget,GdkEvent * event,gpointer user_data)1556 static gboolean _rawoverexposed_quickbutton_pressed(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1557 {
1558   dt_develop_t *d = (dt_develop_t *)user_data;
1559   const GdkEventButton *e = (GdkEventButton *)event;
1560   if(e->button == 3)
1561   {
1562     _toolbar_show_popup(d->rawoverexposed.floating_window);
1563     return TRUE;
1564   }
1565   else
1566   {
1567     d->rawoverexposed.timeout = g_timeout_add_seconds(1, _toolbar_show_popup, d->rawoverexposed.floating_window);
1568     return FALSE;
1569   }
1570 }
1571 
_rawoverexposed_quickbutton_released(GtkWidget * widget,GdkEvent * event,gpointer user_data)1572 static gboolean _rawoverexposed_quickbutton_released(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1573 {
1574   dt_develop_t *d = (dt_develop_t *)user_data;
1575   if(d->rawoverexposed.timeout > 0) g_source_remove(d->rawoverexposed.timeout);
1576   d->rawoverexposed.timeout = 0;
1577   return FALSE;
1578 }
1579 
rawoverexposed_mode_callback(GtkWidget * combo,gpointer user_data)1580 static void rawoverexposed_mode_callback(GtkWidget *combo, gpointer user_data)
1581 {
1582   dt_develop_t *d = (dt_develop_t *)user_data;
1583   d->rawoverexposed.mode = dt_bauhaus_combobox_get(combo);
1584   if(d->rawoverexposed.enabled == FALSE)
1585     gtk_button_clicked(GTK_BUTTON(d->rawoverexposed.button));
1586   else
1587     dt_dev_reprocess_center(d);
1588 }
1589 
rawoverexposed_colorscheme_callback(GtkWidget * combo,gpointer user_data)1590 static void rawoverexposed_colorscheme_callback(GtkWidget *combo, gpointer user_data)
1591 {
1592   dt_develop_t *d = (dt_develop_t *)user_data;
1593   d->rawoverexposed.colorscheme = dt_bauhaus_combobox_get(combo);
1594   if(d->rawoverexposed.enabled == FALSE)
1595     gtk_button_clicked(GTK_BUTTON(d->rawoverexposed.button));
1596   else
1597     dt_dev_reprocess_center(d);
1598 }
1599 
rawoverexposed_threshold_callback(GtkWidget * slider,gpointer user_data)1600 static void rawoverexposed_threshold_callback(GtkWidget *slider, gpointer user_data)
1601 {
1602   dt_develop_t *d = (dt_develop_t *)user_data;
1603   d->rawoverexposed.threshold = dt_bauhaus_slider_get(slider);
1604   if(d->rawoverexposed.enabled == FALSE)
1605     gtk_button_clicked(GTK_BUTTON(d->rawoverexposed.button));
1606   else
1607     dt_dev_reprocess_center(d);
1608 }
1609 
_toolbox_toggle_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)1610 static gboolean _toolbox_toggle_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
1611                                              GdkModifierType modifier, gpointer data)
1612 {
1613   gtk_button_clicked(GTK_BUTTON(data));
1614   return TRUE;
1615 }
1616 
1617 /* softproof */
_softproof_quickbutton_clicked(GtkWidget * w,gpointer user_data)1618 static void _softproof_quickbutton_clicked(GtkWidget *w, gpointer user_data)
1619 {
1620   dt_develop_t *d = (dt_develop_t *)user_data;
1621   if(darktable.color_profiles->mode == DT_PROFILE_SOFTPROOF)
1622     darktable.color_profiles->mode = DT_PROFILE_NORMAL;
1623   else
1624     darktable.color_profiles->mode = DT_PROFILE_SOFTPROOF;
1625 
1626   _update_softproof_gamut_checking(d);
1627 
1628   dt_dev_reprocess_center(d);
1629 }
1630 
_softproof_quickbutton_pressed(GtkWidget * widget,GdkEvent * event,gpointer user_data)1631 static gboolean _softproof_quickbutton_pressed(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1632 {
1633   dt_develop_t *d = (dt_develop_t *)user_data;
1634   GdkEventButton *e = (GdkEventButton *)event;
1635 
1636   gtk_popover_set_relative_to(GTK_POPOVER(d->profile.floating_window), d->profile.softproof_button);
1637 
1638   if(e->button == 3)
1639   {
1640     _toolbar_show_popup(d->profile.floating_window);
1641     return TRUE;
1642   }
1643   else
1644   {
1645     d->profile.timeout = g_timeout_add_seconds(1, _toolbar_show_popup, d->profile.floating_window);
1646     return FALSE;
1647   }
1648 }
1649 
_second_window_quickbutton_pressed(GtkWidget * widget,GdkEvent * event,gpointer user_data)1650 static gboolean _second_window_quickbutton_pressed(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1651 {
1652   dt_develop_t *d = (dt_develop_t *)user_data;
1653   GdkEventButton *e = (GdkEventButton *)event;
1654 
1655   gtk_popover_set_relative_to(GTK_POPOVER(d->profile.floating_window), d->second_window.button);
1656 
1657   if(e->button == 3)
1658   {
1659     _toolbar_show_popup(d->profile.floating_window);
1660     return TRUE;
1661   }
1662   else
1663   {
1664     d->profile.timeout = g_timeout_add_seconds(1, _toolbar_show_popup, d->profile.floating_window);
1665     return FALSE;
1666   }
1667 }
1668 
_profile_quickbutton_released(GtkWidget * widget,GdkEvent * event,gpointer user_data)1669 static gboolean _profile_quickbutton_released(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1670 {
1671   dt_develop_t *d = (dt_develop_t *)user_data;
1672   if(d->profile.timeout > 0) g_source_remove(d->profile.timeout);
1673   d->profile.timeout = 0;
1674   return FALSE;
1675 }
1676 
1677 /* gamut */
_gamut_quickbutton_clicked(GtkWidget * w,gpointer user_data)1678 static void _gamut_quickbutton_clicked(GtkWidget *w, gpointer user_data)
1679 {
1680   dt_develop_t *d = (dt_develop_t *)user_data;
1681   if(darktable.color_profiles->mode == DT_PROFILE_GAMUTCHECK)
1682     darktable.color_profiles->mode = DT_PROFILE_NORMAL;
1683   else
1684     darktable.color_profiles->mode = DT_PROFILE_GAMUTCHECK;
1685 
1686   _update_softproof_gamut_checking(d);
1687 
1688   dt_dev_reprocess_center(d);
1689 }
1690 
_gamut_quickbutton_pressed(GtkWidget * widget,GdkEvent * event,gpointer user_data)1691 static gboolean _gamut_quickbutton_pressed(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1692 {
1693   dt_develop_t *d = (dt_develop_t *)user_data;
1694   GdkEventButton *e = (GdkEventButton *)event;
1695 
1696   gtk_popover_set_relative_to(GTK_POPOVER(d->profile.floating_window), d->profile.gamut_button);
1697 
1698   if(e->button == 3)
1699   {
1700     _toolbar_show_popup(d->profile.floating_window);
1701     return TRUE;
1702   }
1703   else
1704   {
1705     d->profile.timeout = g_timeout_add_seconds(1, _toolbar_show_popup, d->profile.floating_window);
1706     return FALSE;
1707   }
1708 }
1709 
1710 /* set the gui state for both softproof and gamut checking */
_update_softproof_gamut_checking(dt_develop_t * d)1711 static void _update_softproof_gamut_checking(dt_develop_t *d)
1712 {
1713   g_signal_handlers_block_by_func(d->profile.softproof_button, _softproof_quickbutton_clicked, d);
1714   g_signal_handlers_block_by_func(d->profile.gamut_button, _gamut_quickbutton_clicked, d);
1715 
1716   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->profile.softproof_button), darktable.color_profiles->mode == DT_PROFILE_SOFTPROOF);
1717   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->profile.gamut_button), darktable.color_profiles->mode == DT_PROFILE_GAMUTCHECK);
1718 
1719   g_signal_handlers_unblock_by_func(d->profile.softproof_button, _softproof_quickbutton_clicked, d);
1720   g_signal_handlers_unblock_by_func(d->profile.gamut_button, _gamut_quickbutton_clicked, d);
1721 }
1722 
display_intent_callback(GtkWidget * combo,gpointer user_data)1723 static void display_intent_callback(GtkWidget *combo, gpointer user_data)
1724 {
1725   dt_develop_t *d = (dt_develop_t *)user_data;
1726   const int pos = dt_bauhaus_combobox_get(combo);
1727 
1728   dt_iop_color_intent_t new_intent = darktable.color_profiles->display_intent;
1729 
1730   // we are not using the int value directly so it's robust against changes on lcms' side
1731   switch(pos)
1732   {
1733     case 0:
1734       new_intent = DT_INTENT_PERCEPTUAL;
1735       break;
1736     case 1:
1737       new_intent = DT_INTENT_RELATIVE_COLORIMETRIC;
1738       break;
1739     case 2:
1740       new_intent = DT_INTENT_SATURATION;
1741       break;
1742     case 3:
1743       new_intent = DT_INTENT_ABSOLUTE_COLORIMETRIC;
1744       break;
1745   }
1746 
1747   if(new_intent != darktable.color_profiles->display_intent)
1748   {
1749     darktable.color_profiles->display_intent = new_intent;
1750     dt_dev_reprocess_all(d);
1751   }
1752 }
1753 
display2_intent_callback(GtkWidget * combo,gpointer user_data)1754 static void display2_intent_callback(GtkWidget *combo, gpointer user_data)
1755 {
1756   dt_develop_t *d = (dt_develop_t *)user_data;
1757   const int pos = dt_bauhaus_combobox_get(combo);
1758 
1759   dt_iop_color_intent_t new_intent = darktable.color_profiles->display2_intent;
1760 
1761   // we are not using the int value directly so it's robust against changes on lcms' side
1762   switch(pos)
1763   {
1764     case 0:
1765       new_intent = DT_INTENT_PERCEPTUAL;
1766       break;
1767     case 1:
1768       new_intent = DT_INTENT_RELATIVE_COLORIMETRIC;
1769       break;
1770     case 2:
1771       new_intent = DT_INTENT_SATURATION;
1772       break;
1773     case 3:
1774       new_intent = DT_INTENT_ABSOLUTE_COLORIMETRIC;
1775       break;
1776   }
1777 
1778   if(new_intent != darktable.color_profiles->display2_intent)
1779   {
1780     darktable.color_profiles->display2_intent = new_intent;
1781     dt_dev_reprocess_all(d);
1782   }
1783 }
1784 
softproof_profile_callback(GtkWidget * combo,gpointer user_data)1785 static void softproof_profile_callback(GtkWidget *combo, gpointer user_data)
1786 {
1787   dt_develop_t *d = (dt_develop_t *)user_data;
1788   gboolean profile_changed = FALSE;
1789   const int pos = dt_bauhaus_combobox_get(combo);
1790   for(GList *profiles = darktable.color_profiles->profiles; profiles; profiles = g_list_next(profiles))
1791   {
1792     dt_colorspaces_color_profile_t *pp = (dt_colorspaces_color_profile_t *)profiles->data;
1793     if(pp->out_pos == pos)
1794     {
1795       if(darktable.color_profiles->softproof_type != pp->type
1796         || (darktable.color_profiles->softproof_type == DT_COLORSPACE_FILE
1797             && strcmp(darktable.color_profiles->softproof_filename, pp->filename)))
1798 
1799       {
1800         darktable.color_profiles->softproof_type = pp->type;
1801         g_strlcpy(darktable.color_profiles->softproof_filename, pp->filename,
1802                   sizeof(darktable.color_profiles->softproof_filename));
1803         profile_changed = TRUE;
1804       }
1805       goto end;
1806     }
1807   }
1808 
1809   // profile not found, fall back to sRGB. shouldn't happen
1810   fprintf(stderr, "can't find softproof profile `%s', using sRGB instead\n", dt_bauhaus_combobox_get_text(combo));
1811   profile_changed = darktable.color_profiles->softproof_type != DT_COLORSPACE_SRGB;
1812   darktable.color_profiles->softproof_type = DT_COLORSPACE_SRGB;
1813   darktable.color_profiles->softproof_filename[0] = '\0';
1814 
1815 end:
1816   if(profile_changed)
1817   {
1818     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_CONTROL_PROFILE_USER_CHANGED, DT_COLORSPACES_PROFILE_TYPE_SOFTPROOF);
1819     dt_dev_reprocess_all(d);
1820   }
1821 }
1822 
display_profile_callback(GtkWidget * combo,gpointer user_data)1823 static void display_profile_callback(GtkWidget *combo, gpointer user_data)
1824 {
1825   dt_develop_t *d = (dt_develop_t *)user_data;
1826   gboolean profile_changed = FALSE;
1827   const int pos = dt_bauhaus_combobox_get(combo);
1828   for(GList *profiles = darktable.color_profiles->profiles; profiles; profiles = g_list_next(profiles))
1829   {
1830     dt_colorspaces_color_profile_t *pp = (dt_colorspaces_color_profile_t *)profiles->data;
1831     if(pp->display_pos == pos)
1832     {
1833       if(darktable.color_profiles->display_type != pp->type
1834         || (darktable.color_profiles->display_type == DT_COLORSPACE_FILE
1835             && strcmp(darktable.color_profiles->display_filename, pp->filename)))
1836       {
1837         darktable.color_profiles->display_type = pp->type;
1838         g_strlcpy(darktable.color_profiles->display_filename, pp->filename,
1839                   sizeof(darktable.color_profiles->display_filename));
1840         profile_changed = TRUE;
1841       }
1842       goto end;
1843     }
1844   }
1845 
1846   // profile not found, fall back to system display profile. shouldn't happen
1847   fprintf(stderr, "can't find display profile `%s', using system display profile instead\n", dt_bauhaus_combobox_get_text(combo));
1848   profile_changed = darktable.color_profiles->display_type != DT_COLORSPACE_DISPLAY;
1849   darktable.color_profiles->display_type = DT_COLORSPACE_DISPLAY;
1850   darktable.color_profiles->display_filename[0] = '\0';
1851 
1852 end:
1853   if(profile_changed)
1854   {
1855     pthread_rwlock_rdlock(&darktable.color_profiles->xprofile_lock);
1856     dt_colorspaces_update_display_transforms();
1857     pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
1858     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_CONTROL_PROFILE_USER_CHANGED, DT_COLORSPACES_PROFILE_TYPE_DISPLAY);
1859     dt_dev_reprocess_all(d);
1860   }
1861 }
1862 
display2_profile_callback(GtkWidget * combo,gpointer user_data)1863 static void display2_profile_callback(GtkWidget *combo, gpointer user_data)
1864 {
1865   dt_develop_t *d = (dt_develop_t *)user_data;
1866   gboolean profile_changed = FALSE;
1867   const int pos = dt_bauhaus_combobox_get(combo);
1868   for(GList *profiles = darktable.color_profiles->profiles; profiles; profiles = g_list_next(profiles))
1869   {
1870     dt_colorspaces_color_profile_t *pp = (dt_colorspaces_color_profile_t *)profiles->data;
1871     if(pp->display2_pos == pos)
1872     {
1873       if(darktable.color_profiles->display2_type != pp->type
1874          || (darktable.color_profiles->display2_type == DT_COLORSPACE_FILE
1875              && strcmp(darktable.color_profiles->display2_filename, pp->filename)))
1876       {
1877         darktable.color_profiles->display2_type = pp->type;
1878         g_strlcpy(darktable.color_profiles->display2_filename, pp->filename,
1879                   sizeof(darktable.color_profiles->display2_filename));
1880         profile_changed = TRUE;
1881       }
1882       goto end;
1883     }
1884   }
1885 
1886   // profile not found, fall back to system display2 profile. shouldn't happen
1887   fprintf(stderr, "can't find preview display profile `%s', using system display profile instead\n",
1888           dt_bauhaus_combobox_get_text(combo));
1889   profile_changed = darktable.color_profiles->display2_type != DT_COLORSPACE_DISPLAY2;
1890   darktable.color_profiles->display2_type = DT_COLORSPACE_DISPLAY2;
1891   darktable.color_profiles->display2_filename[0] = '\0';
1892 
1893 end:
1894   if(profile_changed)
1895   {
1896     pthread_rwlock_rdlock(&darktable.color_profiles->xprofile_lock);
1897     dt_colorspaces_update_display2_transforms();
1898     pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
1899     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_CONTROL_PROFILE_USER_CHANGED,
1900                             DT_COLORSPACES_PROFILE_TYPE_DISPLAY2);
1901     dt_dev_reprocess_all(d);
1902   }
1903 }
1904 
histogram_profile_callback(GtkWidget * combo,gpointer user_data)1905 static void histogram_profile_callback(GtkWidget *combo, gpointer user_data)
1906 {
1907   dt_develop_t *d = (dt_develop_t *)user_data;
1908   gboolean profile_changed = FALSE;
1909   const int pos = dt_bauhaus_combobox_get(combo);
1910   for(GList *profiles = darktable.color_profiles->profiles; profiles; profiles = g_list_next(profiles))
1911   {
1912     dt_colorspaces_color_profile_t *pp = (dt_colorspaces_color_profile_t *)profiles->data;
1913     if(pp->category_pos == pos)
1914     {
1915       if(darktable.color_profiles->histogram_type != pp->type
1916         || (darktable.color_profiles->histogram_type == DT_COLORSPACE_FILE
1917             && strcmp(darktable.color_profiles->histogram_filename, pp->filename)))
1918       {
1919         darktable.color_profiles->histogram_type = pp->type;
1920         g_strlcpy(darktable.color_profiles->histogram_filename, pp->filename,
1921                   sizeof(darktable.color_profiles->histogram_filename));
1922         profile_changed = TRUE;
1923       }
1924       goto end;
1925     }
1926   }
1927 
1928   // profile not found, fall back to export profile. shouldn't happen
1929   fprintf(stderr, "can't find histogram profile `%s', using export profile instead\n", dt_bauhaus_combobox_get_text(combo));
1930   profile_changed = darktable.color_profiles->histogram_type != DT_COLORSPACE_WORK;
1931   darktable.color_profiles->histogram_type = DT_COLORSPACE_WORK;
1932   darktable.color_profiles->histogram_filename[0] = '\0';
1933 
1934 end:
1935   if(profile_changed)
1936   {
1937     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_CONTROL_PROFILE_USER_CHANGED, DT_COLORSPACES_PROFILE_TYPE_HISTOGRAM);
1938     dt_dev_reprocess_all(d);
1939   }
1940 }
1941 
1942 // FIXME: turning off lcms2 in prefs hides the widget but leaves the window sized like before -> ugly-ish
_preference_changed(gpointer instance,gpointer user_data)1943 static void _preference_changed(gpointer instance, gpointer user_data)
1944 {
1945   GtkWidget *display_intent = GTK_WIDGET(user_data);
1946 
1947   const int force_lcms2 = dt_conf_get_bool("plugins/lighttable/export/force_lcms2");
1948   if(force_lcms2)
1949   {
1950     gtk_widget_set_no_show_all(display_intent, FALSE);
1951     gtk_widget_set_visible(display_intent, TRUE);
1952   }
1953   else
1954   {
1955     gtk_widget_set_no_show_all(display_intent, TRUE);
1956     gtk_widget_set_visible(display_intent, FALSE);
1957   }
1958 }
1959 
_preference_prev_downsample_change(gpointer instance,gpointer user_data)1960 static void _preference_prev_downsample_change(gpointer instance, gpointer user_data)
1961 {
1962   if(user_data != NULL)
1963   {
1964     float *ds_value = user_data;
1965     *ds_value = dt_dev_get_preview_downsampling();
1966   }
1967 }
1968 
_preference_changed_button_hide(gpointer instance,dt_develop_t * dev)1969 static void _preference_changed_button_hide(gpointer instance, dt_develop_t *dev)
1970 {
1971   for(const GList *modules = dev->iop; modules; modules = g_list_next(modules))
1972   {
1973     dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
1974 
1975     if(module->header)
1976       add_remove_mask_indicator(module, (module->blend_params->mask_mode != DEVELOP_MASK_DISABLED) &&
1977                                 (module->blend_params->mask_mode != DEVELOP_MASK_ENABLED));
1978   }
1979 }
1980 
_update_display_profile_cmb(GtkWidget * cmb_display_profile)1981 static void _update_display_profile_cmb(GtkWidget *cmb_display_profile)
1982 {
1983   for(const GList *l = darktable.color_profiles->profiles; l; l = g_list_next(l))
1984   {
1985     dt_colorspaces_color_profile_t *prof = (dt_colorspaces_color_profile_t *)l->data;
1986     if(prof->display_pos > -1)
1987     {
1988       if(prof->type == darktable.color_profiles->display_type
1989          && (prof->type != DT_COLORSPACE_FILE
1990              || !strcmp(prof->filename, darktable.color_profiles->display_filename)))
1991       {
1992         if(dt_bauhaus_combobox_get(cmb_display_profile) != prof->display_pos)
1993         {
1994           dt_bauhaus_combobox_set(cmb_display_profile, prof->display_pos);
1995           break;
1996         }
1997       }
1998     }
1999   }
2000 }
2001 
_update_display2_profile_cmb(GtkWidget * cmb_display_profile)2002 static void _update_display2_profile_cmb(GtkWidget *cmb_display_profile)
2003 {
2004   for(const GList *l = darktable.color_profiles->profiles; l; l = g_list_next(l))
2005   {
2006     dt_colorspaces_color_profile_t *prof = (dt_colorspaces_color_profile_t *)l->data;
2007     if(prof->display2_pos > -1)
2008     {
2009       if(prof->type == darktable.color_profiles->display2_type
2010          && (prof->type != DT_COLORSPACE_FILE
2011              || !strcmp(prof->filename, darktable.color_profiles->display2_filename)))
2012       {
2013         if(dt_bauhaus_combobox_get(cmb_display_profile) != prof->display2_pos)
2014         {
2015           dt_bauhaus_combobox_set(cmb_display_profile, prof->display2_pos);
2016           break;
2017         }
2018       }
2019     }
2020   }
2021 }
2022 
_display_profile_changed(gpointer instance,uint8_t profile_type,gpointer user_data)2023 static void _display_profile_changed(gpointer instance, uint8_t profile_type, gpointer user_data)
2024 {
2025   GtkWidget *cmb_display_profile = GTK_WIDGET(user_data);
2026 
2027   _update_display_profile_cmb(cmb_display_profile);
2028 }
2029 
_display2_profile_changed(gpointer instance,uint8_t profile_type,gpointer user_data)2030 static void _display2_profile_changed(gpointer instance, uint8_t profile_type, gpointer user_data)
2031 {
2032   GtkWidget *cmb_display_profile = GTK_WIDGET(user_data);
2033 
2034   _update_display2_profile_cmb(cmb_display_profile);
2035 }
2036 
2037 /** end of toolbox */
2038 
_brush_size_up_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2039 static gboolean _brush_size_up_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2040                                         GdkModifierType modifier, gpointer data)
2041 {
2042   dt_develop_t *dev = (dt_develop_t *)data;
2043 
2044   if(dev->form_visible) dt_masks_events_mouse_scrolled(dev->gui_module, 0, 0, 0, 0);
2045   return TRUE;
2046 }
_brush_size_down_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2047 static gboolean _brush_size_down_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2048                                           GdkModifierType modifier, gpointer data)
2049 {
2050   dt_develop_t *dev = (dt_develop_t *)data;
2051 
2052   if(dev->form_visible) dt_masks_events_mouse_scrolled(dev->gui_module, 0, 0, 1, 0);
2053   return TRUE;
2054 }
2055 
_brush_hardness_up_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2056 static gboolean _brush_hardness_up_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2057                                             GdkModifierType modifier, gpointer data)
2058 {
2059   dt_develop_t *dev = (dt_develop_t *)data;
2060 
2061   if(dev->form_visible) dt_masks_events_mouse_scrolled(dev->gui_module, 0, 0, 0, GDK_SHIFT_MASK);
2062   return TRUE;
2063 }
_brush_hardness_down_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2064 static gboolean _brush_hardness_down_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2065                                               GdkModifierType modifier, gpointer data)
2066 {
2067   dt_develop_t *dev = (dt_develop_t *)data;
2068 
2069   if(dev->form_visible) dt_masks_events_mouse_scrolled(dev->gui_module, 0, 0, 1, GDK_SHIFT_MASK);
2070   return TRUE;
2071 }
2072 
_brush_opacity_up_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2073 static gboolean _brush_opacity_up_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2074                                            GdkModifierType modifier, gpointer data)
2075 {
2076   dt_develop_t *dev = (dt_develop_t *)data;
2077 
2078   if(dev->form_visible) dt_masks_events_mouse_scrolled(dev->gui_module, 0, 0, 0, GDK_CONTROL_MASK);
2079   return TRUE;
2080 }
_brush_opacity_down_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2081 static gboolean _brush_opacity_down_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2082                                              GdkModifierType modifier, gpointer data)
2083 {
2084   dt_develop_t *dev = (dt_develop_t *)data;
2085 
2086   if(dev->form_visible) dt_masks_events_mouse_scrolled(dev->gui_module, 0, 0, 1, GDK_CONTROL_MASK);
2087   return TRUE;
2088 }
2089 
_overlay_cycle_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2090 static gboolean _overlay_cycle_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2091                                              GdkModifierType modifier, gpointer data)
2092 {
2093   dt_develop_t *dev = (dt_develop_t *)data;
2094   GtkWidget * combobox = dev->overlay_color.colors;
2095 
2096   const int currentval = dt_bauhaus_combobox_get(combobox);
2097   const int nextval = currentval + 1 >= dt_bauhaus_combobox_length(combobox) ? 0 : currentval + 1;
2098   dt_bauhaus_combobox_set(combobox, nextval);
2099   dt_accel_widget_toast(combobox);
2100   return TRUE;
2101 }
2102 
_toggle_mask_visibility_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2103 static gboolean _toggle_mask_visibility_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2104                                              GdkModifierType modifier, gpointer data)
2105 {
2106   if(darktable.gui->reset) return FALSE;
2107 
2108   dt_develop_t *dev = (dt_develop_t *)data;
2109   dt_iop_module_t *mod = dev->gui_module;
2110 
2111   //retouch and spot removal module use masks differently and have different buttons associated
2112   //keep the shortcuts independent
2113   if(mod && strcmp(mod->so->op, "spots") != 0 && strcmp(mod->so->op, "retouch") != 0)
2114   {
2115     dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)mod->blend_data;
2116 
2117     ++darktable.gui->reset;
2118 
2119     dt_iop_color_picker_reset(mod, TRUE);
2120 
2121     dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop, mod->blend_params->mask_id);
2122     if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
2123     {
2124       if(bd->masks_shown == DT_MASKS_EDIT_OFF)
2125         bd->masks_shown = DT_MASKS_EDIT_FULL;
2126       else
2127         bd->masks_shown = DT_MASKS_EDIT_OFF;
2128 
2129       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), bd->masks_shown != DT_MASKS_EDIT_OFF);
2130       dt_masks_set_edit_mode(mod, bd->masks_shown);
2131 
2132       // set all add shape buttons to inactive
2133       for(int n = 0; n < DEVELOP_MASKS_NB_SHAPES; n++)
2134         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), FALSE);
2135     }
2136 
2137     --darktable.gui->reset;
2138 
2139     return TRUE;
2140   }
2141   else
2142     return FALSE;
2143 }
2144 
gui_init(dt_view_t * self)2145 void gui_init(dt_view_t *self)
2146 {
2147   dt_develop_t *dev = (dt_develop_t *)self->data;
2148   /*
2149    * Add view specific tool buttons
2150    */
2151 
2152   /* create favorite plugin preset popup tool */
2153   GtkWidget *favorite_presets
2154       = dtgtk_button_new(dtgtk_cairo_paint_presets, CPF_STYLE_FLAT, NULL);
2155   gtk_widget_set_tooltip_text(favorite_presets, _("quick access to presets"));
2156   g_signal_connect(G_OBJECT(favorite_presets), "clicked", G_CALLBACK(_darkroom_ui_favorite_presets_popupmenu),
2157                    NULL);
2158   dt_gui_add_help_link(favorite_presets, dt_get_help_url("favorite_presets"));
2159   dt_view_manager_view_toolbox_add(darktable.view_manager, favorite_presets, DT_VIEW_DARKROOM);
2160 
2161   /* create quick styles popup menu tool */
2162   GtkWidget *styles = dtgtk_button_new(dtgtk_cairo_paint_styles, CPF_STYLE_FLAT, NULL);
2163   g_signal_connect(G_OBJECT(styles), "clicked", G_CALLBACK(_darkroom_ui_apply_style_popupmenu), NULL);
2164   gtk_widget_set_tooltip_text(styles, _("quick access for applying any of your styles"));
2165   dt_gui_add_help_link(styles, dt_get_help_url("bottom_panel_styles"));
2166   dt_view_manager_view_toolbox_add(darktable.view_manager, styles, DT_VIEW_DARKROOM);
2167 
2168   /* create second window display button */
2169   dev->second_window.button
2170       = dtgtk_togglebutton_new(dtgtk_cairo_paint_display2, CPF_STYLE_FLAT, NULL);
2171   g_signal_connect(G_OBJECT(dev->second_window.button), "clicked", G_CALLBACK(_second_window_quickbutton_clicked),
2172                    dev);
2173   g_signal_connect(G_OBJECT(dev->second_window.button), "button-press-event",
2174                    G_CALLBACK(_second_window_quickbutton_pressed), dev);
2175   g_signal_connect(G_OBJECT(dev->second_window.button), "button-release-event",
2176                    G_CALLBACK(_profile_quickbutton_released), dev);
2177   gtk_widget_set_tooltip_text(dev->second_window.button, _("display a second darkroom image window"));
2178   dt_view_manager_view_toolbox_add(darktable.view_manager, dev->second_window.button, DT_VIEW_DARKROOM);
2179 
2180   const int dialog_width       = 350;
2181   const int large_dialog_width = 550; // for dialog with profile names
2182 
2183   /* Enable ISO 12646-compliant colour assessment conditions */
2184   dev->iso_12646.button
2185       = dtgtk_togglebutton_new(dtgtk_cairo_paint_bulb, CPF_STYLE_FLAT, NULL);
2186   gtk_widget_set_tooltip_text(dev->iso_12646.button,
2187                               _("toggle ISO 12646 color assessment conditions"));
2188   g_signal_connect(G_OBJECT(dev->iso_12646.button), "clicked", G_CALLBACK(_iso_12646_quickbutton_clicked), dev);
2189   dt_view_manager_module_toolbox_add(darktable.view_manager, dev->iso_12646.button, DT_VIEW_DARKROOM);
2190 
2191   /* create rawoverexposed popup tool */
2192   {
2193     // the button
2194     dev->rawoverexposed.button
2195         = dtgtk_togglebutton_new(dtgtk_cairo_paint_rawoverexposed, CPF_STYLE_FLAT, NULL);
2196     gtk_widget_set_tooltip_text(dev->rawoverexposed.button,
2197                                 _("toggle raw over exposed indication\nright click for options"));
2198     g_signal_connect(G_OBJECT(dev->rawoverexposed.button), "clicked",
2199                      G_CALLBACK(_rawoverexposed_quickbutton_clicked), dev);
2200     g_signal_connect(G_OBJECT(dev->rawoverexposed.button), "button-press-event",
2201                      G_CALLBACK(_rawoverexposed_quickbutton_pressed), dev);
2202     g_signal_connect(G_OBJECT(dev->rawoverexposed.button), "button-release-event",
2203                      G_CALLBACK(_rawoverexposed_quickbutton_released), dev);
2204     dt_view_manager_module_toolbox_add(darktable.view_manager, dev->rawoverexposed.button, DT_VIEW_DARKROOM);
2205     dt_gui_add_help_link(dev->rawoverexposed.button, dt_get_help_url("rawoverexposed"));
2206 
2207     // and the popup window
2208     dev->rawoverexposed.floating_window = gtk_popover_new(dev->rawoverexposed.button);
2209     gtk_widget_set_size_request(GTK_WIDGET(dev->rawoverexposed.floating_window), dialog_width, -1);
2210 #if GTK_CHECK_VERSION(3, 16, 0)
2211     g_object_set(G_OBJECT(dev->rawoverexposed.floating_window), "transitions-enabled", FALSE, NULL);
2212 #endif
2213 
2214     GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2215     gtk_container_add(GTK_CONTAINER(dev->rawoverexposed.floating_window), vbox);
2216 
2217     /** let's fill the encapsulating widgets */
2218     /* mode of operation */
2219     GtkWidget *mode = dt_bauhaus_combobox_new(NULL);
2220     dt_bauhaus_widget_set_label(mode, NULL, N_("mode"));
2221     dt_bauhaus_combobox_add(mode, _("mark with CFA color"));
2222     dt_bauhaus_combobox_add(mode, _("mark with solid color"));
2223     dt_bauhaus_combobox_add(mode, _("false color"));
2224     dt_bauhaus_combobox_set(mode, dev->rawoverexposed.mode);
2225     gtk_widget_set_tooltip_text(mode, _("select how to mark the clipped pixels"));
2226     g_signal_connect(G_OBJECT(mode), "value-changed", G_CALLBACK(rawoverexposed_mode_callback), dev);
2227     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(mode), TRUE, TRUE, 0);
2228     gtk_widget_set_state_flags(mode, GTK_STATE_FLAG_SELECTED, TRUE);
2229 
2230     /* color scheme */
2231     GtkWidget *colorscheme = dt_bauhaus_combobox_new(NULL);
2232     dt_bauhaus_widget_set_label(colorscheme, NULL, N_("color scheme"));
2233     dt_bauhaus_combobox_add(colorscheme, C_("solidcolor", "red"));
2234     dt_bauhaus_combobox_add(colorscheme, C_("solidcolor", "green"));
2235     dt_bauhaus_combobox_add(colorscheme, C_("solidcolor", "blue"));
2236     dt_bauhaus_combobox_add(colorscheme, C_("solidcolor", "black"));
2237     dt_bauhaus_combobox_set(colorscheme, dev->rawoverexposed.colorscheme);
2238     gtk_widget_set_tooltip_text(
2239         colorscheme,
2240         _("select the solid color to indicate over exposure.\nwill only be used if mode = mark with solid color"));
2241     g_signal_connect(G_OBJECT(colorscheme), "value-changed", G_CALLBACK(rawoverexposed_colorscheme_callback), dev);
2242     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(colorscheme), TRUE, TRUE, 0);
2243     gtk_widget_set_state_flags(colorscheme, GTK_STATE_FLAG_SELECTED, TRUE);
2244 
2245     /* threshold */
2246     GtkWidget *threshold = dt_bauhaus_slider_new_with_range(NULL, 0.0, 2.0, 0.01, 1.0, 3);
2247     dt_bauhaus_slider_set(threshold, dev->rawoverexposed.threshold);
2248     dt_bauhaus_widget_set_label(threshold, NULL, N_("clipping threshold"));
2249     gtk_widget_set_tooltip_text(
2250         threshold, _("threshold of what shall be considered overexposed\n1.0 - white level\n0.0 - black level"));
2251     g_signal_connect(G_OBJECT(threshold), "value-changed", G_CALLBACK(rawoverexposed_threshold_callback), dev);
2252     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(threshold), TRUE, TRUE, 0);
2253   }
2254 
2255   /* create overexposed popup tool */
2256   {
2257     // the button
2258     dev->overexposed.button
2259         = dtgtk_togglebutton_new(dtgtk_cairo_paint_overexposed, CPF_STYLE_FLAT, NULL);
2260     gtk_widget_set_tooltip_text(dev->overexposed.button,
2261                                 _("toggle clipping indication\nright click for options"));
2262     g_signal_connect(G_OBJECT(dev->overexposed.button), "clicked",
2263                      G_CALLBACK(_overexposed_quickbutton_clicked), dev);
2264     g_signal_connect(G_OBJECT(dev->overexposed.button), "button-press-event",
2265                      G_CALLBACK(_overexposed_quickbutton_pressed), dev);
2266     g_signal_connect(G_OBJECT(dev->overexposed.button), "button-release-event",
2267                      G_CALLBACK(_overexposed_quickbutton_released), dev);
2268     dt_view_manager_module_toolbox_add(darktable.view_manager, dev->overexposed.button, DT_VIEW_DARKROOM);
2269     dt_gui_add_help_link(dev->overexposed.button, dt_get_help_url("overexposed"));
2270 
2271     // and the popup window
2272     dev->overexposed.floating_window = gtk_popover_new(dev->overexposed.button);
2273     gtk_widget_set_size_request(GTK_WIDGET(dev->overexposed.floating_window), dialog_width, -1);
2274 #if GTK_CHECK_VERSION(3, 16, 0)
2275     g_object_set(G_OBJECT(dev->overexposed.floating_window), "transitions-enabled", FALSE, NULL);
2276 #endif
2277 
2278     GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2279     gtk_container_add(GTK_CONTAINER(dev->overexposed.floating_window), vbox);
2280 
2281     /** let's fill the encapsulating widgets */
2282     /* preview mode */
2283     GtkWidget *mode = dt_bauhaus_combobox_new(NULL);
2284     dt_bauhaus_widget_set_label(mode, NULL, N_("clipping preview mode"));
2285     dt_bauhaus_combobox_add(mode, _("full gamut"));
2286     dt_bauhaus_combobox_add(mode, _("any RGB channel"));
2287     dt_bauhaus_combobox_add(mode, _("luminance only"));
2288     dt_bauhaus_combobox_add(mode, _("saturation only"));
2289     dt_bauhaus_combobox_set(mode, dev->overexposed.mode);
2290     gtk_widget_set_tooltip_text(mode, _("select the metric you want to preview\n"
2291                                         "full gamut is the combination of all other modes\n"));
2292     g_signal_connect(G_OBJECT(mode), "value-changed", G_CALLBACK(mode_callback), dev);
2293     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(mode), TRUE, TRUE, 0);
2294     gtk_widget_set_state_flags(mode, GTK_STATE_FLAG_SELECTED, TRUE);
2295 
2296     /* color scheme */
2297     GtkWidget *colorscheme = dt_bauhaus_combobox_new(NULL);
2298     dt_bauhaus_widget_set_label(colorscheme, NULL, N_("color scheme"));
2299     dt_bauhaus_combobox_add(colorscheme, _("black & white"));
2300     dt_bauhaus_combobox_add(colorscheme, _("red & blue"));
2301     dt_bauhaus_combobox_add(colorscheme, _("purple & green"));
2302     dt_bauhaus_combobox_set(colorscheme, dev->overexposed.colorscheme);
2303     gtk_widget_set_tooltip_text(colorscheme, _("select colors to indicate clipping"));
2304     g_signal_connect(G_OBJECT(colorscheme), "value-changed", G_CALLBACK(colorscheme_callback), dev);
2305     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(colorscheme), TRUE, TRUE, 0);
2306     gtk_widget_set_state_flags(colorscheme, GTK_STATE_FLAG_SELECTED, TRUE);
2307 
2308     /* lower */
2309     GtkWidget *lower = dt_bauhaus_slider_new_with_range(NULL, -32., -4., 1., -12.69, 2);
2310     dt_bauhaus_slider_set(lower, dev->overexposed.lower);
2311     dt_bauhaus_slider_set_format(lower, _("%+.2f EV"));
2312     dt_bauhaus_widget_set_label(lower, NULL, N_("lower threshold"));
2313     gtk_widget_set_tooltip_text(lower, _("clipping threshold for the black point,\n"
2314                                          "in EV, relatively to white (0 EV).\n"
2315                                          "8 bits sRGB clips blacks at -12.69 EV,\n"
2316                                          "8 bits Adobe RGB clips blacks at -19.79 EV,\n"
2317                                          "16 bits sRGB clips blacks at -20.69 EV,\n"
2318                                          "typical fine-art mat prints produce black at -5.30 EV,\n"
2319                                          "typical color glossy prints produce black at -8.00 EV,\n"
2320                                          "typical B&W glossy prints produce black at -9.00 EV."
2321                                          ));
2322     g_signal_connect(G_OBJECT(lower), "value-changed", G_CALLBACK(lower_callback), dev);
2323     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(lower), TRUE, TRUE, 0);
2324 
2325     /* upper */
2326     GtkWidget *upper = dt_bauhaus_slider_new_with_range(NULL, 0.0, 100.0, 0.1, 99.99, 2);
2327     dt_bauhaus_slider_set(upper, dev->overexposed.upper);
2328     dt_bauhaus_slider_set_format(upper, "%.2f%%");
2329     dt_bauhaus_widget_set_label(upper, NULL, N_("upper threshold"));
2330     /* xgettext:no-c-format */
2331     gtk_widget_set_tooltip_text(upper, _("clipping threshold for the white point.\n"
2332                                          "100% is peak medium luminance."));
2333     g_signal_connect(G_OBJECT(upper), "value-changed", G_CALLBACK(upper_callback), dev);
2334     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(upper), TRUE, TRUE, 0);
2335   }
2336 
2337   /* create profile popup tool & buttons (softproof + gamut) */
2338   {
2339     // the softproof button
2340     dev->profile.softproof_button =
2341       dtgtk_togglebutton_new(dtgtk_cairo_paint_softproof, CPF_STYLE_FLAT, NULL);
2342     gtk_widget_set_tooltip_text(dev->profile.softproof_button,
2343                                 _("toggle softproofing\nright click for profile options"));
2344     g_signal_connect(G_OBJECT(dev->profile.softproof_button), "clicked",
2345                      G_CALLBACK(_softproof_quickbutton_clicked), dev);
2346     g_signal_connect(G_OBJECT(dev->profile.softproof_button), "button-press-event",
2347                      G_CALLBACK(_softproof_quickbutton_pressed), dev);
2348     g_signal_connect(G_OBJECT(dev->profile.softproof_button), "button-release-event",
2349                      G_CALLBACK(_profile_quickbutton_released), dev);
2350     dt_view_manager_module_toolbox_add(darktable.view_manager, dev->profile.softproof_button, DT_VIEW_DARKROOM);
2351     dt_gui_add_help_link(dev->profile.softproof_button, dt_get_help_url("softproof"));
2352 
2353     // the gamut check button
2354     dev->profile.gamut_button =
2355       dtgtk_togglebutton_new(dtgtk_cairo_paint_gamut_check, CPF_STYLE_FLAT, NULL);
2356     gtk_widget_set_tooltip_text(dev->profile.gamut_button,
2357                  _("toggle gamut checking\nright click for profile options"));
2358     g_signal_connect(G_OBJECT(dev->profile.gamut_button), "clicked",
2359                      G_CALLBACK(_gamut_quickbutton_clicked), dev);
2360     g_signal_connect(G_OBJECT(dev->profile.gamut_button), "button-press-event",
2361                      G_CALLBACK(_gamut_quickbutton_pressed), dev);
2362     g_signal_connect(G_OBJECT(dev->profile.gamut_button), "button-release-event",
2363                      G_CALLBACK(_profile_quickbutton_released), dev);
2364     dt_view_manager_module_toolbox_add(darktable.view_manager, dev->profile.gamut_button, DT_VIEW_DARKROOM);
2365     dt_gui_add_help_link(dev->profile.gamut_button, dt_get_help_url("gamut"));
2366 
2367     // and the popup window, which is shared between the two profile buttons
2368     dev->profile.floating_window = gtk_popover_new(NULL);
2369     gtk_widget_set_size_request(GTK_WIDGET(dev->profile.floating_window), large_dialog_width, -1);
2370 #if GTK_CHECK_VERSION(3, 16, 0)
2371     g_object_set(G_OBJECT(dev->profile.floating_window), "transitions-enabled", FALSE, NULL);
2372 #endif
2373 
2374     GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2375     gtk_container_add(GTK_CONTAINER(dev->profile.floating_window), vbox);
2376 
2377     /** let's fill the encapsulating widgets */
2378     char datadir[PATH_MAX] = { 0 };
2379     char confdir[PATH_MAX] = { 0 };
2380     dt_loc_get_user_config_dir(confdir, sizeof(confdir));
2381     dt_loc_get_datadir(datadir, sizeof(datadir));
2382     const int force_lcms2 = dt_conf_get_bool("plugins/lighttable/export/force_lcms2");
2383 
2384     GtkWidget *display_intent = dt_bauhaus_combobox_new(NULL);
2385     dt_bauhaus_widget_set_label(display_intent, NULL, N_("intent"));
2386     dt_bauhaus_combobox_add(display_intent, _("perceptual"));
2387     dt_bauhaus_combobox_add(display_intent, _("relative colorimetric"));
2388     dt_bauhaus_combobox_add(display_intent, C_("rendering intent", "saturation"));
2389     dt_bauhaus_combobox_add(display_intent, _("absolute colorimetric"));
2390 
2391     GtkWidget *display2_intent = dt_bauhaus_combobox_new(NULL);
2392     dt_bauhaus_widget_set_label(display2_intent, NULL, N_("intent"));
2393     dt_bauhaus_combobox_add(display2_intent, _("perceptual"));
2394     dt_bauhaus_combobox_add(display2_intent, _("relative colorimetric"));
2395     dt_bauhaus_combobox_add(display2_intent, C_("rendering intent", "saturation"));
2396     dt_bauhaus_combobox_add(display2_intent, _("absolute colorimetric"));
2397 
2398     if(!force_lcms2)
2399     {
2400       gtk_widget_set_no_show_all(display_intent, TRUE);
2401       gtk_widget_set_visible(display_intent, FALSE);
2402       gtk_widget_set_no_show_all(display2_intent, TRUE);
2403       gtk_widget_set_visible(display2_intent, FALSE);
2404     }
2405 
2406     GtkWidget *display_profile = dt_bauhaus_combobox_new(NULL);
2407     GtkWidget *display2_profile = dt_bauhaus_combobox_new(NULL);
2408     GtkWidget *softproof_profile = dt_bauhaus_combobox_new(NULL);
2409     GtkWidget *histogram_profile = dt_bauhaus_combobox_new(NULL);
2410 
2411     dt_bauhaus_widget_set_label(display_profile, NULL, N_("display profile"));
2412     dt_bauhaus_widget_set_label(display2_profile, NULL, N_("preview display profile"));
2413     dt_bauhaus_widget_set_label(softproof_profile, NULL, N_("softproof profile"));
2414     dt_bauhaus_widget_set_label(histogram_profile, NULL, N_("histogram profile"));
2415 
2416     dt_bauhaus_combobox_set_entries_ellipsis(display_profile, PANGO_ELLIPSIZE_MIDDLE);
2417     dt_bauhaus_combobox_set_entries_ellipsis(display2_profile, PANGO_ELLIPSIZE_MIDDLE);
2418     dt_bauhaus_combobox_set_entries_ellipsis(softproof_profile, PANGO_ELLIPSIZE_MIDDLE);
2419     dt_bauhaus_combobox_set_entries_ellipsis(histogram_profile, PANGO_ELLIPSIZE_MIDDLE);
2420 
2421     gtk_box_pack_start(GTK_BOX(vbox), display_profile, TRUE, TRUE, 0);
2422     gtk_box_pack_start(GTK_BOX(vbox), display_intent, TRUE, TRUE, 0);
2423     gtk_box_pack_start(GTK_BOX(vbox), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), TRUE, TRUE, 0);
2424 
2425     gtk_box_pack_start(GTK_BOX(vbox), display2_profile, TRUE, TRUE, 0);
2426     gtk_box_pack_start(GTK_BOX(vbox), display2_intent, TRUE, TRUE, 0);
2427     gtk_box_pack_start(GTK_BOX(vbox), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), TRUE, TRUE, 0);
2428 
2429     gtk_box_pack_start(GTK_BOX(vbox), softproof_profile, TRUE, TRUE, 0);
2430     gtk_box_pack_start(GTK_BOX(vbox), histogram_profile, TRUE, TRUE, 0);
2431 
2432     for(const GList *l = darktable.color_profiles->profiles; l; l = g_list_next(l))
2433     {
2434       dt_colorspaces_color_profile_t *prof = (dt_colorspaces_color_profile_t *)l->data;
2435       if(prof->display_pos > -1)
2436       {
2437         dt_bauhaus_combobox_add(display_profile, prof->name);
2438         if(prof->type == darktable.color_profiles->display_type
2439           && (prof->type != DT_COLORSPACE_FILE
2440               || !strcmp(prof->filename, darktable.color_profiles->display_filename)))
2441         {
2442           dt_bauhaus_combobox_set(display_profile, prof->display_pos);
2443         }
2444       }
2445       if(prof->display2_pos > -1)
2446       {
2447         dt_bauhaus_combobox_add(display2_profile, prof->name);
2448         if(prof->type == darktable.color_profiles->display2_type
2449            && (prof->type != DT_COLORSPACE_FILE
2450                || !strcmp(prof->filename, darktable.color_profiles->display2_filename)))
2451         {
2452           dt_bauhaus_combobox_set(display2_profile, prof->display2_pos);
2453         }
2454       }
2455       // the system display profile is only suitable for display purposes
2456       if(prof->out_pos > -1)
2457       {
2458         dt_bauhaus_combobox_add(softproof_profile, prof->name);
2459         if(prof->type == darktable.color_profiles->softproof_type
2460           && (prof->type != DT_COLORSPACE_FILE
2461               || !strcmp(prof->filename, darktable.color_profiles->softproof_filename)))
2462           dt_bauhaus_combobox_set(softproof_profile, prof->out_pos);
2463       }
2464       if(prof->category_pos > -1)
2465       {
2466         dt_bauhaus_combobox_add(histogram_profile, prof->name);
2467         if(prof->type == darktable.color_profiles->histogram_type
2468           && (prof->type != DT_COLORSPACE_FILE
2469               || !strcmp(prof->filename, darktable.color_profiles->histogram_filename)))
2470         {
2471           dt_bauhaus_combobox_set(histogram_profile, prof->category_pos);
2472         }
2473       }
2474     }
2475 
2476     char *system_profile_dir = g_build_filename(datadir, "color", "out", NULL);
2477     char *user_profile_dir = g_build_filename(confdir, "color", "out", NULL);
2478     char *tooltip = g_strdup_printf(_("display ICC profiles in %s or %s"), user_profile_dir, system_profile_dir);
2479     gtk_widget_set_tooltip_text(display_profile, tooltip);
2480     g_free(tooltip);
2481     tooltip = g_strdup_printf(_("preview display ICC profiles in %s or %s"), user_profile_dir, system_profile_dir);
2482     gtk_widget_set_tooltip_text(display2_profile, tooltip);
2483     g_free(tooltip);
2484     tooltip = g_strdup_printf(_("softproof ICC profiles in %s or %s"), user_profile_dir, system_profile_dir);
2485     gtk_widget_set_tooltip_text(softproof_profile, tooltip);
2486     g_free(tooltip);
2487     tooltip = g_strdup_printf(_("histogram and color picker ICC profiles in %s or %s"), user_profile_dir, system_profile_dir);
2488     gtk_widget_set_tooltip_text(histogram_profile, tooltip);
2489     g_free(tooltip);
2490     g_free(system_profile_dir);
2491     g_free(user_profile_dir);
2492 
2493     g_signal_connect(G_OBJECT(display_intent), "value-changed", G_CALLBACK(display_intent_callback), dev);
2494     g_signal_connect(G_OBJECT(display_profile), "value-changed", G_CALLBACK(display_profile_callback), dev);
2495     g_signal_connect(G_OBJECT(display2_intent), "value-changed", G_CALLBACK(display2_intent_callback), dev);
2496     g_signal_connect(G_OBJECT(display2_profile), "value-changed", G_CALLBACK(display2_profile_callback), dev);
2497     g_signal_connect(G_OBJECT(softproof_profile), "value-changed", G_CALLBACK(softproof_profile_callback), dev);
2498     g_signal_connect(G_OBJECT(histogram_profile), "value-changed", G_CALLBACK(histogram_profile_callback), dev);
2499 
2500     _update_softproof_gamut_checking(dev);
2501 
2502     DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_PREFERENCES_CHANGE,
2503                               G_CALLBACK(_preference_prev_downsample_change), &(dev->preview_downsampling));
2504     // update the gui when the preferences changed (i.e. show intent when using lcms2)
2505     DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_PREFERENCES_CHANGE,
2506                               G_CALLBACK(_preference_changed), (gpointer)display_intent);
2507     DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_PREFERENCES_CHANGE, G_CALLBACK(_preference_changed),
2508                               (gpointer)display2_intent);
2509     // and when profiles change
2510     DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_CONTROL_PROFILE_USER_CHANGED,
2511                               G_CALLBACK(_display_profile_changed), (gpointer)display_profile);
2512     DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_CONTROL_PROFILE_USER_CHANGED,
2513                               G_CALLBACK(_display2_profile_changed), (gpointer)display2_profile);
2514   }
2515 
2516   /* create overlay color changer popup tool */
2517   {
2518     // the button
2519     dev->overlay_color.button
2520         = dtgtk_togglebutton_new(dtgtk_cairo_paint_grid, CPF_STYLE_FLAT, NULL);
2521     gtk_widget_set_tooltip_text(dev->overlay_color.button,
2522                                 _("set the color of lines that overlay the image (drawn masks, crop and rotate guides etc.)"));
2523     g_signal_connect(G_OBJECT(dev->overlay_color.button), "clicked",
2524                      G_CALLBACK(_overlay_color_quickbutton_clicked), dev);
2525     g_signal_connect(G_OBJECT(dev->overlay_color.button), "button-press-event",
2526                      G_CALLBACK(_overlay_color_quickbutton_pressed), dev);
2527     g_signal_connect(G_OBJECT(dev->overlay_color.button), "button-release-event",
2528                      G_CALLBACK(_overlay_color_quickbutton_released), dev);
2529     dt_view_manager_module_toolbox_add(darktable.view_manager, dev->overlay_color.button, DT_VIEW_DARKROOM);
2530 
2531     // and the popup window
2532     dev->overlay_color.floating_window = gtk_popover_new(dev->overlay_color.button);
2533     gtk_widget_set_size_request(GTK_WIDGET(dev->overlay_color.floating_window), dialog_width, -1);
2534 #if GTK_CHECK_VERSION(3, 16, 0)
2535     g_object_set(G_OBJECT(dev->overlay_color.floating_window), "transitions-enabled", FALSE, NULL);
2536 #endif
2537 
2538     GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2539     gtk_container_add(GTK_CONTAINER(dev->overlay_color.floating_window), vbox);
2540 
2541     /** let's fill the encapsulating widget */
2542     GtkWidget *overlay_colors = dev->overlay_color.colors = dt_bauhaus_combobox_new(NULL);
2543     dt_bauhaus_widget_set_label(overlay_colors, NULL, N_("overlay color"));
2544     dt_bauhaus_combobox_add(overlay_colors, _("gray"));
2545     dt_bauhaus_combobox_add(overlay_colors, _("red"));
2546     dt_bauhaus_combobox_add(overlay_colors, _("green"));
2547     dt_bauhaus_combobox_add(overlay_colors, _("yellow"));
2548     dt_bauhaus_combobox_add(overlay_colors, _("cyan"));
2549     dt_bauhaus_combobox_add(overlay_colors, _("magenta"));
2550     dt_bauhaus_combobox_set(overlay_colors, dev->overlay_color.color);
2551     gtk_widget_set_tooltip_text(overlay_colors, _("set overlay color"));
2552     g_signal_connect(G_OBJECT(overlay_colors), "value-changed", G_CALLBACK(overlay_colors_callback), dev);
2553     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(overlay_colors), TRUE, TRUE, 0);
2554     gtk_widget_set_state_flags(overlay_colors, GTK_STATE_FLAG_SELECTED, TRUE);
2555   }
2556 
2557   darktable.view_manager->proxy.darkroom.view = self;
2558   darktable.view_manager->proxy.darkroom.get_layout = _lib_darkroom_get_layout;
2559   dev->border_size = DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
2560 }
2561 
2562 enum
2563 {
2564   DND_TARGET_IOP,
2565 };
2566 
2567 /** drag and drop module list */
2568 static const GtkTargetEntry _iop_target_list_internal[] = { { "iop", GTK_TARGET_SAME_WIDGET, DND_TARGET_IOP } };
2569 static const guint _iop_n_targets_internal = G_N_ELEMENTS(_iop_target_list_internal);
2570 
_get_dnd_dest_module(GtkBox * container,gint x,gint y,dt_iop_module_t * module_src)2571 static dt_iop_module_t *_get_dnd_dest_module(GtkBox *container, gint x, gint y, dt_iop_module_t *module_src)
2572 {
2573   dt_iop_module_t *module_dest = NULL;
2574 
2575   GtkAllocation allocation_w = {0};
2576   gtk_widget_get_allocation(module_src->header, &allocation_w);
2577   const int y_slop = allocation_w.height / 2;
2578   // after source in pixelpipe, which is before it in the widgets list
2579   gboolean after_src = TRUE;
2580 
2581   GtkWidget *widget_dest = NULL;
2582   GList *children = gtk_container_get_children(GTK_CONTAINER(container));
2583   for(GList *l = children; l != NULL; l = g_list_next(l))
2584   {
2585     GtkWidget *w = GTK_WIDGET(l->data);
2586     if(w)
2587     {
2588       if(w == module_src->expander) after_src = FALSE;
2589       if(gtk_widget_is_visible(w))
2590       {
2591         gtk_widget_get_allocation(w, &allocation_w);
2592         // If dragging to later in the pixelpipe, we will insert after
2593         // the destination module. If dragging to earlier in the
2594         // pixelpipe, will insert before the destination module. This
2595         // results in two code paths here and in our caller, but can
2596         // handle all cases from inserting at the very start to the
2597         // very end.
2598         if((after_src && y <= allocation_w.y + y_slop) ||
2599            (!after_src && y <= allocation_w.y + allocation_w.height + y_slop))
2600         {
2601           widget_dest = w;
2602           break;
2603         }
2604       }
2605     }
2606   }
2607   g_list_free(children);
2608 
2609   if(widget_dest)
2610   {
2611     for(const GList *modules = darktable.develop->iop; modules; modules = g_list_next(modules))
2612     {
2613       dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
2614       if(mod->expander == widget_dest)
2615       {
2616         module_dest = mod;
2617         break;
2618       }
2619     }
2620   }
2621 
2622   return module_dest;
2623 }
2624 
_get_dnd_source_module(GtkBox * container)2625 static dt_iop_module_t *_get_dnd_source_module(GtkBox *container)
2626 {
2627   dt_iop_module_t *module_source = NULL;
2628   gpointer *source_data = g_object_get_data(G_OBJECT(container), "source_data");
2629   if(source_data) module_source = (dt_iop_module_t *)source_data;
2630 
2631   return module_source;
2632 }
2633 
2634 // this will be used for a custom highlight, if ever implemented
_on_drag_end(GtkWidget * widget,GdkDragContext * context,gpointer user_data)2635 static void _on_drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
2636 {
2637 }
2638 
2639 // FIXME: default highlight for the dnd is barely visible
2640 // it should be possible to configure it
_on_drag_begin(GtkWidget * widget,GdkDragContext * context,gpointer user_data)2641 static void _on_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
2642 {
2643   GtkBox *container = dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER);
2644   dt_iop_module_t *module_src = _get_dnd_source_module(container);
2645   if(module_src && module_src->expander)
2646   {
2647     GdkWindow *window = gtk_widget_get_parent_window(module_src->header);
2648     if(window)
2649     {
2650       GtkAllocation allocation_w = {0};
2651       gtk_widget_get_allocation(module_src->header, &allocation_w);
2652       // method from https://blog.gtk.org/2017/04/23/drag-and-drop-in-lists/
2653       cairo_surface_t *surface = dt_cairo_image_surface_create(CAIRO_FORMAT_RGB24, allocation_w.width, allocation_w.height);
2654       cairo_t *cr = cairo_create(surface);
2655 
2656       // hack to render not transparent
2657       GtkStyleContext *style_context = gtk_widget_get_style_context(module_src->header);
2658       gtk_style_context_add_class(style_context, "iop_drag_icon");
2659       gtk_widget_draw(module_src->header, cr);
2660       gtk_style_context_remove_class(style_context, "iop_drag_icon");
2661 
2662       // FIXME: this centers the icon on the mouse -- instead translate such that the label doesn't jump when mouse down?
2663       cairo_surface_set_device_offset(surface, -allocation_w.width * darktable.gui->ppd / 2, -allocation_w.height * darktable.gui->ppd / 2);
2664       gtk_drag_set_icon_surface(context, surface);
2665 
2666       cairo_destroy(cr);
2667       cairo_surface_destroy(surface);
2668     }
2669   }
2670 }
2671 
_on_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time,gpointer user_data)2672 static void _on_drag_data_get(GtkWidget *widget, GdkDragContext *context,
2673                               GtkSelectionData *selection_data, guint info, guint time,
2674                               gpointer user_data)
2675 {
2676   gpointer *target_data = g_object_get_data(G_OBJECT(widget), "target_data");
2677   guint number_data = 0;
2678   if(target_data) number_data = GPOINTER_TO_UINT(target_data[DND_TARGET_IOP]);
2679   gtk_selection_data_set(selection_data, gdk_atom_intern("iop", TRUE), // type
2680                                         32,                            // format
2681                                         (guchar*)&number_data,         // data
2682                                         1);                            // length
2683 }
2684 
_on_drag_drop(GtkWidget * widget,GdkDragContext * dc,gint x,gint y,guint time,gpointer user_data)2685 static gboolean _on_drag_drop(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint time, gpointer user_data)
2686 {
2687   GdkAtom target_atom = GDK_NONE;
2688 
2689   target_atom = gdk_atom_intern("iop", TRUE);
2690 
2691   gtk_drag_get_data(widget, dc, target_atom, time);
2692 
2693   return TRUE;
2694 }
2695 
_on_drag_motion(GtkWidget * widget,GdkDragContext * dc,gint x,gint y,guint time,gpointer user_data)2696 static gboolean _on_drag_motion(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint time, gpointer user_data)
2697 {
2698   gboolean can_moved = FALSE;
2699   GtkBox *container = dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER);
2700   dt_iop_module_t *module_src = _get_dnd_source_module(container);
2701   if(!module_src) return FALSE;
2702 
2703   dt_iop_module_t *module_dest = _get_dnd_dest_module(container, x, y, module_src);
2704 
2705   if(module_src && module_dest && module_src != module_dest)
2706   {
2707     if(module_src->iop_order < module_dest->iop_order)
2708       can_moved = dt_ioppr_check_can_move_after_iop(darktable.develop->iop, module_src, module_dest);
2709     else
2710       can_moved = dt_ioppr_check_can_move_before_iop(darktable.develop->iop, module_src, module_dest);
2711   }
2712 
2713   for(const GList *modules = g_list_last(darktable.develop->iop); modules; modules = g_list_previous(modules))
2714   {
2715     dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
2716 
2717     if(module->expander)
2718     {
2719       GtkStyleContext *context = gtk_widget_get_style_context(module->expander);
2720       gtk_style_context_remove_class(context, "iop_drop_after");
2721       gtk_style_context_remove_class(context, "iop_drop_before");
2722     }
2723   }
2724 
2725   if(can_moved)
2726   {
2727     GtkStyleContext *context = gtk_widget_get_style_context(module_dest->expander);
2728     if(module_src->iop_order < module_dest->iop_order)
2729       gtk_style_context_add_class(context, "iop_drop_after");
2730     else
2731       gtk_style_context_add_class(context, "iop_drop_before");
2732 
2733     gdk_drag_status(dc, GDK_ACTION_COPY, time);
2734     GtkWidget *w = g_object_get_data(G_OBJECT(widget), "highlighted");
2735     if(w) gtk_drag_unhighlight(w);
2736     g_object_set_data(G_OBJECT(widget), "highlighted", (gpointer)module_dest->expander);
2737     gtk_drag_highlight(module_dest->expander);
2738   }
2739   else
2740   {
2741     gdk_drag_status(dc, 0, time);
2742     GtkWidget *w = g_object_get_data(G_OBJECT(widget), "highlighted");
2743     if(w)
2744     {
2745       gtk_drag_unhighlight(w);
2746       g_object_set_data(G_OBJECT(widget), "highlighted", (gpointer)FALSE);
2747     }
2748   }
2749 
2750   return can_moved;
2751 }
2752 
_on_drag_data_received(GtkWidget * widget,GdkDragContext * dc,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time,gpointer user_data)2753 static void _on_drag_data_received(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
2754                                    GtkSelectionData *selection_data,
2755                                    guint info, guint time, gpointer user_data)
2756 {
2757   int moved = 0;
2758   GtkBox *container = dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER);
2759   dt_iop_module_t *module_src = _get_dnd_source_module(container);
2760   dt_iop_module_t *module_dest = _get_dnd_dest_module(container, x, y, module_src);
2761 
2762   if(module_src && module_dest && module_src != module_dest)
2763   {
2764     if(module_src->iop_order < module_dest->iop_order)
2765     {
2766       /* printf("[_on_drag_data_received] moving %s %s(%f) after %s %s(%f)\n",
2767           module_src->op, module_src->multi_name, module_src->iop_order,
2768           module_dest->op, module_dest->multi_name, module_dest->iop_order); */
2769       moved = dt_ioppr_move_iop_after(darktable.develop, module_src, module_dest);
2770     }
2771     else
2772     {
2773       /* printf("[_on_drag_data_received] moving %s %s(%f) before %s %s(%f)\n",
2774           module_src->op, module_src->multi_name, module_src->iop_order,
2775           module_dest->op, module_dest->multi_name, module_dest->iop_order); */
2776       moved = dt_ioppr_move_iop_before(darktable.develop, module_src, module_dest);
2777     }
2778   }
2779   else
2780   {
2781     if(module_src == NULL)
2782       fprintf(stderr, "[_on_drag_data_received] can't find source module\n");
2783     if(module_dest == NULL)
2784       fprintf(stderr, "[_on_drag_data_received] can't find destination module\n");
2785   }
2786 
2787   for(const GList *modules = g_list_last(darktable.develop->iop); modules; modules = g_list_previous(modules))
2788   {
2789     dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
2790 
2791     if(module->expander)
2792     {
2793       GtkStyleContext *context = gtk_widget_get_style_context(module->expander);
2794       gtk_style_context_remove_class(context, "iop_drop_after");
2795       gtk_style_context_remove_class(context, "iop_drop_before");
2796     }
2797   }
2798 
2799   gtk_drag_finish(dc, TRUE, FALSE, time);
2800 
2801   if(moved)
2802   {
2803     // we move the headers
2804     GValue gv = { 0, { { 0 } } };
2805     g_value_init(&gv, G_TYPE_INT);
2806     gtk_container_child_get_property(
2807         GTK_CONTAINER(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER)), module_dest->expander,
2808         "position", &gv);
2809     gtk_box_reorder_child(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER),
2810         module_src->expander, g_value_get_int(&gv));
2811 
2812     // we update the headers
2813     dt_dev_modules_update_multishow(module_src->dev);
2814 
2815     dt_dev_add_history_item(module_src->dev, module_src, TRUE);
2816 
2817     dt_ioppr_check_iop_order(module_src->dev, 0, "_on_drag_data_received end");
2818 
2819     // we rebuild the pipe
2820     module_src->dev->pipe->changed |= DT_DEV_PIPE_REMOVE;
2821     module_src->dev->preview_pipe->changed |= DT_DEV_PIPE_REMOVE;
2822     module_src->dev->preview2_pipe->changed |= DT_DEV_PIPE_REMOVE;
2823     module_src->dev->pipe->cache_obsolete = 1;
2824     module_src->dev->preview_pipe->cache_obsolete = 1;
2825     module_src->dev->preview2_pipe->cache_obsolete = 1;
2826 
2827     // rebuild the accelerators
2828     dt_iop_connect_accels_multi(module_src->so);
2829 
2830     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_DEVELOP_MODULE_MOVED);
2831 
2832     // invalidate buffers and force redraw of darkroom
2833     dt_dev_invalidate_all(module_src->dev);
2834   }
2835 }
2836 
_on_drag_leave(GtkWidget * widget,GdkDragContext * dc,guint time,gpointer user_data)2837 static void _on_drag_leave(GtkWidget *widget, GdkDragContext *dc, guint time, gpointer user_data)
2838 {
2839   for(const GList *modules = g_list_last(darktable.develop->iop); modules; modules = g_list_previous(modules))
2840   {
2841     dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
2842 
2843     if(module->expander)
2844     {
2845       GtkStyleContext *context = gtk_widget_get_style_context(module->expander);
2846       gtk_style_context_remove_class(context, "iop_drop_after");
2847       gtk_style_context_remove_class(context, "iop_drop_before");
2848     }
2849   }
2850 
2851   GtkWidget *w = g_object_get_data(G_OBJECT(widget), "highlighted");
2852   if(w)
2853   {
2854     gtk_drag_unhighlight(w);
2855     g_object_set_data(G_OBJECT(widget), "highlighted", (gpointer)FALSE);
2856   }
2857 }
2858 
_register_modules_drag_n_drop(dt_view_t * self)2859 static void _register_modules_drag_n_drop(dt_view_t *self)
2860 {
2861   if(darktable.gui)
2862   {
2863     GtkWidget *container = GTK_WIDGET(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER));
2864 
2865     gtk_drag_source_set(container, GDK_BUTTON1_MASK | GDK_SHIFT_MASK, _iop_target_list_internal, _iop_n_targets_internal, GDK_ACTION_COPY);
2866 
2867     g_object_set_data(G_OBJECT(container), "targetlist", (gpointer)_iop_target_list_internal);
2868     g_object_set_data(G_OBJECT(container), "ntarget", GUINT_TO_POINTER(_iop_n_targets_internal));
2869 
2870     g_signal_connect(container, "drag-begin", G_CALLBACK(_on_drag_begin), NULL);
2871     g_signal_connect(container, "drag-data-get", G_CALLBACK(_on_drag_data_get), NULL);
2872     g_signal_connect(container, "drag-end", G_CALLBACK(_on_drag_end), NULL);
2873 
2874     gtk_drag_dest_set(container, 0, _iop_target_list_internal, _iop_n_targets_internal, GDK_ACTION_COPY);
2875 
2876     g_signal_connect(container, "drag-data-received", G_CALLBACK(_on_drag_data_received), NULL);
2877     g_signal_connect(container, "drag-drop", G_CALLBACK(_on_drag_drop), NULL);
2878     g_signal_connect(container, "drag-motion", G_CALLBACK(_on_drag_motion), NULL);
2879     g_signal_connect(container, "drag-leave", G_CALLBACK(_on_drag_leave), NULL);
2880   }
2881 }
2882 
_unregister_modules_drag_n_drop(dt_view_t * self)2883 static void _unregister_modules_drag_n_drop(dt_view_t *self)
2884 {
2885   if(darktable.gui)
2886   {
2887     gtk_drag_source_unset(dt_ui_center(darktable.gui->ui));
2888 
2889     GtkBox *container = dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER);
2890 
2891     g_signal_handlers_disconnect_matched(container, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK(_on_drag_begin), NULL);
2892     g_signal_handlers_disconnect_matched(container, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK(_on_drag_data_get), NULL);
2893     g_signal_handlers_disconnect_matched(container, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK(_on_drag_end), NULL);
2894     g_signal_handlers_disconnect_matched(container, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK(_on_drag_data_received), NULL);
2895     g_signal_handlers_disconnect_matched(container, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK(_on_drag_drop), NULL);
2896     g_signal_handlers_disconnect_matched(container, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK(_on_drag_motion), NULL);
2897     g_signal_handlers_disconnect_matched(container, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK(_on_drag_leave), NULL);
2898   }
2899 }
2900 
enter(dt_view_t * self)2901 void enter(dt_view_t *self)
2902 {
2903   // prevent accels_window to refresh
2904   darktable.view_manager->accels_window.prevent_refresh = TRUE;
2905 
2906   // clean the undo list
2907   dt_undo_clear(darktable.undo, DT_UNDO_DEVELOP);
2908 
2909   /* connect to ui pipe finished signal for redraw */
2910   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED,
2911                             G_CALLBACK(_darkroom_ui_pipe_finish_signal_callback), (gpointer)self);
2912 
2913   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_PREVIEW2_PIPE_FINISHED,
2914                             G_CALLBACK(_darkroom_ui_preview2_pipe_finish_signal_callback), (gpointer)self);
2915 
2916   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_TROUBLE_MESSAGE,
2917                                   G_CALLBACK(_display_module_trouble_message_callback),
2918                                   (gpointer)self);
2919 
2920   dt_print(DT_DEBUG_CONTROL, "[run_job+] 11 %f in darkroom mode\n", dt_get_wtime());
2921   dt_develop_t *dev = (dt_develop_t *)self->data;
2922   if(!dev->form_gui)
2923   {
2924     dev->form_gui = (dt_masks_form_gui_t *)calloc(1, sizeof(dt_masks_form_gui_t));
2925     dt_masks_init_form_gui(dev->form_gui);
2926   }
2927   dt_masks_change_form_gui(NULL);
2928   dev->form_gui->pipe_hash = 0;
2929   dev->form_gui->formid = 0;
2930   dev->gui_leaving = 0;
2931   dev->gui_module = NULL;
2932 
2933   // change active image
2934   dt_view_active_images_reset(FALSE);
2935   dt_view_active_images_add(dev->image_storage.id, TRUE);
2936   dt_ui_thumbtable(darktable.gui->ui)->mouse_inside = FALSE; // consider mouse outside filmstrip by default
2937 
2938   dt_control_set_dev_zoom(DT_ZOOM_FIT);
2939   dt_control_set_dev_zoom_x(0);
2940   dt_control_set_dev_zoom_y(0);
2941   dt_control_set_dev_closeup(0);
2942 
2943   // take a copy of the image struct for convenience.
2944 
2945   dt_dev_load_image(darktable.develop, dev->image_storage.id);
2946 
2947 
2948   /*
2949    * add IOP modules to plugin list
2950    */
2951   char option[1024];
2952   for(const GList *modules = g_list_last(dev->iop); modules; modules = g_list_previous(modules))
2953   {
2954     dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
2955 
2956     /* initialize gui if iop have one defined */
2957     if(!dt_iop_is_hidden(module))
2958     {
2959       dt_iop_gui_init(module);
2960 
2961       /* add module to right panel */
2962       dt_iop_gui_set_expander(module);
2963 
2964       if(module->multi_priority == 0)
2965       {
2966         snprintf(option, sizeof(option), "plugins/darkroom/%s/expanded", module->op);
2967         module->expanded = dt_conf_get_bool(option);
2968         dt_iop_gui_update_expanded(module);
2969       }
2970 
2971       dt_iop_reload_defaults(module);
2972     }
2973   }
2974 
2975   /* signal that darktable.develop is initialized and ready to be used */
2976   DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_DEVELOP_INITIALIZE);
2977 
2978   // synch gui and flag pipe as dirty
2979   // this is done here and not in dt_read_history, as it would else be triggered before module->gui_init.
2980   dt_dev_pop_history_items(dev, dev->history_end);
2981 
2982   /* ensure that filmstrip shows current image */
2983   dt_thumbtable_set_offset_image(dt_ui_thumbtable(darktable.gui->ui), dev->image_storage.id, TRUE);
2984 
2985   // get last active plugin:
2986   gchar *active_plugin = dt_conf_get_string("plugins/darkroom/active");
2987   if(active_plugin)
2988   {
2989     for(const GList *modules = dev->iop; modules; modules = g_list_next(modules))
2990     {
2991       dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
2992       if(!strcmp(module->op, active_plugin)) dt_iop_request_focus(module);
2993     }
2994     g_free(active_plugin);
2995   }
2996 
2997   // update module multishow state now modules are loaded
2998   dt_dev_modules_update_multishow(dev);
2999 
3000   // image should be there now.
3001   float zoom_x, zoom_y;
3002   dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, DT_ZOOM_FIT, 0, NULL, NULL);
3003   dt_control_set_dev_zoom_x(zoom_x);
3004   dt_control_set_dev_zoom_y(zoom_y);
3005 
3006   /* connect signal for filmstrip image activate */
3007   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE,
3008                             G_CALLBACK(_view_darkroom_filmstrip_activate_callback), self);
3009 
3010   dt_collection_hint_message(darktable.collection);
3011 
3012   dt_ui_scrollbars_show(darktable.gui->ui, dt_conf_get_bool("darkroom/ui/scrollbars"));
3013 
3014   _register_modules_drag_n_drop(self);
3015 
3016   if(dt_conf_get_bool("second_window/last_visible"))
3017   {
3018     _darkroom_display_second_window(dev);
3019     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dev->second_window.button), TRUE);
3020   }
3021 
3022   // just make sure at this stage we have only history info into the undo, all automatic
3023   // tagging should be ignored.
3024   dt_undo_clear(darktable.undo, DT_UNDO_TAGS);
3025 
3026   // update accels_window
3027   darktable.view_manager->accels_window.prevent_refresh = FALSE;
3028 
3029   //connect iop accelerators
3030   dt_iop_connect_accels_all();
3031 
3032   // switch on groups as they were last time:
3033   dt_dev_modulegroups_set(dev, dt_conf_get_int("plugins/darkroom/groups"));
3034 
3035   // connect to preference change for module header button hiding
3036   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_PREFERENCES_CHANGE,
3037                                   G_CALLBACK(_preference_changed_button_hide), dev);
3038 
3039   dt_iop_color_picker_init();
3040 }
3041 
leave(dt_view_t * self)3042 void leave(dt_view_t *self)
3043 {
3044   dt_iop_color_picker_cleanup();
3045 
3046   _unregister_modules_drag_n_drop(self);
3047 
3048   /* disconnect from filmstrip image activate */
3049   DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_view_darkroom_filmstrip_activate_callback),
3050                                (gpointer)self);
3051 
3052   /* disconnect from pipe finish signal */
3053   DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_darkroom_ui_pipe_finish_signal_callback),
3054                                (gpointer)self);
3055 
3056   DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_darkroom_ui_preview2_pipe_finish_signal_callback),
3057                                (gpointer)self);
3058 
3059   DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals,
3060                                      G_CALLBACK(_display_module_trouble_message_callback),
3061                                      (gpointer)self);
3062 
3063   // store groups for next time:
3064   dt_conf_set_int("plugins/darkroom/groups", dt_dev_modulegroups_get(darktable.develop));
3065 
3066   // store last active plugin:
3067   if(darktable.develop->gui_module)
3068     dt_conf_set_string("plugins/darkroom/active", darktable.develop->gui_module->op);
3069   else
3070     dt_conf_set_string("plugins/darkroom/active", "");
3071 
3072   dt_develop_t *dev = (dt_develop_t *)self->data;
3073 
3074   DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals,
3075                                      G_CALLBACK(_preference_changed_button_hide), dev);
3076 
3077   // reset color assessment mode
3078   if(dev->iso_12646.enabled)
3079   {
3080     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dev->iso_12646.button), FALSE);
3081     dev->iso_12646.enabled = FALSE;
3082     dev->width = dev->orig_width;
3083     dev->height = dev->orig_height;
3084     dev->border_size = DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
3085   }
3086 
3087   // commit image ops to db
3088   dt_dev_write_history(dev);
3089 
3090   // update aspect ratio
3091   if(dev->preview_pipe->backbuf && dev->preview_status == DT_DEV_PIXELPIPE_VALID)
3092   {
3093     double aspect_ratio = (double)dev->preview_pipe->backbuf_width / (double)dev->preview_pipe->backbuf_height;
3094     dt_image_set_aspect_ratio_to(dev->preview_pipe->image.id, aspect_ratio, FALSE);
3095   }
3096   else
3097   {
3098     dt_image_set_aspect_ratio(dev->image_storage.id, FALSE);
3099   }
3100 
3101   // be sure light table will regenerate the thumbnail:
3102   if (!dt_history_hash_is_mipmap_synced(dev->image_storage.id))
3103   {
3104     dt_mipmap_cache_remove(darktable.mipmap_cache, dev->image_storage.id);
3105     dt_image_update_final_size(dev->image_storage.id);
3106     // dump new xmp data
3107     dt_image_synch_xmp(dev->image_storage.id);
3108     dt_history_hash_set_mipmap(dev->image_storage.id);
3109   }
3110 
3111   // clear gui.
3112 
3113   dt_pthread_mutex_lock(&dev->preview_pipe_mutex);
3114   dt_pthread_mutex_lock(&dev->preview2_pipe_mutex);
3115   dt_pthread_mutex_lock(&dev->pipe_mutex);
3116 
3117   dev->gui_leaving = 1;
3118 
3119   dt_dev_pixelpipe_cleanup_nodes(dev->pipe);
3120   dt_dev_pixelpipe_cleanup_nodes(dev->preview2_pipe);
3121   dt_dev_pixelpipe_cleanup_nodes(dev->preview_pipe);
3122 
3123   dt_pthread_mutex_lock(&dev->history_mutex);
3124   while(dev->history)
3125   {
3126     dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(dev->history->data);
3127     // printf("removing history item %d - %s, data %f %f\n", hist->module->instance, hist->module->op, *(float
3128     // *)hist->params, *((float *)hist->params+1));
3129     dt_dev_free_history_item(hist);
3130     dev->history = g_list_delete_link(dev->history, dev->history);
3131   }
3132 
3133   while(dev->iop)
3134   {
3135     dt_iop_module_t *module = (dt_iop_module_t *)(dev->iop->data);
3136     if(!dt_iop_is_hidden(module)) dt_iop_gui_cleanup_module(module);
3137 
3138     // force refresh if module has mask visualized
3139     if (module->request_mask_display || module->suppress_mask) dt_iop_refresh_center(module);
3140 
3141     dt_accel_cleanup_closures_iop(module);
3142     module->accel_closures = NULL;
3143     dt_iop_cleanup_module(module);
3144     free(module);
3145     dev->iop = g_list_delete_link(dev->iop, dev->iop);
3146   }
3147   while(dev->alliop)
3148   {
3149     dt_iop_cleanup_module((dt_iop_module_t *)dev->alliop->data);
3150     free(dev->alliop->data);
3151     dev->alliop = g_list_delete_link(dev->alliop, dev->alliop);
3152   }
3153 
3154   dt_pthread_mutex_unlock(&dev->history_mutex);
3155 
3156   dt_pthread_mutex_unlock(&dev->pipe_mutex);
3157   dt_pthread_mutex_unlock(&dev->preview2_pipe_mutex);
3158   dt_pthread_mutex_unlock(&dev->preview_pipe_mutex);
3159 
3160   // cleanup visible masks
3161   if(dev->form_gui)
3162   {
3163     dev->gui_module = NULL; // modules have already been free()
3164     dt_masks_clear_form_gui(dev);
3165     free(dev->form_gui);
3166     dev->form_gui = NULL;
3167     dt_masks_change_form_gui(NULL);
3168   }
3169   // clear masks
3170   g_list_free_full(dev->forms, (void (*)(void *))dt_masks_free_form);
3171   dev->forms = NULL;
3172   g_list_free_full(dev->allforms, (void (*)(void *))dt_masks_free_form);
3173   dev->allforms = NULL;
3174 
3175   // take care of the overexposed window
3176   if(dev->overexposed.timeout > 0) g_source_remove(dev->overexposed.timeout);
3177   gtk_widget_hide(dev->overexposed.floating_window);
3178   gtk_widget_hide(dev->profile.floating_window);
3179 
3180   dt_ui_scrollbars_show(darktable.gui->ui, FALSE);
3181 
3182   // darkroom development could have changed a collection, so update that before being back in lighttable
3183   dt_collection_update_query(darktable.collection, DT_COLLECTION_CHANGE_RELOAD, DT_COLLECTION_PROP_UNDEF,
3184                              g_list_prepend(NULL, GINT_TO_POINTER(darktable.develop->image_storage.id)));
3185 
3186   darktable.develop->image_storage.id = -1;
3187 
3188   dt_print(DT_DEBUG_CONTROL, "[run_job-] 11 %f in darkroom mode\n", dt_get_wtime());
3189 }
3190 
mouse_leave(dt_view_t * self)3191 void mouse_leave(dt_view_t *self)
3192 {
3193   // if we are not hovering over a thumbnail in the filmstrip -> show metadata of opened image.
3194   dt_develop_t *dev = (dt_develop_t *)self->data;
3195   dt_control_set_mouse_over_id(dev->image_storage.id);
3196 
3197   // masks
3198   int handled = dt_masks_events_mouse_leave(dev->gui_module);
3199   if(handled) return;
3200   // module
3201   if(dev->gui_module && dev->gui_module->mouse_leave)
3202     handled = dev->gui_module->mouse_leave(dev->gui_module);
3203 
3204   // reset any changes the selected plugin might have made.
3205   dt_control_change_cursor(GDK_LEFT_PTR);
3206 }
3207 
3208 /* This helper function tests for a position to be within the displayed area
3209    of an image. To avoid "border cases" we accept values to be slightly out of area too.
3210 */
mouse_in_imagearea(dt_view_t * self,double x,double y)3211 static int mouse_in_imagearea(dt_view_t *self, double x, double y)
3212 {
3213   dt_develop_t *dev = (dt_develop_t *)self->data;
3214 
3215   const int closeup = dt_control_get_dev_closeup();
3216   const int pwidth = (dev->pipe->output_backbuf_width<<closeup) / darktable.gui->ppd;
3217   const int pheight = (dev->pipe->output_backbuf_height<<closeup) / darktable.gui->ppd;
3218 
3219   x -= (self->width - pwidth) / 2;
3220   y -= (self->height - pheight) / 2;
3221 
3222   if((x < -3) || (x > (pwidth + 6)) || (y < -3) || (y > (pheight + 6))) return FALSE;
3223   return TRUE;
3224 }
3225 
mouse_enter(dt_view_t * self)3226 void mouse_enter(dt_view_t *self)
3227 {
3228   dt_develop_t *dev = (dt_develop_t *)self->data;
3229   // masks
3230   dt_masks_events_mouse_enter(dev->gui_module);
3231 }
3232 
mouse_moved(dt_view_t * self,double x,double y,double pressure,int which)3233 void mouse_moved(dt_view_t *self, double x, double y, double pressure, int which)
3234 {
3235   dt_develop_t *dev = (dt_develop_t *)self->data;
3236   const int32_t tb = dev->border_size;
3237   const int32_t capwd = self->width  - 2*tb;
3238   const int32_t capht = self->height - 2*tb;
3239 
3240   // if we are not hovering over a thumbnail in the filmstrip -> show metadata of opened image.
3241   int32_t mouse_over_id = dt_control_get_mouse_over_id();
3242   if(mouse_over_id == -1)
3243   {
3244     mouse_over_id = dev->image_storage.id;
3245     dt_control_set_mouse_over_id(mouse_over_id);
3246   }
3247 
3248   dt_control_t *ctl = darktable.control;
3249   const int32_t width_i = self->width;
3250   const int32_t height_i = self->height;
3251   float offx = 0.0f, offy = 0.0f;
3252   if(width_i > capwd) offx = (capwd - width_i) * .5f;
3253   if(height_i > capht) offy = (capht - height_i) * .5f;
3254   int handled = 0;
3255 
3256   if(dev->gui_module && dev->gui_module->request_color_pick != DT_REQUEST_COLORPICK_OFF && ctl->button_down
3257      && ctl->button_down_which == 1)
3258   {
3259     // module requested a color box
3260     if(mouse_in_imagearea(self, x, y))
3261     {
3262       // Make sure a minimal width/height
3263       float delta_x = 1 / (float) dev->pipe->processed_width;
3264       float delta_y = 1 / (float) dev->pipe->processed_height;
3265 
3266       float zoom_x, zoom_y;
3267       dt_dev_get_pointer_zoom_pos(dev, x + offx, y + offy, &zoom_x, &zoom_y);
3268 
3269       if(darktable.lib->proxy.colorpicker.size)
3270       {
3271         dev->gui_module->color_picker_box[0] = fmaxf(0.0, fminf(dev->gui_module->color_picker_point[0], .5f + zoom_x) - delta_x);
3272         dev->gui_module->color_picker_box[1] = fmaxf(0.0, fminf(dev->gui_module->color_picker_point[1], .5f + zoom_y) - delta_y);
3273         dev->gui_module->color_picker_box[2] = fminf(1.0, fmaxf(dev->gui_module->color_picker_point[0], .5f + zoom_x) + delta_x);
3274         dev->gui_module->color_picker_box[3] = fminf(1.0, fmaxf(dev->gui_module->color_picker_point[1], .5f + zoom_y) + delta_y);
3275       }
3276       else
3277       {
3278         dev->gui_module->color_picker_point[0] = .5f + zoom_x;
3279         dev->gui_module->color_picker_point[1] = .5f + zoom_y;
3280 
3281         dev->preview_status = DT_DEV_PIXELPIPE_DIRTY;
3282       }
3283     }
3284     dt_control_queue_redraw();
3285     return;
3286   }
3287   x += offx;
3288   y += offy;
3289   // masks
3290   handled = dt_masks_events_mouse_moved(dev->gui_module, x, y, pressure, which);
3291   if(handled) return;
3292   // module
3293   if(dev->gui_module && dev->gui_module->mouse_moved
3294      && dt_dev_modulegroups_get_activated(darktable.develop) != DT_MODULEGROUP_BASICS)
3295     handled = dev->gui_module->mouse_moved(dev->gui_module, x, y, pressure, which);
3296   if(handled) return;
3297 
3298   if(darktable.control->button_down && darktable.control->button_down_which == 1)
3299   {
3300     // depending on dev_zoom, adjust dev_zoom_x/y.
3301     const dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
3302     const int closeup = dt_control_get_dev_closeup();
3303     int procw, proch;
3304     dt_dev_get_processed_size(dev, &procw, &proch);
3305     const float scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 0);
3306     float old_zoom_x, old_zoom_y;
3307     old_zoom_x = dt_control_get_dev_zoom_x();
3308     old_zoom_y = dt_control_get_dev_zoom_y();
3309     float zx = old_zoom_x - (1.0 / scale) * (x - ctl->button_x - offx) / procw;
3310     float zy = old_zoom_y - (1.0 / scale) * (y - ctl->button_y - offy) / proch;
3311     dt_dev_check_zoom_bounds(dev, &zx, &zy, zoom, closeup, NULL, NULL);
3312     dt_control_set_dev_zoom_x(zx);
3313     dt_control_set_dev_zoom_y(zy);
3314     ctl->button_x = x - offx;
3315     ctl->button_y = y - offy;
3316     dt_dev_invalidate(dev);
3317     dt_control_queue_redraw_center();
3318     dt_control_navigation_redraw();
3319   }
3320 }
3321 
3322 
button_released(dt_view_t * self,double x,double y,int which,uint32_t state)3323 int button_released(dt_view_t *self, double x, double y, int which, uint32_t state)
3324 {
3325   dt_develop_t *dev = darktable.develop;
3326   const int32_t tb = dev->border_size;
3327   const int32_t capwd = self->width  - 2*tb;
3328   const int32_t capht = self->height - 2*tb;
3329   const int32_t width_i = self->width;
3330   const int32_t height_i = self->height;
3331   if(width_i > capwd) x += (capwd - width_i) * .5f;
3332   if(height_i > capht) y += (capht - height_i) * .5f;
3333 
3334   int handled = 0;
3335   if(dev->gui_module && dev->gui_module->request_color_pick != DT_REQUEST_COLORPICK_OFF && which == 1)
3336   {
3337     dev->preview_status = DT_DEV_PIXELPIPE_DIRTY;
3338     dt_control_queue_redraw();
3339     return 1;
3340   }
3341   // masks
3342   if(dev->form_visible) handled = dt_masks_events_button_released(dev->gui_module, x, y, which, state);
3343   if(handled) return handled;
3344   // module
3345   if(dev->gui_module && dev->gui_module->button_released
3346      && dt_dev_modulegroups_get_activated(darktable.develop) != DT_MODULEGROUP_BASICS)
3347     handled = dev->gui_module->button_released(dev->gui_module, x, y, which, state);
3348   if(handled) return handled;
3349   if(which == 1) dt_control_change_cursor(GDK_LEFT_PTR);
3350   return 1;
3351 }
3352 
3353 
button_pressed(dt_view_t * self,double x,double y,double pressure,int which,int type,uint32_t state)3354 int button_pressed(dt_view_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
3355 {
3356   dt_develop_t *dev = (dt_develop_t *)self->data;
3357   const int32_t tb = dev->border_size;
3358   const int32_t capwd = self->width  - 2*tb;
3359   const int32_t capht = self->height - 2*tb;
3360   const int32_t width_i = self->width;
3361   const int32_t height_i = self->height;
3362   float offx = 0.0f, offy = 0.0f;
3363   if(width_i > capwd) offx = (capwd - width_i) * .5f;
3364   if(height_i > capht) offy = (capht - height_i) * .5f;
3365 
3366   int handled = 0;
3367   if(dev->gui_module && dev->gui_module->request_color_pick != DT_REQUEST_COLORPICK_OFF && which == 1)
3368   {
3369     float zoom_x, zoom_y;
3370     dt_dev_get_pointer_zoom_pos(dev, x + offx, y + offy, &zoom_x, &zoom_y);
3371     if(mouse_in_imagearea(self, x, y))
3372     {
3373       // The default box will be a square with 1% of the image width
3374       const float delta_x = 0.01f;
3375       const float delta_y = delta_x * (float)dev->pipe->processed_width / (float)dev->pipe->processed_height;
3376 
3377       zoom_x += 0.5f;
3378       zoom_y += 0.5f;
3379 
3380       dev->gui_module->color_picker_point[0] = zoom_x;
3381       dev->gui_module->color_picker_point[1] = zoom_y;
3382 
3383       if(darktable.lib->proxy.colorpicker.size)
3384       {
3385         gboolean on_corner_prev_box = TRUE;
3386         float opposite_x, opposite_y;
3387 
3388         if(fabsf(zoom_x - dev->gui_module->color_picker_box[0]) < .005f)
3389           opposite_x = dev->gui_module->color_picker_box[2];
3390         else if(fabsf(zoom_x - dev->gui_module->color_picker_box[2]) < .005f)
3391           opposite_x = dev->gui_module->color_picker_box[0];
3392         else
3393           on_corner_prev_box = FALSE;
3394 
3395         if(fabsf(zoom_y - dev->gui_module->color_picker_box[1]) < .005f)
3396           opposite_y = dev->gui_module->color_picker_box[3];
3397         else if(fabsf(zoom_y - dev->gui_module->color_picker_box[3]) < .005f)
3398           opposite_y = dev->gui_module->color_picker_box[1];
3399         else
3400           on_corner_prev_box = FALSE;
3401 
3402         if(on_corner_prev_box)
3403         {
3404           dev->gui_module->color_picker_point[0] = opposite_x;
3405           dev->gui_module->color_picker_point[1] = opposite_y;
3406         }
3407         else
3408         {
3409           dev->gui_module->color_picker_box[0] = fmaxf(0.0, zoom_x - delta_x);
3410           dev->gui_module->color_picker_box[1] = fmaxf(0.0, zoom_y - delta_y);
3411           dev->gui_module->color_picker_box[2] = fminf(1.0, zoom_x + delta_x);
3412           dev->gui_module->color_picker_box[3] = fminf(1.0, zoom_y + delta_y);
3413         }
3414       }
3415       else
3416       {
3417         dev->preview_status = DT_DEV_PIXELPIPE_DIRTY;
3418       }
3419     }
3420     dt_control_queue_redraw();
3421     return 1;
3422   }
3423 
3424   if(dev->gui_module && dev->gui_module->request_color_pick != DT_REQUEST_COLORPICK_OFF && which == 3)
3425   {
3426     // default is hardcoded this way
3427     dev->gui_module->color_picker_box[0] = dev->gui_module->color_picker_box[1] = .01f;
3428     dev->gui_module->color_picker_box[2] = dev->gui_module->color_picker_box[3] = .99f;
3429 
3430     dev->preview_status = DT_DEV_PIXELPIPE_DIRTY;
3431     dt_control_queue_redraw();
3432     return 1;
3433   }
3434 
3435   x += offx;
3436   y += offy;
3437   // masks
3438   if(dev->form_visible)
3439     handled = dt_masks_events_button_pressed(dev->gui_module, x, y, pressure, which, type, state);
3440   if(handled) return handled;
3441   // module
3442   if(dev->gui_module && dev->gui_module->button_pressed
3443      && dt_dev_modulegroups_get_activated(darktable.develop) != DT_MODULEGROUP_BASICS)
3444     handled = dev->gui_module->button_pressed(dev->gui_module, x, y, pressure, which, type, state);
3445   if(handled) return handled;
3446 
3447   if(which == 1 && type == GDK_2BUTTON_PRESS) return 0;
3448   if(which == 1)
3449   {
3450     dt_control_change_cursor(GDK_HAND1);
3451     return 1;
3452   }
3453   if(which == 2)
3454   {
3455     // zoom to 1:1 2:1 and back
3456     int procw, proch;
3457     dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
3458     int closeup = dt_control_get_dev_closeup();
3459     float zoom_x = dt_control_get_dev_zoom_x();
3460     float zoom_y = dt_control_get_dev_zoom_y();
3461     dt_dev_get_processed_size(dev, &procw, &proch);
3462     float scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 0);
3463     const float ppd = darktable.gui->ppd;
3464     const gboolean low_ppd = (darktable.gui->ppd == 1);
3465     const float mouse_off_x = x - 0.5f * dev->width;
3466     const float mouse_off_y = y - 0.5f * dev->height;
3467     zoom_x += mouse_off_x / (procw * scale);
3468     zoom_y += mouse_off_y / (proch * scale);
3469     const float tscale = scale * ppd;
3470     closeup = 0;
3471     if((tscale > 0.95f) && (tscale < 1.05f)) // we are at 100% and switch to 200%
3472     {
3473       zoom = DT_ZOOM_1;
3474       scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
3475       if(low_ppd) closeup = 1;
3476     }
3477     else if((tscale > 1.95f) && (tscale < 2.05f)) // at 200% so switch to zoomfit
3478     {
3479       zoom = DT_ZOOM_FIT;
3480       scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_FIT, 1.0, 0);
3481     }
3482     else // other than 100 or 200% so zoom to 100 %
3483     {
3484       if(low_ppd)
3485       {
3486         zoom = DT_ZOOM_1;
3487         scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
3488       }
3489       else
3490       {
3491         zoom = DT_ZOOM_FREE;
3492         scale = 1.0f / ppd;
3493       }
3494     }
3495     dt_control_set_dev_zoom_scale(scale);
3496     dt_control_set_dev_closeup(closeup);
3497     scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 0);
3498     zoom_x -= mouse_off_x / (procw * scale);
3499     zoom_y -= mouse_off_y / (proch * scale);
3500     dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, zoom, closeup, NULL, NULL);
3501     dt_control_set_dev_zoom(zoom);
3502     dt_control_set_dev_zoom_x(zoom_x);
3503     dt_control_set_dev_zoom_y(zoom_y);
3504     dt_dev_invalidate(dev);
3505     dt_control_queue_redraw_center();
3506     dt_control_navigation_redraw();
3507     return 1;
3508   }
3509   return 0;
3510 }
3511 
scrollbar_changed(dt_view_t * self,double x,double y)3512 void scrollbar_changed(dt_view_t *self, double x, double y)
3513 {
3514   dt_control_set_dev_zoom_x(x);
3515   dt_control_set_dev_zoom_y(y);
3516 
3517   /* redraw pipe */
3518   dt_dev_invalidate(darktable.develop);
3519   dt_control_queue_redraw_center();
3520   dt_control_navigation_redraw();
3521 }
3522 
scrolled(dt_view_t * self,double x,double y,int up,int state)3523 void scrolled(dt_view_t *self, double x, double y, int up, int state)
3524 {
3525   dt_develop_t *dev = (dt_develop_t *)self->data;
3526   const int32_t tb = dev->border_size;
3527   const int32_t capwd = self->width  - 2*tb;
3528   const int32_t capht = self->height - 2*tb;
3529   const int32_t width_i = self->width;
3530   const int32_t height_i = self->height;
3531   if(width_i > capwd) x += (capwd - width_i) * .5f;
3532   if(height_i > capht) y += (capht - height_i) * .5f;
3533 
3534   int handled = 0;
3535   // dynamic accels
3536   if(self->dynamic_accel_current)
3537   {
3538     GtkWidget *widget = self->dynamic_accel_current;
3539     dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
3540 
3541     if(w->type == DT_BAUHAUS_SLIDER)
3542     {
3543       float value = dt_bauhaus_slider_get(widget);
3544       float step = dt_bauhaus_slider_get_step(widget);
3545       float multiplier = dt_accel_get_slider_scale_multiplier();
3546 
3547       const float min_visible = powf(10.0f, -dt_bauhaus_slider_get_digits(widget));
3548       if(fabsf(step*multiplier) < min_visible)
3549         multiplier = min_visible / fabsf(step);
3550 
3551       if(up)
3552         dt_bauhaus_slider_set(widget, value + step * multiplier);
3553       else
3554         dt_bauhaus_slider_set(widget, value - step * multiplier);
3555     }
3556     else
3557     {
3558       const int currentval = dt_bauhaus_combobox_get(widget);
3559 
3560       if(up)
3561       {
3562         const int nextval = currentval + 1 >= dt_bauhaus_combobox_length(widget) ? 0 : currentval + 1;
3563         dt_bauhaus_combobox_set(widget, nextval);
3564       }
3565       else
3566       {
3567         const int prevval = currentval - 1 < 0 ? dt_bauhaus_combobox_length(widget) : currentval - 1;
3568         dt_bauhaus_combobox_set(widget, prevval);
3569       }
3570 
3571     }
3572     g_signal_emit_by_name(G_OBJECT(widget), "value-changed");
3573     dt_accel_widget_toast(widget);
3574     return;
3575   }
3576   // masks
3577   if(dev->form_visible) handled = dt_masks_events_mouse_scrolled(dev->gui_module, x, y, up, state);
3578   if(handled) return;
3579   // module
3580   if(dev->gui_module && dev->gui_module->scrolled
3581      && dt_dev_modulegroups_get_activated(darktable.develop) != DT_MODULEGROUP_BASICS)
3582     handled = dev->gui_module->scrolled(dev->gui_module, x, y, up, state);
3583   if(handled) return;
3584   // free zoom
3585   int procw, proch;
3586   dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
3587   int closeup = dt_control_get_dev_closeup();
3588   float zoom_x = dt_control_get_dev_zoom_x();
3589   float zoom_y = dt_control_get_dev_zoom_y();
3590   dt_dev_get_processed_size(dev, &procw, &proch);
3591   float scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 0);
3592   const float ppd = darktable.gui->ppd;
3593   const float fitscale = dt_dev_get_zoom_scale(dev, DT_ZOOM_FIT, 1.0, 0);
3594   const float oldscale = scale;
3595 
3596   // offset from center now (current zoom_{x,y} points there)
3597   const float mouse_off_x = x - 0.5f * dev->width;
3598   const float mouse_off_y = y - 0.5f * dev->height;
3599   zoom_x += mouse_off_x / (procw * scale);
3600   zoom_y += mouse_off_y / (proch * scale);
3601   zoom = DT_ZOOM_FREE;
3602   closeup = 0;
3603 
3604   const gboolean constrained = !dt_modifier_is(state, GDK_CONTROL_MASK);
3605   const gboolean low_ppd = (darktable.gui->ppd == 1);
3606   const float stepup = 0.1f * fabsf(1.0f - fitscale) / ppd;
3607 
3608   if(up)
3609   {
3610     if(fitscale <= 1.0f && (scale == (1.0f / ppd) || scale == (2.0f / ppd)) && constrained) return; // for large image size
3611     else if(fitscale > 1.0f && fitscale <= 2.0f && scale == (2.0f / ppd) && constrained) return; // for medium image size
3612 
3613     if((oldscale <= 1.0f / ppd) && constrained && (scale + stepup >= 1.0f / ppd))
3614       scale = 1.0f / ppd;
3615     else if((oldscale <= 2.0f / ppd) && constrained && (scale + stepup >= 2.0f / ppd))
3616       scale = 2.0f / ppd;
3617     // calculate new scale
3618     else if(scale >= 16.0f / ppd)
3619       return;
3620     else if(scale >= 8.0f / ppd)
3621       scale = 16.0f / ppd;
3622     else if(scale >= 4.0f / ppd)
3623       scale = 8.0f / ppd;
3624     else if(scale >= 2.0f / ppd)
3625       scale = 4.0f / ppd;
3626     else if(scale >= fitscale)
3627       scale += stepup;
3628     else
3629       scale += 0.5f * stepup;
3630   }
3631   else
3632   {
3633     if(fitscale <= 2.0f && ((scale == fitscale && constrained) || scale < 0.5 * fitscale)) return; // for large and medium image size
3634     else if(fitscale > 2.0f && scale < 1.0f / ppd) return; // for small image size
3635 
3636     // calculate new scale
3637     if(scale <= fitscale)
3638       scale -= 0.5f * stepup;
3639     else if(scale <= 2.0f / ppd)
3640       scale -= stepup;
3641     else if(scale <= 4.0f / ppd)
3642       scale = 2.0f / ppd;
3643     else if(scale <= 8.0f / ppd)
3644       scale = 4.0f / ppd;
3645     else
3646       scale = 8.0f / ppd;
3647   }
3648 
3649   if (fitscale <= 1.0f) // for large image size, stop at 1:1 and FIT levels, minimum at 0.5 * FIT
3650   {
3651     if((scale - 1.0) * (oldscale - 1.0) < 0) scale = 1.0f / ppd;
3652     if((scale - fitscale) * (oldscale - fitscale) < 0) scale = fitscale;
3653     scale = fmaxf(scale, 0.5 * fitscale);
3654   }
3655   else if (fitscale > 1.0f && fitscale <= 2.0f) // for medium image size, stop at 2:1 and FIT levels, minimum at 0.5 * FIT
3656   {
3657     if((scale - 2.0) * (oldscale - 2.0) < 0) scale = 2.0f / ppd;
3658     if((scale - fitscale) * (oldscale - fitscale) < 0) scale = fitscale;
3659     scale = fmaxf(scale, 0.5 * fitscale);
3660   }
3661   else scale = fmaxf(scale, 1.0f / ppd); // for small image size, minimum at 1:1
3662   scale = fminf(scale, 16.0f / ppd);
3663 
3664   // for 200% zoom or more we want pixel doubling instead of interpolation
3665   if(scale > 15.9999f / ppd)
3666   {
3667     scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
3668     zoom = DT_ZOOM_1;
3669     closeup = low_ppd ? 4 : 3;
3670   }
3671   else if(scale > 7.9999f / ppd)
3672   {
3673     scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
3674     zoom = DT_ZOOM_1;
3675     closeup = low_ppd ? 3 : 2;
3676   }
3677   else if(scale > 3.9999f / ppd)
3678   {
3679     scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
3680     zoom = DT_ZOOM_1;
3681     closeup = low_ppd ? 2 : 1;
3682   }
3683   else if(scale > 1.9999f / ppd)
3684   {
3685     scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
3686     zoom = DT_ZOOM_1;
3687     if(low_ppd) closeup = 1;
3688   }
3689 
3690   if(fabsf(scale - 1.0f) < 0.001f) zoom = DT_ZOOM_1;
3691   if(fabsf(scale - fitscale) < 0.001f) zoom = DT_ZOOM_FIT;
3692   dt_control_set_dev_zoom_scale(scale);
3693   dt_control_set_dev_closeup(closeup);
3694   scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 0);
3695 
3696   zoom_x -= mouse_off_x / (procw * scale);
3697   zoom_y -= mouse_off_y / (proch * scale);
3698   dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, zoom, closeup, NULL, NULL);
3699   dt_control_set_dev_zoom(zoom);
3700   dt_control_set_dev_zoom_x(zoom_x);
3701   dt_control_set_dev_zoom_y(zoom_y);
3702   dt_dev_invalidate(dev);
3703   dt_control_queue_redraw_center();
3704   dt_control_navigation_redraw();
3705 }
3706 
key_released(dt_view_t * self,guint key,guint state)3707 int key_released(dt_view_t *self, guint key, guint state)
3708 {
3709   const dt_control_accels_t *accels = &darktable.control->accels;
3710   dt_develop_t *lib = (dt_develop_t *)self->data;
3711 
3712   if(!darktable.control->key_accelerators_on)
3713     return 0;
3714 
3715   if(key == accels->darkroom_preview.accel_key && state == accels->darkroom_preview.accel_mods && lib->full_preview)
3716   {
3717     dt_ui_restore_panels(darktable.gui->ui);
3718     dt_control_set_dev_zoom(lib->full_preview_last_zoom);
3719     dt_control_set_dev_zoom_x(lib->full_preview_last_zoom_x);
3720     dt_control_set_dev_zoom_y(lib->full_preview_last_zoom_y);
3721     dt_control_set_dev_closeup(lib->full_preview_last_closeup);
3722     lib->full_preview = FALSE;
3723     dt_iop_request_focus(lib->full_preview_last_module);
3724     dt_masks_set_edit_mode(darktable.develop->gui_module, lib->full_preview_masks_state);
3725     dt_dev_invalidate(darktable.develop);
3726     dt_control_queue_redraw_center();
3727     dt_control_navigation_redraw();
3728   }
3729   // add an option to allow skip mouse events while editing masks
3730   if(key == accels->darkroom_skip_mouse_events.accel_key && state == accels->darkroom_skip_mouse_events.accel_mods)
3731   {
3732     darktable.develop->darkroom_skip_mouse_events = FALSE;
3733   }
3734 
3735   return 1;
3736 }
3737 
key_pressed(dt_view_t * self,guint key,guint state)3738 int key_pressed(dt_view_t *self, guint key, guint state)
3739 {
3740   const dt_control_accels_t *accels = &darktable.control->accels;
3741   dt_develop_t *lib = (dt_develop_t *)self->data;
3742 
3743   if(!darktable.control->key_accelerators_on)
3744     return 0;
3745 
3746   if(key == accels->darkroom_preview.accel_key && state == accels->darkroom_preview.accel_mods)
3747   {
3748     // hack to avoid triggering darkroom fullpreview if user enter the view with the key already pressed
3749     if(!lib->full_preview
3750        && (lib->preview_status == DT_DEV_PIXELPIPE_DIRTY || lib->preview_status == DT_DEV_PIXELPIPE_INVALID))
3751     {
3752       lib->full_preview = TRUE;
3753     }
3754 
3755     if(!lib->full_preview)
3756     {
3757       lib->full_preview = TRUE;
3758       // we hide all panels
3759       for(int k = 0; k < DT_UI_PANEL_SIZE; k++)
3760         dt_ui_panel_show(darktable.gui->ui, k, FALSE, FALSE);
3761       // we remember the masks edit state
3762       if(darktable.develop->gui_module)
3763       {
3764         dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)darktable.develop->gui_module->blend_data;
3765         if (bd) lib->full_preview_masks_state = bd->masks_shown;
3766       }
3767       // we set the zoom values to "fit"
3768       lib->full_preview_last_zoom = dt_control_get_dev_zoom();
3769       lib->full_preview_last_zoom_x = dt_control_get_dev_zoom_x();
3770       lib->full_preview_last_zoom_y = dt_control_get_dev_zoom_y();
3771       lib->full_preview_last_closeup = dt_control_get_dev_closeup();
3772       dt_control_set_dev_zoom(DT_ZOOM_FIT);
3773       dt_control_set_dev_zoom_x(0);
3774       dt_control_set_dev_zoom_y(0);
3775       dt_control_set_dev_closeup(0);
3776       // we quit the active iop if any
3777       lib->full_preview_last_module = darktable.develop->gui_module;
3778       dt_iop_request_focus(NULL);
3779       gtk_widget_grab_focus(dt_ui_center(darktable.gui->ui));
3780       dt_dev_invalidate(darktable.develop);
3781       dt_control_queue_redraw_center();
3782     }
3783     else
3784       return 0;
3785   }
3786 
3787   if(key == GDK_KEY_Left || key == GDK_KEY_Right || key == GDK_KEY_Up || key == GDK_KEY_Down)
3788   {
3789     dt_develop_t *dev = (dt_develop_t *)self->data;
3790     dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
3791     const int closeup = dt_control_get_dev_closeup();
3792     float scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 0);
3793     int procw, proch;
3794     dt_dev_get_processed_size(dev, &procw, &proch);
3795 
3796     // For each cursor press, move one screen by default
3797     float step_changex = dev->width / (procw * scale);
3798     float step_changey = dev->height / (proch * scale);
3799     float factor = 0.2f;
3800 
3801     if(dt_modifier_is(state, GDK_MOD1_MASK)) factor = 0.02f;
3802     if(dt_modifier_is(state, GDK_CONTROL_MASK)) factor = 1.0f;
3803 
3804     float old_zoom_x, old_zoom_y;
3805 
3806     old_zoom_x = dt_control_get_dev_zoom_x();
3807     old_zoom_y = dt_control_get_dev_zoom_y();
3808 
3809     float zx = old_zoom_x;
3810     float zy = old_zoom_y;
3811 
3812     if(key == GDK_KEY_Left) zx = zx - step_changex * factor;
3813     if(key == GDK_KEY_Right) zx = zx + step_changex * factor;
3814     if(key == GDK_KEY_Up) zy = zy - step_changey * factor;
3815     if(key == GDK_KEY_Down) zy = zy + step_changey * factor;
3816 
3817     dt_dev_check_zoom_bounds(dev, &zx, &zy, zoom, closeup, NULL, NULL);
3818     dt_control_set_dev_zoom_x(zx);
3819     dt_control_set_dev_zoom_y(zy);
3820 
3821     dt_dev_invalidate(dev);
3822     dt_control_queue_redraw_center();
3823     dt_control_navigation_redraw();
3824 
3825     return 1;
3826   }
3827 
3828   // add an option to allow skip mouse events while editing masks
3829   if(key == accels->darkroom_skip_mouse_events.accel_key && state == accels->darkroom_skip_mouse_events.accel_mods)
3830   {
3831     darktable.develop->darkroom_skip_mouse_events = TRUE;
3832     return 1;
3833   }
3834 
3835   return 1;
3836 }
3837 
search_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)3838 static gboolean search_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
3839                                            GdkModifierType modifier, gpointer data)
3840 {
3841   // set focus to the search module text box
3842   dt_dev_modulegroups_search_text_focus(darktable.develop);
3843   return TRUE;
3844 }
3845 
change_slider_accel_precision(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)3846 static gboolean change_slider_accel_precision(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
3847                                            GdkModifierType modifier, gpointer data)
3848 {
3849   const int curr_precision = dt_conf_get_int("accel/slider_precision");
3850   const int new_precision = curr_precision + 1 == 3 ? 0 : curr_precision + 1;
3851   dt_conf_set_int("accel/slider_precision", new_precision);
3852 
3853   if(new_precision == DT_IOP_PRECISION_FINE)
3854     dt_toast_log(_("keyboard shortcut slider precision: fine"));
3855   else if(new_precision == DT_IOP_PRECISION_NORMAL)
3856     dt_toast_log(_("keyboard shortcut slider precision: normal"));
3857   else
3858     dt_toast_log(_("keyboard shortcut slider precision: coarse"));
3859 
3860   return TRUE;
3861 }
3862 
zoom_in_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)3863 static gboolean zoom_in_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
3864                                            GdkModifierType modifier, gpointer data)
3865 {
3866   dt_view_t *self = (dt_view_t *)data;
3867   dt_develop_t *dev = (dt_develop_t *)self->data;
3868 
3869   scrolled(self, dev->width / 2, dev->height / 2, 1, modifier);
3870   return TRUE;
3871 }
3872 
zoom_out_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)3873 static gboolean zoom_out_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
3874                                            GdkModifierType modifier, gpointer data)
3875 {
3876   dt_view_t *self = (dt_view_t *)data;
3877   dt_develop_t *dev = (dt_develop_t *)self->data;
3878 
3879   scrolled(self, dev->width / 2, dev->height / 2, 0, modifier);
3880   return TRUE;
3881 }
3882 
3883 
configure(dt_view_t * self,int wd,int ht)3884 void configure(dt_view_t *self, int wd, int ht)
3885 {
3886   dt_develop_t *dev = (dt_develop_t *)self->data;
3887   dev->orig_width = wd;
3888   dev->orig_height = ht;
3889   dt_dev_configure(dev, wd, ht);
3890 }
3891 
init_key_accels(dt_view_t * self)3892 void init_key_accels(dt_view_t *self)
3893 {
3894   // Zoom shortcuts
3895   dt_accel_register_view(self, NC_("accel", "zoom close-up"), GDK_KEY_1, GDK_MOD1_MASK);
3896   dt_accel_register_view(self, NC_("accel", "zoom fill"), GDK_KEY_2, GDK_MOD1_MASK);
3897   dt_accel_register_view(self, NC_("accel", "zoom fit"), GDK_KEY_3, GDK_MOD1_MASK);
3898 
3899   // zoom in/out
3900   dt_accel_register_view(self, NC_("accel", "zoom in"), GDK_KEY_plus, GDK_CONTROL_MASK);
3901   dt_accel_register_view(self, NC_("accel", "zoom out"), GDK_KEY_minus, GDK_CONTROL_MASK);
3902 
3903   // Shortcut to skip images
3904   dt_accel_register_view(self, NC_("accel", "image forward"), GDK_KEY_space, 0);
3905   dt_accel_register_view(self, NC_("accel", "image back"), GDK_KEY_BackSpace, 0);
3906 
3907   // toggle ISO 12646 color assessment condition
3908   dt_accel_register_view(self, NC_("accel", "color assessment"), GDK_KEY_b, GDK_CONTROL_MASK);
3909 
3910   // toggle raw overexposure indication
3911   dt_accel_register_view(self, NC_("accel", "raw overexposed"), GDK_KEY_o, GDK_SHIFT_MASK);
3912 
3913   // toggle overexposure indication
3914   dt_accel_register_view(self, NC_("accel", "overexposed"), GDK_KEY_o, 0);
3915 
3916   // cycle overlay colors
3917   dt_accel_register_view(self, NC_("accel", "cycle overlay colors"), GDK_KEY_o, GDK_CONTROL_MASK);
3918 
3919   // toggle softproofing
3920   dt_accel_register_view(self, NC_("accel", "softproof"), GDK_KEY_s, GDK_CONTROL_MASK);
3921 
3922   // toggle gamut check
3923   dt_accel_register_view(self, NC_("accel", "gamut check"), GDK_KEY_g, GDK_CONTROL_MASK);
3924 
3925   // toggle visibility of drawn masks for current gui module
3926   dt_accel_register_view(self, NC_("accel", "show drawn masks"), 0, 0);
3927 
3928   // brush size +/-
3929   dt_accel_register_view(self, NC_("accel", "increase brush size"), 0, 0);
3930   dt_accel_register_view(self, NC_("accel", "decrease brush size"), 0, 0);
3931 
3932   // brush hardness +/-
3933   dt_accel_register_view(self, NC_("accel", "increase brush hardness"), GDK_KEY_braceright, 0);
3934   dt_accel_register_view(self, NC_("accel", "decrease brush hardness"), GDK_KEY_braceleft, 0);
3935 
3936   // brush opacity +/-
3937   dt_accel_register_view(self, NC_("accel", "increase brush opacity"), GDK_KEY_greater, 0);
3938   dt_accel_register_view(self, NC_("accel", "decrease brush opacity"), GDK_KEY_less, 0);
3939 
3940   // fullscreen view
3941   dt_accel_register_view(self, NC_("accel", "full preview"), GDK_KEY_w, 0);
3942 
3943   // undo/redo
3944   dt_accel_register_view(self, NC_("accel", "undo"), GDK_KEY_z, GDK_CONTROL_MASK);
3945   dt_accel_register_view(self, NC_("accel", "redo"), GDK_KEY_y, GDK_CONTROL_MASK);
3946 
3947   // add an option to allow skip mouse events while editing masks
3948   dt_accel_register_view(self, NC_("accel", "allow to pan & zoom while editing masks"), GDK_KEY_a, 0);
3949 
3950   // set focus to the search modules text box
3951   dt_accel_register_view(self, NC_("accel", "search modules"), 0, 0);
3952 
3953   // change the precision for adjusting sliders with keyboard shortcuts
3954   dt_accel_register_view(self, NC_("accel", "change keyboard shortcut slider precision"), 0, 0);
3955 }
3956 
_darkroom_undo_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)3957 static gboolean _darkroom_undo_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
3958                                         GdkModifierType modifier, gpointer data)
3959 {
3960   dt_undo_do_undo(darktable.undo, DT_UNDO_DEVELOP);
3961   return TRUE;
3962 }
3963 
_darkroom_redo_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)3964 static gboolean _darkroom_redo_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
3965                                         GdkModifierType modifier, gpointer data)
3966 {
3967   dt_undo_do_redo(darktable.undo, DT_UNDO_DEVELOP);
3968   return TRUE;
3969 }
3970 
connect_key_accels(dt_view_t * self)3971 void connect_key_accels(dt_view_t *self)
3972 {
3973   GClosure *closure;
3974   dt_develop_t *data = (dt_develop_t *)self->data;
3975 
3976   // Zoom shortcuts
3977   closure = g_cclosure_new(G_CALLBACK(zoom_key_accel), GINT_TO_POINTER(1), NULL);
3978   dt_accel_connect_view(self, "zoom close-up", closure);
3979 
3980   closure = g_cclosure_new(G_CALLBACK(zoom_key_accel), GINT_TO_POINTER(2), NULL);
3981   dt_accel_connect_view(self, "zoom fill", closure);
3982 
3983   closure = g_cclosure_new(G_CALLBACK(zoom_key_accel), GINT_TO_POINTER(3), NULL);
3984   dt_accel_connect_view(self, "zoom fit", closure);
3985 
3986   // zoom in/out
3987   closure = g_cclosure_new(G_CALLBACK(zoom_in_callback), (gpointer)self, NULL);
3988   dt_accel_connect_view(self, "zoom in", closure);
3989 
3990   closure = g_cclosure_new(G_CALLBACK(zoom_out_callback), (gpointer)self, NULL);
3991   dt_accel_connect_view(self, "zoom out", closure);
3992 
3993   // Shortcut to skip images
3994   closure = g_cclosure_new(G_CALLBACK(skip_f_key_accel_callback), (gpointer)self->data, NULL);
3995   dt_accel_connect_view(self, "image forward", closure);
3996 
3997   closure = g_cclosure_new(G_CALLBACK(skip_b_key_accel_callback), (gpointer)self->data, NULL);
3998   dt_accel_connect_view(self, "image back", closure);
3999 
4000   // toggle ISO 12646 color assessment condition
4001   closure = g_cclosure_new(G_CALLBACK(_toolbox_toggle_callback), data->iso_12646.button, NULL);
4002   dt_accel_connect_view(self, "color assessment", closure);
4003 
4004   // toggle raw overexposure indication
4005   closure = g_cclosure_new(G_CALLBACK(_toolbox_toggle_callback), data->rawoverexposed.button, NULL);
4006   dt_accel_connect_view(self, "raw overexposed", closure);
4007 
4008   // toggle overexposure indication
4009   closure = g_cclosure_new(G_CALLBACK(_toolbox_toggle_callback), data->overexposed.button, NULL);
4010   dt_accel_connect_view(self, "overexposed", closure);
4011 
4012   // cycle through overlay colors
4013   closure = g_cclosure_new(G_CALLBACK(_overlay_cycle_callback), (gpointer)self->data, NULL);
4014   dt_accel_connect_view(self, "cycle overlay colors", closure);
4015 
4016   // toggle visibility of drawn masks for current gui module
4017   closure = g_cclosure_new(G_CALLBACK(_toggle_mask_visibility_callback), (gpointer)self->data, NULL);
4018   dt_accel_connect_view(self, "show drawn masks", closure);
4019 
4020   // toggle softproof indication
4021   closure = g_cclosure_new(G_CALLBACK(_toolbox_toggle_callback), data->profile.softproof_button, NULL);
4022   dt_accel_connect_view(self, "softproof", closure);
4023 
4024   // toggle gamut indication
4025   closure = g_cclosure_new(G_CALLBACK(_toolbox_toggle_callback), data->profile.gamut_button, NULL);
4026   dt_accel_connect_view(self, "gamut check", closure);
4027 
4028   // brush size +/-
4029   closure = g_cclosure_new(G_CALLBACK(_brush_size_up_callback), (gpointer)self->data, NULL);
4030   dt_accel_connect_view(self, "increase brush size", closure);
4031   closure = g_cclosure_new(G_CALLBACK(_brush_size_down_callback), (gpointer)self->data, NULL);
4032   dt_accel_connect_view(self, "decrease brush size", closure);
4033 
4034   // brush hardness +/-
4035   closure = g_cclosure_new(G_CALLBACK(_brush_hardness_up_callback), (gpointer)self->data, NULL);
4036   dt_accel_connect_view(self, "increase brush hardness", closure);
4037   closure = g_cclosure_new(G_CALLBACK(_brush_hardness_down_callback), (gpointer)self->data, NULL);
4038   dt_accel_connect_view(self, "decrease brush hardness", closure);
4039 
4040   // brush opacity +/-
4041   closure = g_cclosure_new(G_CALLBACK(_brush_opacity_up_callback), (gpointer)self->data, NULL);
4042   dt_accel_connect_view(self, "increase brush opacity", closure);
4043   closure = g_cclosure_new(G_CALLBACK(_brush_opacity_down_callback), (gpointer)self->data, NULL);
4044   dt_accel_connect_view(self, "decrease brush opacity", closure);
4045 
4046   // undo/redo
4047   closure = g_cclosure_new(G_CALLBACK(_darkroom_undo_callback), (gpointer)self, NULL);
4048   dt_accel_connect_view(self, "undo", closure);
4049   closure = g_cclosure_new(G_CALLBACK(_darkroom_redo_callback), (gpointer)self, NULL);
4050   dt_accel_connect_view(self, "redo", closure);
4051 
4052   // search modules
4053   closure = g_cclosure_new(G_CALLBACK(search_callback), (gpointer)self, NULL);
4054   dt_accel_connect_view(self, "search modules", closure);
4055 
4056   // change the precision for adjusting sliders with keyboard shortcuts
4057   closure = g_cclosure_new(G_CALLBACK(change_slider_accel_precision), (gpointer)self, NULL);
4058   dt_accel_connect_view(self, "change keyboard shortcut slider precision", closure);
4059 }
4060 
mouse_actions(const dt_view_t * self)4061 GSList *mouse_actions(const dt_view_t *self)
4062 {
4063   GSList *lm = NULL;
4064   GSList *lm2 = NULL;
4065   lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_DOUBLE_LEFT, 0, _("switch to lighttable"));
4066   lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_SCROLL, 0, _("zoom in the image"));
4067   lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_SCROLL, GDK_CONTROL_MASK, _("unbounded zoom in the image"));
4068   lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_MIDDLE, 0, _("zoom to 100% 200% and back"));
4069   lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_SCROLL, GDK_SHIFT_MASK,
4070                                      _("[modules] expand module without closing others"));
4071   lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_DRAG_DROP, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
4072                                      _("[modules] change module position in pipe"));
4073 
4074   const dt_develop_t *dev = (dt_develop_t *)self->data;
4075   if(dev->form_visible)
4076   {
4077     // masks
4078     lm2 = dt_masks_mouse_actions(dev->form_visible);
4079   }
4080   else if(dev->gui_module && dev->gui_module->mouse_actions)
4081   {
4082     // modules with on canvas actions
4083     lm2 = dev->gui_module->mouse_actions(dev->gui_module);
4084   }
4085 
4086   return g_slist_concat(lm, lm2);
4087 }
4088 
4089 //-----------------------------------------------------------
4090 // second darkroom window
4091 //-----------------------------------------------------------
4092 
4093 /* helper macro that applies the DPI transformation to fixed pixel values. input should be defaulting to 96
4094  * DPI */
4095 #define DT_PIXEL_APPLY_DPI_2ND_WND(dev, value) ((value) * dev->second_window.dpi_factor)
4096 
dt_second_window_change_cursor(dt_develop_t * dev,dt_cursor_t curs)4097 static void dt_second_window_change_cursor(dt_develop_t *dev, dt_cursor_t curs)
4098 {
4099   GtkWidget *widget = dev->second_window.second_wnd;
4100   GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), curs);
4101   gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
4102   g_object_unref(cursor);
4103 }
4104 
second_window_expose(GtkWidget * widget,dt_develop_t * dev,cairo_t * cri,int32_t width,int32_t height,int32_t pointerx,int32_t pointery)4105 static void second_window_expose(GtkWidget *widget, dt_develop_t *dev, cairo_t *cri, int32_t width, int32_t height,
4106                                  int32_t pointerx, int32_t pointery)
4107 {
4108   cairo_set_source_rgb(cri, .2, .2, .2);
4109   cairo_save(cri);
4110 
4111   const int32_t tb = 0; // DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
4112   // account for border, make it transparent for other modules called below:
4113   pointerx -= tb;
4114   pointery -= tb;
4115 
4116   if(dev->preview2_status == DT_DEV_PIXELPIPE_DIRTY || dev->preview2_status == DT_DEV_PIXELPIPE_INVALID
4117      || dev->pipe->input_timestamp > dev->preview2_pipe->input_timestamp)
4118     dt_dev_process_preview2(dev);
4119 
4120   dt_pthread_mutex_t *mutex = NULL;
4121   const float zoom_y = dt_second_window_get_dev_zoom_y(dev);
4122   const float zoom_x = dt_second_window_get_dev_zoom_x(dev);
4123   const dt_dev_zoom_t zoom = dt_second_window_get_dev_zoom(dev);
4124   const int closeup = dt_second_window_get_dev_closeup(dev);
4125   const float backbuf_scale = dt_second_window_get_zoom_scale(dev, zoom, 1.0f, 0) * dev->second_window.ppd;
4126 
4127   static cairo_surface_t *image_surface = NULL;
4128   static int image_surface_width = 0, image_surface_height = 0, image_surface_imgid = -1;
4129 
4130   if(image_surface_width != width || image_surface_height != height || image_surface == NULL)
4131   {
4132     // create double-buffered image to draw on, to make modules draw more fluently.
4133     image_surface_width = width;
4134     image_surface_height = height;
4135     if(image_surface) cairo_surface_destroy(image_surface);
4136     image_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width * dev->second_window.ppd, height * dev->second_window.ppd);
4137     cairo_surface_set_device_scale(image_surface, dev->second_window.ppd, dev->second_window.ppd);
4138 
4139     image_surface_imgid = -1; // invalidate old stuff
4140   }
4141   cairo_surface_t *surface;
4142   cairo_t *cr = cairo_create(image_surface);
4143 
4144   if(dev->preview2_pipe->output_backbuf && // do we have an image?
4145     dev->preview2_pipe->backbuf_scale == backbuf_scale && // is this the zoom scale we want to display?
4146     dev->preview2_pipe->backbuf_zoom_x == zoom_x && dev->preview2_pipe->backbuf_zoom_y == zoom_y)
4147   {
4148     // draw image
4149     mutex = &dev->preview2_pipe->backbuf_mutex;
4150     dt_pthread_mutex_lock(mutex);
4151     float wd = dev->preview2_pipe->output_backbuf_width;
4152     float ht = dev->preview2_pipe->output_backbuf_height;
4153     const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, wd);
4154     surface = cairo_image_surface_create_for_data(dev->preview2_pipe->output_backbuf, CAIRO_FORMAT_RGB24, wd, ht, stride);
4155     cairo_surface_set_device_scale(surface, dev->second_window.ppd, dev->second_window.ppd);
4156     wd /= dev->second_window.ppd;
4157     ht /= dev->second_window.ppd;
4158     dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_DARKROOM_BG);
4159     cairo_paint(cr);
4160     cairo_translate(cr, .5f * (width - wd), .5f * (height - ht));
4161 
4162     if(closeup)
4163     {
4164       const double scale = 1<<closeup;
4165       cairo_scale(cr, scale, scale);
4166       cairo_translate(cr, -(.5 - 0.5/scale) * wd, -(.5 - 0.5/scale) * ht);
4167     }
4168 
4169     cairo_rectangle(cr, 0, 0, wd, ht);
4170     cairo_set_source_surface(cr, surface, 0, 0);
4171     cairo_pattern_set_filter(cairo_get_source(cr), _get_filtering_level(dev));
4172     cairo_fill(cr);
4173 
4174     if(darktable.gui->show_focus_peaking)
4175     {
4176       cairo_save(cr);
4177       cairo_scale(cr, 1.0f / dev->second_window.ppd, 1.0f / dev->second_window.ppd);
4178       dt_focuspeaking(cr, wd, ht, cairo_image_surface_get_data(surface),
4179                                   cairo_image_surface_get_width(surface),
4180                                   cairo_image_surface_get_height(surface));
4181       cairo_restore(cr);
4182     }
4183 
4184 
4185     cairo_surface_destroy(surface);
4186     dt_pthread_mutex_unlock(mutex);
4187     image_surface_imgid = dev->image_storage.id;
4188   }
4189   else if(dev->preview_pipe->output_backbuf)
4190   {
4191     // draw preview
4192     mutex = &dev->preview_pipe->backbuf_mutex;
4193     dt_pthread_mutex_lock(mutex);
4194 
4195     const float wd = dev->preview_pipe->output_backbuf_width;
4196     const float ht = dev->preview_pipe->output_backbuf_height;
4197     const float zoom_scale = dt_second_window_get_zoom_scale(dev, zoom, 1 << closeup, 1);
4198     dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_DARKROOM_BG);
4199     cairo_paint(cr);
4200     cairo_rectangle(cr, tb, tb, width - 2 * tb, height - 2 * tb);
4201     cairo_clip(cr);
4202     const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, wd);
4203     surface = cairo_image_surface_create_for_data(dev->preview_pipe->output_backbuf, CAIRO_FORMAT_RGB24, wd, ht, stride);
4204     cairo_translate(cr, width / 2.0, height / 2.0f);
4205     cairo_scale(cr, zoom_scale, zoom_scale);
4206     cairo_translate(cr, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht);
4207     // avoid to draw the 1px garbage that sometimes shows up in the preview :(
4208     cairo_rectangle(cr, 0, 0, wd - 1, ht - 1);
4209     cairo_set_source_surface(cr, surface, 0, 0);
4210     cairo_pattern_set_filter(cairo_get_source(cr), _get_filtering_level(dev));
4211     cairo_fill(cr);
4212     cairo_surface_destroy(surface);
4213     dt_pthread_mutex_unlock(mutex);
4214     image_surface_imgid = dev->image_storage.id;
4215   }
4216 
4217   cairo_restore(cri);
4218 
4219   if(image_surface_imgid == dev->image_storage.id)
4220   {
4221     cairo_destroy(cr);
4222     cairo_set_source_surface(cri, image_surface, 0, 0);
4223     cairo_paint(cri);
4224   }
4225 }
4226 
second_window_scrolled(GtkWidget * widget,dt_develop_t * dev,double x,double y,const int up,const int state)4227 static void second_window_scrolled(GtkWidget *widget, dt_develop_t *dev, double x, double y, const int up,
4228                                    const int state)
4229 {
4230   const int32_t tb = 0; // DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
4231   const int32_t capwd = dev->second_window.width - 2 * tb;
4232   const int32_t capht = dev->second_window.height - 2 * tb;
4233   const int32_t width_i = dev->second_window.width;
4234   const int32_t height_i = dev->second_window.height;
4235   if(width_i > capwd) x += (capwd - width_i) * .5f;
4236   if(height_i > capht) y += (capht - height_i) * .5f;
4237 
4238   // free zoom
4239   dt_dev_zoom_t zoom = dt_second_window_get_dev_zoom(dev);
4240   int procw, proch;
4241   int closeup = dt_second_window_get_dev_closeup(dev);
4242   float zoom_x = dt_second_window_get_dev_zoom_x(dev);
4243   float zoom_y = dt_second_window_get_dev_zoom_y(dev);
4244   dt_second_window_get_processed_size(dev, &procw, &proch);
4245   float scale = dt_second_window_get_zoom_scale(dev, zoom, 1 << closeup, 0);
4246   const float ppd = dev->second_window.ppd;
4247   const float fitscale = dt_second_window_get_zoom_scale(dev, DT_ZOOM_FIT, 1.0, 0);
4248   const float oldscale = scale;
4249 
4250   // offset from center now (current zoom_{x,y} points there)
4251   const float mouse_off_x = x - 0.5f * dev->second_window.width;
4252   const float mouse_off_y = y - 0.5f * dev->second_window.height;
4253   zoom_x += mouse_off_x / (procw * scale);
4254   zoom_y += mouse_off_y / (proch * scale);
4255   zoom = DT_ZOOM_FREE;
4256   closeup = 0;
4257 
4258   const gboolean constrained = !dt_modifier_is(state, GDK_CONTROL_MASK);
4259   const gboolean low_ppd = (dev->second_window.ppd == 1);
4260   const float stepup = 0.1f * fabsf(1.0f - fitscale) / ppd;
4261   if(up)
4262   {
4263     if(fitscale <= 1.0f && (scale == (1.0f / ppd) || scale == (2.0f / ppd)) && constrained) return; // for large image size
4264     else if(fitscale > 1.0f && fitscale <= 2.0f && scale == (2.0f / ppd) && constrained) return; // for medium image size
4265 
4266     if((oldscale <= 1.0f / ppd) && constrained && (scale + stepup >= 1.0f / ppd))
4267       scale = 1.0f / ppd;
4268     else if((oldscale <= 2.0f / ppd) && constrained && (scale + stepup >= 2.0f / ppd))
4269       scale = 2.0f / ppd;
4270     // calculate new scale
4271     else if(scale >= 16.0f / ppd)
4272       return;
4273     else if(scale >= 8.0f / ppd)
4274       scale = 16.0f / ppd;
4275     else if(scale >= 4.0f / ppd)
4276       scale = 8.0f / ppd;
4277     else if(scale >= 2.0f / ppd)
4278       scale = 4.0f / ppd;
4279     else if(scale >= fitscale)
4280       scale += stepup;
4281     else
4282       scale += 0.5f * stepup;
4283   }
4284   else
4285   {
4286     if(fitscale <= 2.0f && ((scale == fitscale && constrained) || scale < 0.5 * fitscale)) return; // for large and medium image size
4287     else if(fitscale > 2.0f && scale < 1.0f / ppd) return; // for small image size
4288 
4289     // calculate new scale
4290     if(scale <= fitscale)
4291       scale -= 0.5f * stepup;
4292     else if(scale <= 2.0f / ppd)
4293       scale -= stepup;
4294     else if(scale <= 4.0f / ppd)
4295       scale = 2.0f / ppd;
4296     else if(scale <= 8.0f / ppd)
4297       scale = 4.0f / ppd;
4298     else
4299       scale = 8.0f / ppd;
4300   }
4301   if (fitscale <= 1.0f) // for large image size, stop at 1:1 and FIT levels, minimum at 0.5 * FIT
4302   {
4303     if((scale - 1.0) * (oldscale - 1.0) < 0) scale = 1.0f / ppd;
4304     if((scale - fitscale) * (oldscale - fitscale) < 0) scale = fitscale;
4305     scale = fmaxf(scale, 0.5 * fitscale);
4306   }
4307   else if (fitscale > 1.0f && fitscale <= 2.0f) // for medium image size, stop at 2:1 and FIT levels, minimum at 0.5 * FIT
4308   {
4309     if((scale - 2.0) * (oldscale - 2.0) < 0) scale = 2.0f / ppd;
4310     if((scale - fitscale) * (oldscale - fitscale) < 0) scale = fitscale;
4311     scale = fmaxf(scale, 0.5 * fitscale);
4312   }
4313   else scale = fmaxf(scale, 1.0f / ppd); // for small image size, minimum at 1:1
4314   scale = fminf(scale, 16.0f / ppd);
4315 
4316   // for 200% zoom or more we want pixel doubling instead of interpolation
4317   if(scale > 15.9999f / ppd)
4318   {
4319     scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
4320     zoom = DT_ZOOM_1;
4321     closeup = low_ppd ? 4 : 3;
4322   }
4323   else if(scale > 7.9999f / ppd)
4324   {
4325     scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
4326     zoom = DT_ZOOM_1;
4327     closeup = low_ppd ? 3 : 2;
4328   }
4329   else if(scale > 3.9999f / ppd)
4330   {
4331     scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
4332     zoom = DT_ZOOM_1;
4333     closeup = low_ppd ? 2 : 1;
4334   }
4335   else if(scale > 1.9999f / ppd)
4336   {
4337    scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
4338    zoom = DT_ZOOM_1;
4339    if(low_ppd) closeup = 1;
4340   }
4341 
4342   if(fabsf(scale - 1.0f) < 0.001f) zoom = DT_ZOOM_1;
4343   if(fabsf(scale - fitscale) < 0.001f) zoom = DT_ZOOM_FIT;
4344   dt_second_window_set_zoom_scale(dev, scale);
4345   dt_second_window_set_dev_closeup(dev, closeup);
4346   scale = dt_second_window_get_zoom_scale(dev, zoom, 1 << closeup, 0);
4347 
4348   zoom_x -= mouse_off_x / (procw * scale);
4349   zoom_y -= mouse_off_y / (proch * scale);
4350   dt_second_window_check_zoom_bounds(dev, &zoom_x, &zoom_y, zoom, closeup, NULL, NULL);
4351   dt_second_window_set_dev_zoom(dev, zoom);
4352   dt_second_window_set_dev_zoom_x(dev, zoom_x);
4353   dt_second_window_set_dev_zoom_y(dev, zoom_y);
4354 
4355   // pipe needs to be reconstructed
4356   dev->preview2_status = DT_DEV_PIXELPIPE_DIRTY;
4357 
4358   gtk_widget_queue_draw(widget);
4359 }
4360 
second_window_leave(dt_develop_t * dev)4361 static void second_window_leave(dt_develop_t *dev)
4362 {
4363   // reset any changes the selected plugin might have made.
4364   dt_second_window_change_cursor(dev, GDK_LEFT_PTR);
4365 }
4366 
second_window_button_pressed(GtkWidget * widget,dt_develop_t * dev,double x,double y,const double pressure,const int which,const int type,const uint32_t state)4367 static int second_window_button_pressed(GtkWidget *widget, dt_develop_t *dev, double x, double y, const double pressure,
4368                                         const int which, const int type, const uint32_t state)
4369 {
4370   const int32_t tb = 0; // DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
4371   const int32_t capwd = dev->second_window.width - 2 * tb;
4372   const int32_t capht = dev->second_window.height - 2 * tb;
4373   const int32_t width_i = dev->second_window.width;
4374   const int32_t height_i = dev->second_window.height;
4375   if(width_i > capwd) x += (capwd - width_i) * .5f;
4376   if(height_i > capht) y += (capht - height_i) * .5f;
4377 
4378   dev->second_window.button_x = x - tb;
4379   dev->second_window.button_y = y - tb;
4380 
4381   if(which == 1 && type == GDK_2BUTTON_PRESS) return 0;
4382   if(which == 1)
4383   {
4384     dt_second_window_change_cursor(dev, GDK_HAND1);
4385     return 1;
4386   }
4387   if(which == 2)
4388   {
4389     // zoom to 1:1 2:1 and back
4390     int procw, proch;
4391     dt_dev_zoom_t zoom = dt_second_window_get_dev_zoom(dev);
4392     int closeup = dt_second_window_get_dev_closeup(dev);
4393     float zoom_x = dt_second_window_get_dev_zoom_x(dev);
4394     float zoom_y = dt_second_window_get_dev_zoom_y(dev);
4395     dt_second_window_get_processed_size(dev, &procw, &proch);
4396     float scale = dt_second_window_get_zoom_scale(dev, zoom, 1 << closeup, 0);
4397     const float ppd = dev->second_window.ppd;
4398     const gboolean low_ppd = dev->second_window.ppd == 1;
4399 
4400     const float mouse_off_x = x - 0.5f * dev->second_window.width;
4401     const float mouse_off_y = y - 0.5f * dev->second_window.height;
4402     zoom_x += mouse_off_x / (procw * scale);
4403     zoom_y += mouse_off_y / (proch * scale);
4404     const float tscale = scale * ppd;
4405     closeup = 0;
4406 
4407     if((tscale > 0.95f) && (tscale < 1.05f)) // we are at 100% and switch to 200%
4408     {
4409       zoom = DT_ZOOM_1;
4410       scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
4411       if(low_ppd) closeup = 1;
4412     }
4413     else if((tscale > 1.95f) && (tscale < 2.05f)) // at 200% so switch to zoomfit
4414     {
4415       zoom = DT_ZOOM_FIT;
4416       scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_FIT, 1.0, 0);
4417     }
4418     else // other than 100 or 200% so zoom to 100 %
4419     {
4420       if(low_ppd)
4421       {
4422         zoom = DT_ZOOM_1;
4423         scale = dt_dev_get_zoom_scale(dev, DT_ZOOM_1, 1.0, 0);
4424       }
4425       else
4426       {
4427         zoom = DT_ZOOM_FREE;
4428         scale = 1.0f / ppd;
4429       }
4430     }
4431     dt_second_window_set_zoom_scale(dev, scale);
4432     dt_second_window_set_dev_closeup(dev, closeup);
4433     scale = dt_second_window_get_zoom_scale(dev, zoom, 1 << closeup, 0);
4434     zoom_x -= mouse_off_x / (procw * scale);
4435     zoom_y -= mouse_off_y / (proch * scale);
4436     dt_second_window_check_zoom_bounds(dev, &zoom_x, &zoom_y, zoom, closeup, NULL, NULL);
4437     dt_second_window_set_dev_zoom(dev, zoom);
4438     dt_second_window_set_dev_zoom_x(dev, zoom_x);
4439     dt_second_window_set_dev_zoom_y(dev, zoom_y);
4440 
4441     // pipe needs to be reconstructed
4442     dev->preview2_status = DT_DEV_PIXELPIPE_DIRTY;
4443 
4444     gtk_widget_queue_draw(widget);
4445 
4446     return 1;
4447   }
4448   return 0;
4449 }
4450 
second_window_button_released(dt_develop_t * dev,const double x,const double y,const int which,const uint32_t state)4451 static int second_window_button_released(dt_develop_t *dev, const double x, const double y, const int which,
4452                                          const uint32_t state)
4453 {
4454   if(which == 1) dt_second_window_change_cursor(dev, GDK_LEFT_PTR);
4455   return 1;
4456 }
4457 
second_window_mouse_moved(GtkWidget * widget,dt_develop_t * dev,double x,double y,const double pressure,const int which)4458 static void second_window_mouse_moved(GtkWidget *widget, dt_develop_t *dev, double x, double y,
4459                                       const double pressure, const int which)
4460 {
4461   const int32_t tb = 0; // DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
4462   const int32_t capwd = dev->second_window.width - 2 * tb;
4463   const int32_t capht = dev->second_window.height - 2 * tb;
4464 
4465   const int32_t width_i = dev->second_window.width;
4466   const int32_t height_i = dev->second_window.height;
4467   int32_t offx = 0.0f, offy = 0.0f;
4468   if(width_i > capwd) offx = (capwd - width_i) * .5f;
4469   if(height_i > capht) offy = (capht - height_i) * .5f;
4470 
4471   x += offx;
4472   y += offy;
4473 
4474   if(which & GDK_BUTTON1_MASK)
4475   {
4476     // depending on dev_zoom, adjust dev_zoom_x/y.
4477     const dt_dev_zoom_t zoom = dt_second_window_get_dev_zoom(dev);
4478     const int closeup = dt_second_window_get_dev_closeup(dev);
4479     int procw, proch;
4480     dt_second_window_get_processed_size(dev, &procw, &proch);
4481     const float scale = dt_second_window_get_zoom_scale(dev, zoom, 1 << closeup, 0);
4482     float old_zoom_x, old_zoom_y;
4483     old_zoom_x = dt_second_window_get_dev_zoom_x(dev);
4484     old_zoom_y = dt_second_window_get_dev_zoom_y(dev);
4485     float zx = old_zoom_x - (1.0 / scale) * (x - dev->second_window.button_x - offx) / procw;
4486     float zy = old_zoom_y - (1.0 / scale) * (y - dev->second_window.button_y - offy) / proch;
4487     dt_second_window_check_zoom_bounds(dev, &zx, &zy, zoom, closeup, NULL, NULL);
4488     dt_second_window_set_dev_zoom_x(dev, zx);
4489     dt_second_window_set_dev_zoom_y(dev, zy);
4490     dev->second_window.button_x = x - offx;
4491     dev->second_window.button_y = y - offy;
4492 
4493     // pipe needs to be reconstructed
4494     dev->preview2_status = DT_DEV_PIXELPIPE_DIRTY;
4495 
4496     gtk_widget_queue_draw(widget);
4497   }
4498 }
4499 
_second_window_configure_ppd_dpi(dt_develop_t * dev)4500 static void _second_window_configure_ppd_dpi(dt_develop_t *dev)
4501 {
4502   GtkWidget *widget = dev->second_window.second_wnd;
4503 
4504   dev->second_window.ppd = dev->second_window.ppd_thb = dt_get_system_gui_ppd(widget);
4505   if(dt_conf_get_bool("ui/performance"))
4506     dev->second_window.ppd_thb *= DT_GUI_THUMBSIZE_REDUCE;
4507 
4508   // get the screen resolution
4509   float screen_dpi_overwrite = dt_conf_get_float("screen_dpi_overwrite");
4510   if(screen_dpi_overwrite > 0.0)
4511   {
4512     dev->second_window.dpi = screen_dpi_overwrite;
4513     gdk_screen_set_resolution(gtk_widget_get_screen(widget), screen_dpi_overwrite);
4514     dt_print(DT_DEBUG_CONTROL, "[screen resolution] setting the screen resolution to %f dpi as specified in "
4515                                "the configuration file\n", screen_dpi_overwrite);
4516   }
4517   else
4518   {
4519 #ifdef GDK_WINDOWING_QUARTZ
4520     dt_osx_autoset_dpi(widget);
4521 #endif
4522     dev->second_window.dpi = gdk_screen_get_resolution(gtk_widget_get_screen(widget));
4523     if(dev->second_window.dpi < 0.0)
4524     {
4525       dev->second_window.dpi = 96.0;
4526       gdk_screen_set_resolution(gtk_widget_get_screen(widget), 96.0);
4527       dt_print(DT_DEBUG_CONTROL, "[screen resolution] setting the screen resolution to the default 96 dpi\n");
4528     }
4529     else
4530       dt_print(DT_DEBUG_CONTROL, "[screen resolution] setting the screen resolution to %f dpi\n", dev->second_window.dpi);
4531   }
4532   dev->second_window.dpi_factor
4533       = dev->second_window.dpi / 96; // according to man xrandr and the docs of gdk_screen_set_resolution 96 is the default
4534 }
4535 
_second_window_draw_callback(GtkWidget * widget,cairo_t * crf,dt_develop_t * dev)4536 static gboolean _second_window_draw_callback(GtkWidget *widget, cairo_t *crf, dt_develop_t *dev)
4537 {
4538   int pointerx, pointery;
4539   GtkAllocation allocation;
4540   gtk_widget_get_allocation(widget, &allocation);
4541   const int32_t width = allocation.width;
4542   const int32_t height = allocation.height;
4543 
4544   dev->second_window.width = width;
4545   dev->second_window.height = height;
4546 
4547 #if GTK_CHECK_VERSION(3, 20, 0)
4548   gdk_window_get_device_position(gtk_widget_get_window(widget),
4549                                  gdk_seat_get_pointer(gdk_display_get_default_seat(gtk_widget_get_display(widget))),
4550                                  &pointerx, &pointery, NULL);
4551 #else
4552   GdkDevice *device
4553       = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gtk_widget_get_display(widget)));
4554   gdk_window_get_device_position(gtk_widget_get_window(widget), device, &pointerx, &pointery, NULL);
4555 #endif
4556 
4557   second_window_expose(widget, dev, crf, width, height, pointerx, pointery);
4558 
4559   return TRUE;
4560 }
4561 
_second_window_scrolled_callback(GtkWidget * widget,GdkEventScroll * event,dt_develop_t * dev)4562 static gboolean _second_window_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_develop_t *dev)
4563 {
4564   int delta_y;
4565   if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
4566   {
4567     second_window_scrolled(widget, dev, event->x, event->y, delta_y < 0, event->state & 0xf);
4568     gtk_widget_queue_draw(widget);
4569   }
4570 
4571   return TRUE;
4572 }
4573 
_second_window_button_pressed_callback(GtkWidget * w,GdkEventButton * event,dt_develop_t * dev)4574 static gboolean _second_window_button_pressed_callback(GtkWidget *w, GdkEventButton *event, dt_develop_t *dev)
4575 {
4576   double pressure = 1.0;
4577   GdkDevice *device = gdk_event_get_source_device((GdkEvent *)event);
4578 
4579   if(device && gdk_device_get_source(device) == GDK_SOURCE_PEN)
4580   {
4581     gdk_event_get_axis((GdkEvent *)event, GDK_AXIS_PRESSURE, &pressure);
4582   }
4583   second_window_button_pressed(w, dev, event->x, event->y, pressure, event->button, event->type, event->state & 0xf);
4584   gtk_widget_grab_focus(w);
4585   gtk_widget_queue_draw(w);
4586   return FALSE;
4587 }
4588 
_second_window_button_released_callback(GtkWidget * w,GdkEventButton * event,dt_develop_t * dev)4589 static gboolean _second_window_button_released_callback(GtkWidget *w, GdkEventButton *event, dt_develop_t *dev)
4590 {
4591   second_window_button_released(dev, event->x, event->y, event->button, event->state & 0xf);
4592   gtk_widget_queue_draw(w);
4593   return TRUE;
4594 }
4595 
_second_window_mouse_moved_callback(GtkWidget * w,GdkEventMotion * event,dt_develop_t * dev)4596 static gboolean _second_window_mouse_moved_callback(GtkWidget *w, GdkEventMotion *event, dt_develop_t *dev)
4597 {
4598   double pressure = 1.0;
4599   GdkDevice *device = gdk_event_get_source_device((GdkEvent *)event);
4600 
4601   if(device && gdk_device_get_source(device) == GDK_SOURCE_PEN)
4602   {
4603     gdk_event_get_axis((GdkEvent *)event, GDK_AXIS_PRESSURE, &pressure);
4604   }
4605   second_window_mouse_moved(w, dev, event->x, event->y, pressure, event->state);
4606   return FALSE;
4607 }
4608 
_second_window_leave_callback(GtkWidget * widget,GdkEventCrossing * event,dt_develop_t * dev)4609 static gboolean _second_window_leave_callback(GtkWidget *widget, GdkEventCrossing *event, dt_develop_t *dev)
4610 {
4611   second_window_leave(dev);
4612   return TRUE;
4613 }
4614 
_second_window_configure_callback(GtkWidget * da,GdkEventConfigure * event,dt_develop_t * dev)4615 static gboolean _second_window_configure_callback(GtkWidget *da, GdkEventConfigure *event, dt_develop_t *dev)
4616 {
4617   static int oldw = 0;
4618   static int oldh = 0;
4619 
4620   if(oldw != event->width || oldh != event->height)
4621   {
4622     dev->second_window.width = event->width;
4623     dev->second_window.height = event->height;
4624 
4625     // pipe needs to be reconstructed
4626     dev->preview2_status = DT_DEV_PIXELPIPE_DIRTY;
4627     dev->preview2_pipe->changed |= DT_DEV_PIPE_REMOVE;
4628     dev->preview2_pipe->cache_obsolete = 1;
4629   }
4630   oldw = event->width;
4631   oldh = event->height;
4632 
4633   dt_colorspaces_set_display_profile(DT_COLORSPACE_DISPLAY2);
4634 
4635 #ifndef GDK_WINDOWING_QUARTZ
4636   _second_window_configure_ppd_dpi(dev);
4637 #endif
4638 
4639   return TRUE;
4640 }
4641 
_darkroom_ui_second_window_init(GtkWidget * widget,dt_develop_t * dev)4642 static void _darkroom_ui_second_window_init(GtkWidget *widget, dt_develop_t *dev)
4643 {
4644   const int width = MAX(10, dt_conf_get_int("second_window/window_w"));
4645   const int height = MAX(10, dt_conf_get_int("second_window/window_h"));
4646 
4647   dev->second_window.width = width;
4648   dev->second_window.height = height;
4649 
4650   const gint x = MAX(0, dt_conf_get_int("second_window/window_x"));
4651   const gint y = MAX(0, dt_conf_get_int("second_window/window_y"));
4652   gtk_window_set_default_size(GTK_WINDOW(widget), width, height);
4653   gtk_widget_show_all(widget);
4654   gtk_window_move(GTK_WINDOW(widget), x, y);
4655   gtk_window_resize(GTK_WINDOW(widget), width, height);
4656   const int fullscreen = dt_conf_get_bool("second_window/fullscreen");
4657   if(fullscreen)
4658     gtk_window_fullscreen(GTK_WINDOW(widget));
4659   else
4660   {
4661     gtk_window_unfullscreen(GTK_WINDOW(widget));
4662     const int maximized = dt_conf_get_bool("second_window/maximized");
4663     if(maximized)
4664       gtk_window_maximize(GTK_WINDOW(widget));
4665     else
4666       gtk_window_unmaximize(GTK_WINDOW(widget));
4667   }
4668 }
4669 
_darkroom_ui_second_window_write_config(GtkWidget * widget)4670 static void _darkroom_ui_second_window_write_config(GtkWidget *widget)
4671 {
4672   GtkAllocation allocation;
4673   gtk_widget_get_allocation(widget, &allocation);
4674   gint x, y;
4675   gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
4676   dt_conf_set_int("second_window/window_x", x);
4677   dt_conf_set_int("second_window/window_y", y);
4678   dt_conf_set_int("second_window/window_w", allocation.width);
4679   dt_conf_set_int("second_window/window_h", allocation.height);
4680   dt_conf_set_bool("second_window/maximized",
4681                    (gdk_window_get_state(gtk_widget_get_window(widget)) & GDK_WINDOW_STATE_MAXIMIZED));
4682   dt_conf_set_bool("second_window/fullscreen",
4683                    (gdk_window_get_state(gtk_widget_get_window(widget)) & GDK_WINDOW_STATE_FULLSCREEN));
4684 }
4685 
_second_window_delete_callback(GtkWidget * widget,GdkEvent * event,dt_develop_t * dev)4686 static gboolean _second_window_delete_callback(GtkWidget *widget, GdkEvent *event, dt_develop_t *dev)
4687 {
4688   _darkroom_ui_second_window_write_config(dev->second_window.second_wnd);
4689 
4690   dev->second_window.second_wnd = NULL;
4691   dev->second_window.widget = NULL;
4692 
4693   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dev->second_window.button), FALSE);
4694 
4695   return FALSE;
4696 }
4697 
_second_window_key_pressed_callback(GtkWidget * widget,GdkEventKey * event,dt_develop_t * dev)4698 static gboolean _second_window_key_pressed_callback(GtkWidget *widget, GdkEventKey *event, dt_develop_t *dev)
4699 {
4700   int fullscreen;
4701 
4702   GtkAccelKey key_on, key_off;
4703   char path_on[256];
4704   char path_off[256];
4705   dt_accel_path_global(path_on, sizeof(path_on), "toggle fullscreen");
4706   dt_accel_path_global(path_off, sizeof(path_off), "leave fullscreen");
4707   gtk_accel_map_lookup_entry(path_on, &key_on);
4708   gtk_accel_map_lookup_entry(path_off, &key_off);
4709 
4710   if(event->keyval == key_on.accel_key && dt_modifier_is(event->state, key_on.accel_mods))
4711   {
4712     fullscreen = gdk_window_get_state(gtk_widget_get_window(widget)) & GDK_WINDOW_STATE_FULLSCREEN;
4713     if(fullscreen)
4714       gtk_window_unfullscreen(GTK_WINDOW(widget));
4715     else
4716       gtk_window_fullscreen(GTK_WINDOW(widget));
4717   }
4718   else if(event->keyval == key_off.accel_key && dt_modifier_is(event->state, key_off.accel_mods))
4719   {
4720     gtk_window_unfullscreen(GTK_WINDOW(widget));
4721   }
4722   else
4723   {
4724     return FALSE;
4725   }
4726 
4727   /* redraw center view */
4728   gtk_widget_queue_draw(dev->second_window.widget);
4729 #ifdef __APPLE__
4730   // workaround for GTK Quartz backend bug
4731   gtk_window_set_title(GTK_WINDOW(widget), _("darktable - darkroom preview"));
4732 #endif
4733   return TRUE;
4734 }
4735 
_darkroom_display_second_window(dt_develop_t * dev)4736 static void _darkroom_display_second_window(dt_develop_t *dev)
4737 {
4738   if(dev->second_window.second_wnd == NULL)
4739   {
4740     dev->second_window.width = -1;
4741     dev->second_window.height = -1;
4742 
4743     dev->second_window.second_wnd = gtk_window_new(GTK_WINDOW_TOPLEVEL);
4744     gtk_widget_set_name(dev->second_window.second_wnd, "second_window");
4745 
4746     _second_window_configure_ppd_dpi(dev);
4747 
4748     gtk_window_set_icon_name(GTK_WINDOW(dev->second_window.second_wnd), "darktable");
4749     gtk_window_set_title(GTK_WINDOW(dev->second_window.second_wnd), _("darktable - darkroom preview"));
4750 
4751     GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
4752     gtk_container_add(GTK_CONTAINER(dev->second_window.second_wnd), container);
4753 
4754     GtkWidget *widget = gtk_grid_new();
4755     gtk_box_pack_start(GTK_BOX(container), widget, TRUE, TRUE, 0);
4756 
4757     dev->second_window.widget = gtk_drawing_area_new();
4758     gtk_widget_set_size_request(dev->second_window.widget, DT_PIXEL_APPLY_DPI_2ND_WND(dev, 50), DT_PIXEL_APPLY_DPI_2ND_WND(dev, 200));
4759     gtk_widget_set_hexpand(dev->second_window.widget, TRUE);
4760     gtk_widget_set_vexpand(dev->second_window.widget, TRUE);
4761     gtk_widget_set_app_paintable(dev->second_window.widget, TRUE);
4762 
4763     gtk_grid_attach(GTK_GRID(widget), dev->second_window.widget, 0, 0, 1, 1);
4764 
4765     gtk_widget_set_events(dev->second_window.widget, GDK_POINTER_MOTION_MASK
4766                                                          | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
4767                                                          | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
4768                                                          | darktable.gui->scroll_mask);
4769 
4770     /* connect callbacks */
4771     g_signal_connect(G_OBJECT(dev->second_window.widget), "draw", G_CALLBACK(_second_window_draw_callback), dev);
4772     g_signal_connect(G_OBJECT(dev->second_window.widget), "scroll-event",
4773                      G_CALLBACK(_second_window_scrolled_callback), dev);
4774     g_signal_connect(G_OBJECT(dev->second_window.widget), "button-press-event",
4775                      G_CALLBACK(_second_window_button_pressed_callback), dev);
4776     g_signal_connect(G_OBJECT(dev->second_window.widget), "button-release-event",
4777                      G_CALLBACK(_second_window_button_released_callback), dev);
4778     g_signal_connect(G_OBJECT(dev->second_window.widget), "motion-notify-event",
4779                      G_CALLBACK(_second_window_mouse_moved_callback), dev);
4780     g_signal_connect(G_OBJECT(dev->second_window.widget), "leave-notify-event",
4781                      G_CALLBACK(_second_window_leave_callback), dev);
4782     g_signal_connect(G_OBJECT(dev->second_window.widget), "configure-event",
4783                      G_CALLBACK(_second_window_configure_callback), dev);
4784 
4785     g_signal_connect(G_OBJECT(dev->second_window.second_wnd), "delete-event",
4786                      G_CALLBACK(_second_window_delete_callback), dev);
4787     g_signal_connect(G_OBJECT(dev->second_window.second_wnd), "key-press-event",
4788                      G_CALLBACK(_second_window_key_pressed_callback), dev);
4789 
4790     _darkroom_ui_second_window_init(dev->second_window.second_wnd, dev);
4791   }
4792 
4793   gtk_widget_show_all(dev->second_window.second_wnd);
4794 }
4795 
4796 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
4797 // vim: shiftwidth=2 expandtab tabstop=2 cindent
4798 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
4799