1 /*
2     This file is part of darktable,
3     Copyright (C) 2012-2021 darktable developers.
4 
5     darktable is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     darktable is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "bauhaus/bauhaus.h"
19 #include "common/camera_control.h"
20 #include "common/darktable.h"
21 #include "common/image_cache.h"
22 #include "common/mipmap_cache.h"
23 #include "control/conf.h"
24 #include "control/control.h"
25 #include "control/jobs.h"
26 #include "dtgtk/button.h"
27 #include "gui/accelerators.h"
28 #include "gui/gtk.h"
29 #include "gui/guides.h"
30 #include "libs/lib.h"
31 #include "libs/lib_api.h"
32 #include <gdk/gdkkeysyms.h>
33 
34 typedef enum dt_lib_live_view_focus_control_t
35 {
36   DT_FOCUS_NEAR = 0,
37   DT_FOCUS_NEARER = 2,
38   DT_FOCUS_FAR = 4,
39   DT_FOCUS_FARTHER = 6
40 } dt_lib_live_view_focus_control_t;
41 
42 typedef enum dt_lib_live_view_flip_t
43 {
44   FLAG_FLIP_NONE = 0,
45   FLAG_FLIP_HORIZONTAL = 1<<0,
46   FLAG_FLIP_VERTICAL = 1<<1,
47   FLAG_FLIP_BOTH = FLAG_FLIP_HORIZONTAL|FLAG_FLIP_VERTICAL
48 } dt_lib_live_view_flip_t;
49 
50 typedef enum dt_lib_live_view_overlay_t
51 {
52   OVERLAY_NONE = 0,
53   OVERLAY_SELECTED,
54   OVERLAY_ID
55 } dt_lib_live_view_overlay_t;
56 
57 #define HANDLE_SIZE 0.02
58 
59 static const cairo_operator_t _overlay_modes[] = {
60   CAIRO_OPERATOR_OVER, CAIRO_OPERATOR_XOR, CAIRO_OPERATOR_ADD, CAIRO_OPERATOR_SATURATE,
61   CAIRO_OPERATOR_MULTIPLY, CAIRO_OPERATOR_SCREEN, CAIRO_OPERATOR_OVERLAY, CAIRO_OPERATOR_DARKEN,
62   CAIRO_OPERATOR_LIGHTEN, CAIRO_OPERATOR_COLOR_DODGE, CAIRO_OPERATOR_COLOR_BURN, CAIRO_OPERATOR_HARD_LIGHT,
63   CAIRO_OPERATOR_SOFT_LIGHT, CAIRO_OPERATOR_DIFFERENCE, CAIRO_OPERATOR_EXCLUSION, CAIRO_OPERATOR_HSL_HUE,
64   CAIRO_OPERATOR_HSL_SATURATION, CAIRO_OPERATOR_HSL_COLOR, CAIRO_OPERATOR_HSL_LUMINOSITY
65 };
66 
67 DT_MODULE(1)
68 
69 typedef struct dt_lib_live_view_t
70 {
71   int imgid;
72   int splitline_rotation;
73   double overlay_x0, overlay_x1, overlay_y0, overlay_y1;
74   double splitline_x, splitline_y; // 0..1
75   gboolean splitline_dragging;
76 
77   GtkWidget *live_view, *live_view_zoom, *rotate_ccw, *rotate_cw, *flip;
78   GtkWidget *auto_focus, *focus_out_small, *focus_out_big, *focus_in_small, *focus_in_big;
79   GtkWidget *guide_selector, *flip_guides, *guides_widgets;
80   GList *guides_widgets_list;
81   GtkWidget *overlay, *overlay_id_box, *overlay_id, *overlay_mode, *overlay_splitline;
82 } dt_lib_live_view_t;
83 
guides_presets_set_visibility(dt_lib_live_view_t * lib,int which)84 static void guides_presets_set_visibility(dt_lib_live_view_t *lib, int which)
85 {
86   if(which == 0)
87   {
88     gtk_widget_set_no_show_all(lib->guides_widgets, TRUE);
89     gtk_widget_hide(lib->guides_widgets);
90     gtk_widget_set_no_show_all(lib->flip_guides, TRUE);
91     gtk_widget_hide(lib->flip_guides);
92   }
93   else
94   {
95     GtkWidget *widget = g_list_nth_data(lib->guides_widgets_list, which - 1);
96     if(widget)
97     {
98       gtk_widget_set_no_show_all(lib->guides_widgets, FALSE);
99       gtk_widget_show_all(lib->guides_widgets);
100       gtk_stack_set_visible_child(GTK_STACK(lib->guides_widgets), widget);
101     }
102     else
103     {
104       gtk_widget_set_no_show_all(lib->guides_widgets, TRUE);
105       gtk_widget_hide(lib->guides_widgets);
106     }
107     gtk_widget_set_no_show_all(lib->flip_guides, FALSE);
108     gtk_widget_show_all(lib->flip_guides);
109   }
110 
111   // TODO: add a support_flip flag to guides to hide the flip gui?
112 }
113 
guides_presets_changed(GtkWidget * combo,dt_lib_live_view_t * lib)114 static void guides_presets_changed(GtkWidget *combo, dt_lib_live_view_t *lib)
115 {
116   int which = dt_bauhaus_combobox_get(combo);
117   guides_presets_set_visibility(lib, which);
118 }
119 
overlay_changed(GtkWidget * combo,dt_lib_live_view_t * lib)120 static void overlay_changed(GtkWidget *combo, dt_lib_live_view_t *lib)
121 {
122   int which = dt_bauhaus_combobox_get(combo);
123   if(which == OVERLAY_NONE)
124   {
125     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE);
126     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE);
127   }
128   else
129   {
130     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), TRUE);
131     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), TRUE);
132   }
133 
134   if(which == OVERLAY_ID)
135     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), TRUE);
136   else
137     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE);
138 }
139 
140 
name(dt_lib_module_t * self)141 const char *name(dt_lib_module_t *self)
142 {
143   return _("live view");
144 }
145 
views(dt_lib_module_t * self)146 const char **views(dt_lib_module_t *self)
147 {
148   static const char *v[] = {"tethering", NULL};
149   return v;
150 }
151 
container(dt_lib_module_t * self)152 uint32_t container(dt_lib_module_t *self)
153 {
154   return DT_UI_CONTAINER_PANEL_RIGHT_CENTER;
155 }
156 
157 
gui_reset(dt_lib_module_t * self)158 void gui_reset(dt_lib_module_t *self)
159 {
160 }
161 
position()162 int position()
163 {
164   return 998;
165 }
166 
init_key_accels(dt_lib_module_t * self)167 void init_key_accels(dt_lib_module_t *self)
168 {
169   dt_accel_register_lib(self, NC_("accel", "toggle live view"), GDK_KEY_v, 0);
170   dt_accel_register_lib(self, NC_("accel", "zoom live view"), GDK_KEY_w, 0);
171   dt_accel_register_lib(self, NC_("accel", "rotate 90 degrees CCW"), 0, 0);
172   dt_accel_register_lib(self, NC_("accel", "rotate 90 degrees CW"), 0, 0);
173   dt_accel_register_lib(self, NC_("accel", "flip horizontally"), 0, 0);
174   dt_accel_register_lib(self, NC_("accel", "move focus point in (big steps)"), 0, 0);
175   dt_accel_register_lib(self, NC_("accel", "move focus point in (small steps)"), 0, 0);
176   dt_accel_register_lib(self, NC_("accel", "move focus point out (small steps)"), 0, 0);
177   dt_accel_register_lib(self, NC_("accel", "move focus point out (big steps)"), 0, 0);
178 }
179 
connect_key_accels(dt_lib_module_t * self)180 void connect_key_accels(dt_lib_module_t *self)
181 {
182   dt_lib_live_view_t *lib = (dt_lib_live_view_t *)self->data;
183 
184   dt_accel_connect_button_lib(self, "toggle live view", GTK_WIDGET(lib->live_view));
185   dt_accel_connect_button_lib(self, "zoom live view", GTK_WIDGET(lib->live_view_zoom));
186   dt_accel_connect_button_lib(self, "rotate 90 degrees CCW", GTK_WIDGET(lib->rotate_ccw));
187   dt_accel_connect_button_lib(self, "rotate 90 degrees CW", GTK_WIDGET(lib->rotate_cw));
188   dt_accel_connect_button_lib(self, "flip horizontally", GTK_WIDGET(lib->flip));
189   dt_accel_connect_button_lib(self, "move focus point in (big steps)", GTK_WIDGET(lib->focus_in_big));
190   dt_accel_connect_button_lib(self, "move focus point in (small steps)", GTK_WIDGET(lib->focus_in_small));
191   dt_accel_connect_button_lib(self, "move focus point out (small steps)", GTK_WIDGET(lib->focus_out_small));
192   dt_accel_connect_button_lib(self, "move focus point out (big steps)", GTK_WIDGET(lib->focus_out_big));
193 }
194 
_rotate_ccw(GtkWidget * widget,gpointer user_data)195 static void _rotate_ccw(GtkWidget *widget, gpointer user_data)
196 {
197   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
198   cam->live_view_rotation = (cam->live_view_rotation + 1) % 4; // 0 -> 1 -> 2 -> 3 -> 0 -> ...
199 }
200 
_rotate_cw(GtkWidget * widget,gpointer user_data)201 static void _rotate_cw(GtkWidget *widget, gpointer user_data)
202 {
203   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
204   cam->live_view_rotation = (cam->live_view_rotation + 3) % 4; // 0 -> 3 -> 2 -> 1 -> 0 -> ...
205 }
206 
207 // Congratulations to Simon for being the first one recognizing live view in a screen shot ^^
_toggle_live_view_clicked(GtkWidget * widget,gpointer user_data)208 static void _toggle_live_view_clicked(GtkWidget *widget, gpointer user_data)
209 {
210   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) == TRUE)
211   {
212     if(dt_camctl_camera_start_live_view(darktable.camctl) == FALSE)
213       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
214   }
215   else
216   {
217     dt_camctl_camera_stop_live_view(darktable.camctl);
218   }
219 }
220 
221 // TODO: using a toggle button would be better, but this setting can also be changed by right clicking on the
222 // canvas (src/views/capture.c).
223 //       maybe using a signal would work? i have no idea.
_zoom_live_view_clicked(GtkWidget * widget,gpointer user_data)224 static void _zoom_live_view_clicked(GtkWidget *widget, gpointer user_data)
225 {
226   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
227   if(cam->is_live_viewing)
228   {
229     cam->live_view_zoom = !cam->live_view_zoom;
230     if(cam->live_view_zoom == TRUE)
231       dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "5");
232     else
233       dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "1");
234   }
235 }
236 
_auto_focus_button_clicked(GtkWidget * widget,gpointer user_data)237 static void _auto_focus_button_clicked(GtkWidget *widget, gpointer user_data)
238 {
239   const char *property = "autofocusdrive";
240   CameraWidgetType property_type;
241   if(dt_camctl_camera_get_property_type(darktable.camctl, NULL, property, &property_type))
242   {
243     dt_print(DT_DEBUG_CAMCTL, "[camera control] unable to get property type for %s\n", property);
244   }
245   else
246   {
247     if(property_type == GP_WIDGET_TOGGLE)
248     {
249       dt_camctl_camera_set_property_toggle(darktable.camctl, NULL, property);
250     }
251     else
252     {
253       // TODO evaluate if this is the right thing to do in default scenario
254       dt_print(DT_DEBUG_CAMCTL, "[camera control] unable to set %s for property type %d\n", property, property_type);
255     }
256   }
257 }
258 
_focus_button_clicked(GtkWidget * widget,gpointer user_data)259 static void _focus_button_clicked(GtkWidget *widget, gpointer user_data)
260 {
261   int focus = GPOINTER_TO_INT(user_data);
262   CameraWidgetType property_type;
263   if(dt_camctl_camera_get_property_type(darktable.camctl, NULL, "manualfocusdrive", &property_type))
264   {
265     // default to avoid breaking backwards compatibility
266     // note that this might not work on non-Canon EOS cameras
267     dt_camctl_camera_set_property_choice(darktable.camctl, NULL, "manualfocusdrive", focus);
268   }
269   else
270   {
271     // we need to check the property type here because of a peculiar difference between the property type that gphoto2
272     // supports for Canon EOS and Nikon systems. In particular, if you have a Canon, expect a TOGGLE or RADIO.
273     // If you have a Nikon, expect a RANGE.
274     switch(property_type)
275     {
276       case GP_WIDGET_RANGE:
277       {
278         float focus_amount;
279         switch(focus)
280         {
281           case DT_FOCUS_NEARER:
282             focus_amount = 250;
283             break;
284           case DT_FOCUS_NEAR:
285             focus_amount = 50;
286             break;
287           case DT_FOCUS_FAR:
288             focus_amount = -50;
289             break;
290           case DT_FOCUS_FARTHER:
291             focus_amount = -250;
292             break;
293           default:
294             focus_amount = 0;
295         }
296         dt_camctl_camera_set_property_float(darktable.camctl, NULL, "manualfocusdrive", focus_amount);
297         break;
298       }
299       case GP_WIDGET_TOGGLE | GP_WIDGET_RADIO:
300         dt_camctl_camera_set_property_choice(darktable.camctl, NULL, "manualfocusdrive", focus);
301         break;
302       default:
303         // TODO evaluate if this is the right thing to do in default scenario
304         dt_print(DT_DEBUG_CAMCTL, "[camera control] unable to set manualfocusdrive for property type %d", property_type);
305         break;
306     }
307   }
308 }
309 
_toggle_flip_clicked(GtkWidget * widget,gpointer user_data)310 static void _toggle_flip_clicked(GtkWidget *widget, gpointer user_data)
311 {
312   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
313   cam->live_view_flip = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
314 }
315 
_overlay_id_changed(GtkWidget * widget,gpointer user_data)316 static void _overlay_id_changed(GtkWidget *widget, gpointer user_data)
317 {
318   dt_lib_live_view_t *lib = (dt_lib_live_view_t *)user_data;
319   lib->imgid = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
320   dt_conf_set_int("plugins/lighttable/live_view/overlay_imgid", lib->imgid);
321 }
322 
_overlay_mode_changed(GtkWidget * combo,gpointer user_data)323 static void _overlay_mode_changed(GtkWidget *combo, gpointer user_data)
324 {
325   dt_conf_set_int("plugins/lighttable/live_view/overlay_mode", dt_bauhaus_combobox_get(combo));
326 }
327 
_overlay_splitline_changed(GtkWidget * combo,gpointer user_data)328 static void _overlay_splitline_changed(GtkWidget *combo, gpointer user_data)
329 {
330   dt_conf_set_int("plugins/lighttable/live_view/splitline", dt_bauhaus_combobox_get(combo));
331 }
332 
gui_init(dt_lib_module_t * self)333 void gui_init(dt_lib_module_t *self)
334 {
335   self->data = calloc(1, sizeof(dt_lib_live_view_t));
336 
337   // Setup lib data
338   dt_lib_live_view_t *lib = self->data;
339   lib->splitline_x = lib->splitline_y = 0.5;
340 
341   // Setup gui
342   self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
343   dt_gui_add_help_link(self->widget, dt_get_help_url("tethering_live_view"));
344   GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
345   gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0);
346   lib->live_view = dtgtk_togglebutton_new(dtgtk_cairo_paint_eye, CPF_STYLE_FLAT, NULL);
347   lib->live_view_zoom = dtgtk_button_new(
348       dtgtk_cairo_paint_zoom, CPF_STYLE_FLAT, NULL); // TODO: see _zoom_live_view_clicked
349   lib->rotate_ccw = dtgtk_button_new(dtgtk_cairo_paint_refresh, CPF_STYLE_FLAT, NULL);
350   lib->rotate_cw = dtgtk_button_new(dtgtk_cairo_paint_refresh,
351                                     CPF_STYLE_FLAT | CPF_DIRECTION_UP, NULL);
352   lib->flip = dtgtk_togglebutton_new(dtgtk_cairo_paint_flip,
353                                      CPF_STYLE_FLAT | CPF_DIRECTION_UP, NULL);
354 
355   gtk_box_pack_start(GTK_BOX(box), lib->live_view, TRUE, TRUE, 0);
356   gtk_box_pack_start(GTK_BOX(box), lib->live_view_zoom, TRUE, TRUE, 0);
357   gtk_box_pack_start(GTK_BOX(box), lib->rotate_ccw, TRUE, TRUE, 0);
358   gtk_box_pack_start(GTK_BOX(box), lib->rotate_cw, TRUE, TRUE, 0);
359   gtk_box_pack_start(GTK_BOX(box), lib->flip, TRUE, TRUE, 0);
360 
361   gtk_widget_set_tooltip_text(lib->live_view, _("toggle live view"));
362   gtk_widget_set_tooltip_text(lib->live_view_zoom, _("zoom live view"));
363   gtk_widget_set_tooltip_text(lib->rotate_ccw, _("rotate 90 degrees ccw"));
364   gtk_widget_set_tooltip_text(lib->rotate_cw, _("rotate 90 degrees cw"));
365   gtk_widget_set_tooltip_text(lib->flip, _("flip live view horizontally"));
366 
367   g_signal_connect(G_OBJECT(lib->live_view), "clicked", G_CALLBACK(_toggle_live_view_clicked), lib);
368   g_signal_connect(G_OBJECT(lib->live_view_zoom), "clicked", G_CALLBACK(_zoom_live_view_clicked), lib);
369   g_signal_connect(G_OBJECT(lib->rotate_ccw), "clicked", G_CALLBACK(_rotate_ccw), lib);
370   g_signal_connect(G_OBJECT(lib->rotate_cw), "clicked", G_CALLBACK(_rotate_cw), lib);
371   g_signal_connect(G_OBJECT(lib->flip), "clicked", G_CALLBACK(_toggle_flip_clicked), lib);
372 
373   // focus buttons
374   box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
375   gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0);
376   lib->focus_in_big = dtgtk_button_new(dtgtk_cairo_paint_solid_triangle,
377                                        CPF_STYLE_FLAT | CPF_DIRECTION_LEFT, NULL);
378   lib->focus_in_small
379       = dtgtk_button_new(dtgtk_cairo_paint_arrow, CPF_STYLE_FLAT
380                                                   | CPF_DIRECTION_LEFT, NULL); // TODO icon not centered
381   lib->auto_focus = dtgtk_button_new(dtgtk_cairo_paint_lock, CPF_STYLE_FLAT, NULL);
382   lib->focus_out_small = dtgtk_button_new(dtgtk_cairo_paint_arrow, CPF_STYLE_FLAT
383                                                                    | CPF_DIRECTION_RIGHT, NULL); // TODO same here
384   lib->focus_out_big = dtgtk_button_new(dtgtk_cairo_paint_solid_triangle,
385                                         CPF_STYLE_FLAT | CPF_DIRECTION_RIGHT, NULL);
386 
387   gtk_box_pack_start(GTK_BOX(box), lib->focus_in_big, TRUE, TRUE, 0);
388   gtk_box_pack_start(GTK_BOX(box), lib->focus_in_small, TRUE, TRUE, 0);
389   gtk_box_pack_start(GTK_BOX(box), lib->auto_focus, TRUE, TRUE, 0);
390   gtk_box_pack_start(GTK_BOX(box), lib->focus_out_small, TRUE, TRUE, 0);
391   gtk_box_pack_start(GTK_BOX(box), lib->focus_out_big, TRUE, TRUE, 0);
392 
393   gtk_widget_set_tooltip_text(lib->focus_in_big, _("move focus point in (big steps)"));
394   gtk_widget_set_tooltip_text(lib->focus_in_small, _("move focus point in (small steps)"));
395   gtk_widget_set_tooltip_text(lib->auto_focus, _("run autofocus"));
396   gtk_widget_set_tooltip_text(lib->focus_out_small, _("move focus point out (small steps)"));
397   gtk_widget_set_tooltip_text(lib->focus_out_big, _("move focus point out (big steps)"));
398 
399 
400   g_signal_connect(G_OBJECT(lib->focus_in_big), "clicked",
401                    G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(DT_FOCUS_NEARER));
402   g_signal_connect(G_OBJECT(lib->focus_in_small), "clicked",
403                    G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(DT_FOCUS_NEAR));
404   g_signal_connect(G_OBJECT(lib->auto_focus), "clicked",
405                    G_CALLBACK(_auto_focus_button_clicked), GINT_TO_POINTER(1));
406   g_signal_connect(G_OBJECT(lib->focus_out_small), "clicked",
407                    G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(DT_FOCUS_FAR));
408   g_signal_connect(G_OBJECT(lib->focus_out_big), "clicked",
409                    G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(DT_FOCUS_FARTHER));
410 
411   // Guides
412   lib->guide_selector = dt_bauhaus_combobox_new(NULL);
413   dt_bauhaus_widget_set_label(lib->guide_selector, NULL, N_("guides"));
414   gtk_box_pack_start(GTK_BOX(self->widget), lib->guide_selector, TRUE, TRUE, 0);
415 
416   lib->guides_widgets = gtk_stack_new();
417   gtk_stack_set_homogeneous(GTK_STACK(lib->guides_widgets), FALSE);
418   gtk_box_pack_start(GTK_BOX(self->widget), lib->guides_widgets, TRUE, TRUE, 0);
419 
420   dt_bauhaus_combobox_add(lib->guide_selector, _("none"));
421   int i = 0;
422   for(GList *iter = darktable.guides; iter; iter = g_list_next(iter), i++)
423   {
424     GtkWidget *widget = NULL;
425     dt_guides_t *guide = (dt_guides_t *)iter->data;
426     dt_bauhaus_combobox_add(lib->guide_selector, _(guide->name));
427     if(guide->widget)
428     {
429       // generate some unique name so that we can have the same name several times
430       char name[5];
431       snprintf(name, sizeof(name), "%d", i);
432       widget = guide->widget(NULL, guide->user_data);
433       gtk_widget_show_all(widget);
434       gtk_stack_add_named(GTK_STACK(lib->guides_widgets), widget, name);
435     }
436     lib->guides_widgets_list = g_list_append(lib->guides_widgets_list, widget);
437   }
438   gtk_widget_set_no_show_all(lib->guides_widgets, TRUE);
439 
440   gtk_widget_set_tooltip_text(lib->guide_selector, _("display guide lines to help compose your photograph"));
441   g_signal_connect(G_OBJECT(lib->guide_selector), "value-changed", G_CALLBACK(guides_presets_changed), lib);
442 
443   lib->flip_guides = dt_bauhaus_combobox_new(NULL);
444   dt_bauhaus_widget_set_label(lib->flip_guides, NULL, N_("flip"));
445   dt_bauhaus_combobox_add(lib->flip_guides, _("none"));
446   dt_bauhaus_combobox_add(lib->flip_guides, _("horizontally"));
447   dt_bauhaus_combobox_add(lib->flip_guides, _("vertically"));
448   dt_bauhaus_combobox_add(lib->flip_guides, _("both"));
449   gtk_widget_set_tooltip_text(lib->flip_guides, _("flip guides"));
450   gtk_box_pack_start(GTK_BOX(self->widget), lib->flip_guides, TRUE, TRUE, 0);
451 
452   lib->overlay = dt_bauhaus_combobox_new(NULL);
453   dt_bauhaus_widget_set_label(lib->overlay, NULL, N_("overlay"));
454   dt_bauhaus_combobox_add(lib->overlay, _("none"));
455   dt_bauhaus_combobox_add(lib->overlay, _("selected image"));
456   dt_bauhaus_combobox_add(lib->overlay, _("id"));
457   gtk_widget_set_tooltip_text(lib->overlay, _("overlay another image over the live view"));
458   g_signal_connect(G_OBJECT(lib->overlay), "value-changed", G_CALLBACK(overlay_changed), lib);
459   gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay, TRUE, TRUE, 0);
460 
461   lib->overlay_id_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
462   GtkWidget *label = gtk_label_new(_("image id"));
463   gtk_widget_set_halign(label, GTK_ALIGN_START);
464   lib->overlay_id = gtk_spin_button_new_with_range(0, 1000000000, 1);
465   gtk_spin_button_set_digits(GTK_SPIN_BUTTON(lib->overlay_id), 0);
466   gtk_widget_set_tooltip_text(lib->overlay_id, _("enter image id of the overlay manually"));
467   g_signal_connect(G_OBJECT(lib->overlay_id), "value-changed", G_CALLBACK(_overlay_id_changed), lib);
468   gtk_spin_button_set_value(GTK_SPIN_BUTTON(lib->overlay_id),
469                             dt_conf_get_int("plugins/lighttable/live_view/overlay_imgid"));
470   gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), label, TRUE, TRUE, 0);
471   gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), lib->overlay_id, TRUE, TRUE, 0);
472   gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_id_box, TRUE, TRUE, 0);
473   gtk_widget_show(lib->overlay_id);
474   gtk_widget_show(label);
475 
476   lib->overlay_mode = dt_bauhaus_combobox_new(NULL);
477   dt_bauhaus_widget_set_label(lib->overlay_mode, NULL, N_("overlay mode"));
478   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "normal"));
479   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "xor"));
480   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "add"));
481   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "saturate"));
482   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "multiply"));
483   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "screen"));
484   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "overlay"));
485   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "darken"));
486   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "lighten"));
487   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color dodge"));
488   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color burn"));
489   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "hard light"));
490   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "soft light"));
491   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "difference"));
492   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "exclusion"));
493   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL hue"));
494   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL saturation"));
495   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL color"));
496   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL luminosity"));
497   gtk_widget_set_tooltip_text(lib->overlay_mode, _("mode of the overlay"));
498   dt_bauhaus_combobox_set(lib->overlay_mode, dt_conf_get_int("plugins/lighttable/live_view/overlay_mode"));
499   g_signal_connect(G_OBJECT(lib->overlay_mode), "value-changed", G_CALLBACK(_overlay_mode_changed), lib);
500   gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_mode, TRUE, TRUE, 0);
501 
502   lib->overlay_splitline = dt_bauhaus_combobox_new(NULL);
503   dt_bauhaus_widget_set_label(lib->overlay_splitline, NULL, N_("split line"));
504   dt_bauhaus_combobox_add(lib->overlay_splitline, _("off"));
505   dt_bauhaus_combobox_add(lib->overlay_splitline, _("on"));
506   gtk_widget_set_tooltip_text(lib->overlay_splitline, _("only draw part of the overlay"));
507   dt_bauhaus_combobox_set(lib->overlay_splitline, dt_conf_get_int("plugins/lighttable/live_view/splitline"));
508   g_signal_connect(G_OBJECT(lib->overlay_splitline), "value-changed", G_CALLBACK(_overlay_splitline_changed),
509                    lib);
510   gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_splitline, TRUE, TRUE, 0);
511 
512   gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE);
513   gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE);
514   gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE);
515 
516   gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_mode), TRUE);
517   gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_id_box), TRUE);
518   gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_splitline), TRUE);
519 
520   guides_presets_set_visibility(lib, 0);
521 }
522 
gui_cleanup(dt_lib_module_t * self)523 void gui_cleanup(dt_lib_module_t *self)
524 {
525   // dt_lib_live_view_t *lib = self->data;
526 
527   // g_list_free(lib->guides_widgets_list);
528   // INTENTIONAL. it's supposed to be leaky until lua is fixed.
529 
530   free(self->data);
531   self->data = NULL;
532 }
533 
view_enter(struct dt_lib_module_t * self,struct dt_view_t * old_view,struct dt_view_t * new_view)534 void view_enter(struct dt_lib_module_t *self,struct dt_view_t *old_view,struct dt_view_t *new_view)
535 {
536   // disable buttons that won't work with this camera
537   // TODO: initialize tethering mode outside of libs/camera.s so we can use darktable.camctl->active_camera
538   // here
539   const dt_lib_live_view_t *lib = self->data;
540   const dt_camera_t *cam = darktable.camctl->active_camera;
541   if(cam == NULL) cam = darktable.camctl->wanted_camera;
542 
543   const gboolean sensitive = cam && cam->can_live_view_advanced;
544 
545   gtk_widget_set_sensitive(lib->live_view_zoom, sensitive);
546   gtk_widget_set_sensitive(lib->focus_in_big, sensitive);
547   gtk_widget_set_sensitive(lib->focus_in_small, sensitive);
548   gtk_widget_set_sensitive(lib->focus_out_big, sensitive);
549   gtk_widget_set_sensitive(lib->focus_out_small, sensitive);
550 }
551 
view_leave(struct dt_lib_module_t * self,struct dt_view_t * old_view,struct dt_view_t * new_view)552 void view_leave(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view)
553 {
554   const dt_lib_live_view_t *lib = self->data;
555 
556   // there's no code to automatically restart live view when entering
557   // the view, and besides the user may not want to jump right back
558   // into live view if they've been out of tethering view doing other
559   // things
560   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(lib->live_view)) == TRUE)
561   {
562     dt_camctl_camera_stop_live_view(darktable.camctl);
563     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lib->live_view), FALSE);
564   }
565 }
566 
567 // TODO: find out where the zoom window is and draw overlay + grid accordingly
568 #define MARGIN 20
569 #define BAR_HEIGHT 18 /* see libs/camera.c */
gui_post_expose(dt_lib_module_t * self,cairo_t * cr,int32_t width,int32_t height,int32_t pointerx,int32_t pointery)570 void gui_post_expose(dt_lib_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx,
571                      int32_t pointery)
572 {
573   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
574   dt_lib_live_view_t *lib = self->data;
575 
576   if(cam->is_live_viewing == FALSE || cam->live_view_zoom == TRUE) return;
577 
578   dt_pthread_mutex_lock(&cam->live_view_buffer_mutex);
579   if(!cam->live_view_buffer)
580   {
581     dt_pthread_mutex_unlock(&cam->live_view_buffer_mutex);
582     return;
583   }
584   const double w = width - (MARGIN * 2.0f);
585   const double h = height - (MARGIN * 2.0f) - BAR_HEIGHT;
586   gint pw = cam->live_view_width;
587   gint ph = cam->live_view_height;
588   lib->overlay_x0 = lib->overlay_x1 = lib->overlay_y0 = lib->overlay_y1 = 0.0;
589 
590   const gboolean use_splitline = (dt_bauhaus_combobox_get(lib->overlay_splitline) == 1);
591 
592   // OVERLAY
593   int imgid = 0;
594   switch(dt_bauhaus_combobox_get(lib->overlay))
595   {
596     case OVERLAY_SELECTED:
597       imgid = dt_view_tethering_get_selected_imgid(darktable.view_manager);
598       break;
599     case OVERLAY_ID:
600       imgid = lib->imgid;
601       break;
602   }
603   if(imgid > 0)
604   {
605     cairo_save(cr);
606     const dt_image_t *img = dt_image_cache_testget(darktable.image_cache, imgid, 'r');
607     // if the user points at this image, we really want it:
608     if(!img) img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
609 
610     const float imgwd = 0.97f;
611     dt_mipmap_buffer_t buf;
612     dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, imgwd * w, imgwd * h);
613     dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, mip, 0, 'r');
614 
615     float scale = 1.0;
616     cairo_surface_t *surface = NULL;
617     if(buf.buf)
618     {
619       const int32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, buf.width);
620       surface = cairo_image_surface_create_for_data(buf.buf, CAIRO_FORMAT_RGB24, buf.width,
621                                                     buf.height, stride);
622       scale = fminf(fminf(w, pw) / (float)buf.width, fminf(h, ph) / (float)buf.height);
623     }
624 
625     // draw centered and fitted:
626     cairo_translate(cr, width / 2.0, (height + BAR_HEIGHT) / 2.0f);
627     cairo_scale(cr, scale, scale);
628 
629     if(buf.buf)
630     {
631       cairo_translate(cr, -.5f * buf.width, -.5f * buf.height);
632 
633       if(use_splitline)
634       {
635         double x0, y0, x1, y1;
636         switch(lib->splitline_rotation)
637         {
638           case 0:
639             x0 = 0.0;
640             y0 = 0.0;
641             x1 = buf.width * lib->splitline_x;
642             y1 = buf.height;
643             break;
644           case 1:
645             x0 = 0.0;
646             y0 = 0.0;
647             x1 = buf.width;
648             y1 = buf.height * lib->splitline_y;
649             break;
650           case 2:
651             x0 = buf.width * lib->splitline_x;
652             y0 = 0.0;
653             x1 = buf.width;
654             y1 = buf.height;
655             break;
656           case 3:
657             x0 = 0.0;
658             y0 = buf.height * lib->splitline_y;
659             x1 = buf.width;
660             y1 = buf.height;
661             break;
662           default:
663             fprintf(stderr, "OMFG, the world will collapse, this shouldn't be reachable!\n");
664             dt_pthread_mutex_unlock(&cam->live_view_buffer_mutex);
665             return;
666         }
667 
668         cairo_rectangle(cr, x0, y0, x1, y1);
669         cairo_clip(cr);
670       }
671 
672       cairo_set_source_surface(cr, surface, 0, 0);
673       // set filter no nearest:
674       // in skull mode, we want to see big pixels.
675       // in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel.
676       // in between, filtering just makes stuff go unsharp.
677       if((buf.width <= 8 && buf.height <= 8) || fabsf(scale - 1.0f) < 0.01f)
678         cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
679       cairo_rectangle(cr, 0, 0, buf.width, buf.height);
680       const int overlay_modes_index = dt_bauhaus_combobox_get(lib->overlay_mode);
681       if(overlay_modes_index >= 0)
682       {
683         const cairo_operator_t mode = _overlay_modes[overlay_modes_index];
684         cairo_set_operator(cr, mode);
685       }
686       cairo_fill(cr);
687       cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
688       cairo_surface_destroy(surface);
689     }
690     cairo_restore(cr);
691     if(buf.buf) dt_mipmap_cache_release(darktable.mipmap_cache, &buf);
692     if(img) dt_image_cache_read_release(darktable.image_cache, img);
693 
694     // ON CANVAS CONTROLS
695     if(use_splitline)
696     {
697       scale = fminf(1.0, fminf(w / pw, h / ph));
698 
699       // image coordinates
700       lib->overlay_x0 = 0.5 * (width - pw * scale);
701       lib->overlay_y0 = 0.5 * (height - ph * scale + BAR_HEIGHT);
702       lib->overlay_x1 = lib->overlay_x0 + pw * scale;
703       lib->overlay_y1 = lib->overlay_y0 + ph * scale;
704 
705       // splitline position to absolute coords:
706       const double sl_x = lib->overlay_x0 + lib->splitline_x * pw * scale;
707       const double sl_y = lib->overlay_y0 + lib->splitline_y * ph * scale;
708 
709       int x0 = sl_x, y0 = 0.0, x1 = x0, y1 = height;
710       if(lib->splitline_rotation % 2 != 0)
711       {
712         x0 = 0.0;
713         y0 = sl_y;
714         x1 = width;
715         y1 = y0;
716       }
717       const gboolean mouse_over_control = (lib->splitline_rotation % 2 == 0)
718         ? (fabs(sl_x - pointerx) < 5)
719         : (fabs(sl_y - pointery) < 5);
720 
721       cairo_save(cr);
722       cairo_set_source_rgb(cr, .7, .7, .7);
723       cairo_set_line_width(cr, (mouse_over_control ? 2.0 : 0.5));
724 
725       cairo_move_to(cr, x0, y0);
726       cairo_line_to(cr, x1, y1);
727       cairo_stroke(cr);
728 
729       /* if mouse over control lets draw center rotate control, hide if split is dragged */
730       if(!lib->splitline_dragging && mouse_over_control)
731       {
732         cairo_set_line_width(cr, 0.5);
733         const double s = width * HANDLE_SIZE;
734         dtgtk_cairo_paint_refresh(cr, sl_x - (s * 0.5), sl_y - (s * 0.5), s, s, 1, NULL);
735       }
736 
737       cairo_restore(cr);
738     }
739   }
740 
741   // GUIDES
742   float scale;
743   if(cam->live_view_rotation % 2 == 0)
744     scale = fminf(w / pw, h / ph);
745   else
746   {
747     const gint tmp = pw;
748     pw = ph;
749     ph = tmp;
750 
751     scale = fminf(w / ph, h / pw);
752   }
753 
754   // ensure some sanity on the scale factor
755   scale = fminf(10.0, scale);
756 
757   const double sw = scale * pw;
758   const double sh = scale * ph;
759 
760   // draw guides
761   const int guide_flip = dt_bauhaus_combobox_get(lib->flip_guides);
762   const double left = (width - sw) * 0.5;
763   const double top = (height + BAR_HEIGHT - sh) * 0.5;
764 
765   const double dashes = 5.0;
766 
767   cairo_save(cr);
768   cairo_rectangle(cr, left, top, sw, sh);
769   cairo_clip(cr);
770   cairo_set_dash(cr, &dashes, 1, 0);
771 
772   // Move coordinates to local center selection.
773   cairo_translate(cr, (sw / 2 + left), (sh / 2 + top));
774 
775   // Flip horizontal.
776   if(guide_flip & FLAG_FLIP_HORIZONTAL) cairo_scale(cr, -1, 1);
777   // Flip vertical.
778   if(guide_flip & FLAG_FLIP_VERTICAL) cairo_scale(cr, 1, -1);
779 
780   const int which = dt_bauhaus_combobox_get(lib->guide_selector);
781   dt_guides_t *guide = (dt_guides_t *)g_list_nth_data(darktable.guides, which - 1);
782   if(guide)
783   {
784     guide->draw(cr, -sw / 2, -sh / 2, sw, sh, 1.0, guide->user_data);
785     cairo_stroke_preserve(cr);
786     cairo_set_dash(cr, &dashes, 0, 0);
787     cairo_set_source_rgba(cr, .3, .3, .3, .8);
788     cairo_stroke(cr);
789   }
790   cairo_restore(cr);
791   dt_pthread_mutex_unlock(&cam->live_view_buffer_mutex);
792 }
793 
button_released(struct dt_lib_module_t * self,double x,double y,int which,uint32_t state)794 int button_released(struct dt_lib_module_t *self, double x, double y, int which, uint32_t state)
795 {
796   dt_lib_live_view_t *d = (dt_lib_live_view_t *)self->data;
797   if(d->splitline_dragging == TRUE)
798   {
799     d->splitline_dragging = FALSE;
800     return 1;
801   }
802   return 0;
803 }
804 
button_pressed(struct dt_lib_module_t * self,double x,double y,double pressure,int which,int type,uint32_t state)805 int button_pressed(struct dt_lib_module_t *self, double x, double y, double pressure, int which, int type,
806                    uint32_t state)
807 {
808   dt_lib_live_view_t *lib = (dt_lib_live_view_t *)self->data;
809   int result = 0;
810 
811   int imgid = 0;
812   switch(dt_bauhaus_combobox_get(lib->overlay))
813   {
814     case OVERLAY_SELECTED:
815       imgid = dt_view_tethering_get_selected_imgid(darktable.view_manager);
816       break;
817     case OVERLAY_ID:
818       imgid = lib->imgid;
819       break;
820   }
821 
822   if(imgid > 0 && dt_bauhaus_combobox_get(lib->overlay_splitline))
823   {
824     const double width = lib->overlay_x1 - lib->overlay_x0;
825     const double height = lib->overlay_y1 - lib->overlay_y0;
826 
827     // splitline position to absolute coords:
828     const double sl_x = lib->overlay_x0 + lib->splitline_x * width;
829     const double sl_y = lib->overlay_y0 + lib->splitline_y * height;
830 
831     const gboolean mouse_over_control = (lib->splitline_rotation % 2 == 0)
832       ? (fabs(sl_x - x) < 5)
833       : (fabs(sl_y - y) < 5);
834 
835     /* do the split rotating */
836     if(which == 1 && fabs(sl_x - x) < 7 && fabs(sl_y - y) < 7)
837     {
838       /* let's rotate */
839       lib->splitline_rotation = (lib->splitline_rotation + 1) % 4;
840 
841       dt_control_queue_redraw_center();
842       result = 1;
843     }
844     /* do the dragging !? */
845     else if(which == 1 && mouse_over_control)
846     {
847       lib->splitline_dragging = TRUE;
848       dt_control_queue_redraw_center();
849       result = 1;
850     }
851   }
852   return result;
853 }
854 
mouse_moved(dt_lib_module_t * self,double x,double y,double pressure,int which)855 int mouse_moved(dt_lib_module_t *self, double x, double y, double pressure, int which)
856 {
857   dt_lib_live_view_t *lib = (dt_lib_live_view_t *)self->data;
858   int result = 0;
859 
860   if(lib->splitline_dragging)
861   {
862     const double width = lib->overlay_x1 - lib->overlay_x0;
863     const double height = lib->overlay_y1 - lib->overlay_y0;
864 
865     // absolute coords to splitline position:
866     lib->splitline_x = CLAMPS((x - lib->overlay_x0) / width, 0.0, 1.0);
867     lib->splitline_y = CLAMPS((y - lib->overlay_y0) / height, 0.0, 1.0);
868 
869     result = 1;
870   }
871 
872   return result;
873 }
874 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
875 // vim: shiftwidth=2 expandtab tabstop=2 cindent
876 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
877