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