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 
80   GtkWidget *overlay, *overlay_id_box, *overlay_id, *overlay_mode, *overlay_splitline;
81 } dt_lib_live_view_t;
82 
overlay_changed(GtkWidget * combo,dt_lib_live_view_t * lib)83 static void overlay_changed(GtkWidget *combo, dt_lib_live_view_t *lib)
84 {
85   int which = dt_bauhaus_combobox_get(combo);
86   if(which == OVERLAY_NONE)
87   {
88     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE);
89     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE);
90   }
91   else
92   {
93     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), TRUE);
94     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), TRUE);
95   }
96 
97   if(which == OVERLAY_ID)
98     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), TRUE);
99   else
100     gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE);
101 }
102 
103 
name(dt_lib_module_t * self)104 const char *name(dt_lib_module_t *self)
105 {
106   return _("live view");
107 }
108 
views(dt_lib_module_t * self)109 const char **views(dt_lib_module_t *self)
110 {
111   static const char *v[] = {"tethering", NULL};
112   return v;
113 }
114 
container(dt_lib_module_t * self)115 uint32_t container(dt_lib_module_t *self)
116 {
117   return DT_UI_CONTAINER_PANEL_RIGHT_CENTER;
118 }
119 
120 
gui_reset(dt_lib_module_t * self)121 void gui_reset(dt_lib_module_t *self)
122 {
123 }
124 
position()125 int position()
126 {
127   return 998;
128 }
129 
init_key_accels(dt_lib_module_t * self)130 void init_key_accels(dt_lib_module_t *self)
131 {
132   dt_accel_register_lib(self, NC_("accel", "toggle live view"), GDK_KEY_v, 0);
133   dt_accel_register_lib(self, NC_("accel", "zoom live view"), GDK_KEY_w, 0);
134   dt_accel_register_lib(self, NC_("accel", "rotate 90 degrees CCW"), 0, 0);
135   dt_accel_register_lib(self, NC_("accel", "rotate 90 degrees CW"), 0, 0);
136   dt_accel_register_lib(self, NC_("accel", "flip horizontally"), 0, 0);
137   dt_accel_register_lib(self, NC_("accel", "move focus point in (big steps)"), 0, 0);
138   dt_accel_register_lib(self, NC_("accel", "move focus point in (small steps)"), 0, 0);
139   dt_accel_register_lib(self, NC_("accel", "move focus point out (small steps)"), 0, 0);
140   dt_accel_register_lib(self, NC_("accel", "move focus point out (big steps)"), 0, 0);
141 }
142 
connect_key_accels(dt_lib_module_t * self)143 void connect_key_accels(dt_lib_module_t *self)
144 {
145   dt_lib_live_view_t *lib = (dt_lib_live_view_t *)self->data;
146 
147   dt_accel_connect_button_lib(self, "toggle live view", GTK_WIDGET(lib->live_view));
148   dt_accel_connect_button_lib(self, "zoom live view", GTK_WIDGET(lib->live_view_zoom));
149   dt_accel_connect_button_lib(self, "rotate 90 degrees CCW", GTK_WIDGET(lib->rotate_ccw));
150   dt_accel_connect_button_lib(self, "rotate 90 degrees CW", GTK_WIDGET(lib->rotate_cw));
151   dt_accel_connect_button_lib(self, "flip horizontally", GTK_WIDGET(lib->flip));
152   dt_accel_connect_button_lib(self, "move focus point in (big steps)", GTK_WIDGET(lib->focus_in_big));
153   dt_accel_connect_button_lib(self, "move focus point in (small steps)", GTK_WIDGET(lib->focus_in_small));
154   dt_accel_connect_button_lib(self, "move focus point out (small steps)", GTK_WIDGET(lib->focus_out_small));
155   dt_accel_connect_button_lib(self, "move focus point out (big steps)", GTK_WIDGET(lib->focus_out_big));
156 }
157 
_rotate_ccw(GtkWidget * widget,gpointer user_data)158 static void _rotate_ccw(GtkWidget *widget, gpointer user_data)
159 {
160   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
161   cam->live_view_rotation = (cam->live_view_rotation + 1) % 4; // 0 -> 1 -> 2 -> 3 -> 0 -> ...
162 }
163 
_rotate_cw(GtkWidget * widget,gpointer user_data)164 static void _rotate_cw(GtkWidget *widget, gpointer user_data)
165 {
166   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
167   cam->live_view_rotation = (cam->live_view_rotation + 3) % 4; // 0 -> 3 -> 2 -> 1 -> 0 -> ...
168 }
169 
170 // Congratulations to Simon for being the first one recognizing live view in a screen shot ^^
_toggle_live_view_clicked(GtkWidget * widget,gpointer user_data)171 static void _toggle_live_view_clicked(GtkWidget *widget, gpointer user_data)
172 {
173   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) == TRUE)
174   {
175     if(dt_camctl_camera_start_live_view(darktable.camctl) == FALSE)
176       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
177   }
178   else
179   {
180     dt_camctl_camera_stop_live_view(darktable.camctl);
181   }
182 }
183 
184 // TODO: using a toggle button would be better, but this setting can also be changed by right clicking on the
185 // canvas (src/views/capture.c).
186 //       maybe using a signal would work? i have no idea.
_zoom_live_view_clicked(GtkWidget * widget,gpointer user_data)187 static void _zoom_live_view_clicked(GtkWidget *widget, gpointer user_data)
188 {
189   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
190   if(cam->is_live_viewing)
191   {
192     cam->live_view_zoom = !cam->live_view_zoom;
193     if(cam->live_view_zoom == TRUE)
194       dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "5");
195     else
196       dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "1");
197   }
198 }
199 
_auto_focus_button_clicked(GtkWidget * widget,gpointer user_data)200 static void _auto_focus_button_clicked(GtkWidget *widget, gpointer user_data)
201 {
202   const char *property = "autofocusdrive";
203   CameraWidgetType property_type;
204   if(dt_camctl_camera_get_property_type(darktable.camctl, NULL, property, &property_type))
205   {
206     dt_print(DT_DEBUG_CAMCTL, "[camera control] unable to get property type for %s\n", property);
207   }
208   else
209   {
210     if(property_type == GP_WIDGET_TOGGLE)
211     {
212       dt_camctl_camera_set_property_toggle(darktable.camctl, NULL, property);
213     }
214     else
215     {
216       // TODO evaluate if this is the right thing to do in default scenario
217       dt_print(DT_DEBUG_CAMCTL, "[camera control] unable to set %s for property type %d\n", property, property_type);
218     }
219   }
220 }
221 
_focus_button_clicked(GtkWidget * widget,gpointer user_data)222 static void _focus_button_clicked(GtkWidget *widget, gpointer user_data)
223 {
224   int focus = GPOINTER_TO_INT(user_data);
225   CameraWidgetType property_type;
226   if(dt_camctl_camera_get_property_type(darktable.camctl, NULL, "manualfocusdrive", &property_type))
227   {
228     // default to avoid breaking backwards compatibility
229     // note that this might not work on non-Canon EOS cameras
230     dt_camctl_camera_set_property_choice(darktable.camctl, NULL, "manualfocusdrive", focus);
231   }
232   else
233   {
234     // we need to check the property type here because of a peculiar difference between the property type that gphoto2
235     // supports for Canon EOS and Nikon systems. In particular, if you have a Canon, expect a TOGGLE or RADIO.
236     // If you have a Nikon, expect a RANGE.
237     switch(property_type)
238     {
239       case GP_WIDGET_RANGE:
240       {
241         float focus_amount;
242         switch(focus)
243         {
244           case DT_FOCUS_NEARER:
245             focus_amount = 250;
246             break;
247           case DT_FOCUS_NEAR:
248             focus_amount = 50;
249             break;
250           case DT_FOCUS_FAR:
251             focus_amount = -50;
252             break;
253           case DT_FOCUS_FARTHER:
254             focus_amount = -250;
255             break;
256           default:
257             focus_amount = 0;
258         }
259         dt_camctl_camera_set_property_float(darktable.camctl, NULL, "manualfocusdrive", focus_amount);
260         break;
261       }
262       case GP_WIDGET_TOGGLE | GP_WIDGET_RADIO:
263         dt_camctl_camera_set_property_choice(darktable.camctl, NULL, "manualfocusdrive", focus);
264         break;
265       default:
266         // TODO evaluate if this is the right thing to do in default scenario
267         dt_print(DT_DEBUG_CAMCTL, "[camera control] unable to set manualfocusdrive for property type %d", property_type);
268         break;
269     }
270   }
271 }
272 
_toggle_flip_clicked(GtkWidget * widget,gpointer user_data)273 static void _toggle_flip_clicked(GtkWidget *widget, gpointer user_data)
274 {
275   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
276   cam->live_view_flip = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
277 }
278 
_overlay_id_changed(GtkWidget * widget,gpointer user_data)279 static void _overlay_id_changed(GtkWidget *widget, gpointer user_data)
280 {
281   dt_lib_live_view_t *lib = (dt_lib_live_view_t *)user_data;
282   lib->imgid = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
283   dt_conf_set_int("plugins/lighttable/live_view/overlay_imgid", lib->imgid);
284 }
285 
_overlay_mode_changed(GtkWidget * combo,gpointer user_data)286 static void _overlay_mode_changed(GtkWidget *combo, gpointer user_data)
287 {
288   dt_conf_set_int("plugins/lighttable/live_view/overlay_mode", dt_bauhaus_combobox_get(combo));
289 }
290 
_overlay_splitline_changed(GtkWidget * combo,gpointer user_data)291 static void _overlay_splitline_changed(GtkWidget *combo, gpointer user_data)
292 {
293   dt_conf_set_int("plugins/lighttable/live_view/splitline", dt_bauhaus_combobox_get(combo));
294 }
295 
gui_init(dt_lib_module_t * self)296 void gui_init(dt_lib_module_t *self)
297 {
298   self->data = calloc(1, sizeof(dt_lib_live_view_t));
299 
300   // Setup lib data
301   dt_lib_live_view_t *lib = self->data;
302   lib->splitline_x = lib->splitline_y = 0.5;
303 
304   // Setup gui
305   self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
306   dt_gui_add_help_link(self->widget, dt_get_help_url("tethering_live_view"));
307   GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
308   gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0);
309   lib->live_view = dtgtk_togglebutton_new(dtgtk_cairo_paint_eye, CPF_STYLE_FLAT, NULL);
310   lib->live_view_zoom = dtgtk_button_new(
311       dtgtk_cairo_paint_zoom, CPF_STYLE_FLAT, NULL); // TODO: see _zoom_live_view_clicked
312   lib->rotate_ccw = dtgtk_button_new(dtgtk_cairo_paint_refresh, CPF_STYLE_FLAT, NULL);
313   lib->rotate_cw = dtgtk_button_new(dtgtk_cairo_paint_refresh,
314                                     CPF_STYLE_FLAT | CPF_DIRECTION_UP, NULL);
315   lib->flip = dtgtk_togglebutton_new(dtgtk_cairo_paint_flip,
316                                      CPF_STYLE_FLAT | CPF_DIRECTION_UP, NULL);
317 
318   gtk_box_pack_start(GTK_BOX(box), lib->live_view, TRUE, TRUE, 0);
319   gtk_box_pack_start(GTK_BOX(box), lib->live_view_zoom, TRUE, TRUE, 0);
320   gtk_box_pack_start(GTK_BOX(box), lib->rotate_ccw, TRUE, TRUE, 0);
321   gtk_box_pack_start(GTK_BOX(box), lib->rotate_cw, TRUE, TRUE, 0);
322   gtk_box_pack_start(GTK_BOX(box), lib->flip, TRUE, TRUE, 0);
323 
324   gtk_widget_set_tooltip_text(lib->live_view, _("toggle live view"));
325   gtk_widget_set_tooltip_text(lib->live_view_zoom, _("zoom live view"));
326   gtk_widget_set_tooltip_text(lib->rotate_ccw, _("rotate 90 degrees ccw"));
327   gtk_widget_set_tooltip_text(lib->rotate_cw, _("rotate 90 degrees cw"));
328   gtk_widget_set_tooltip_text(lib->flip, _("flip live view horizontally"));
329 
330   g_signal_connect(G_OBJECT(lib->live_view), "clicked", G_CALLBACK(_toggle_live_view_clicked), lib);
331   g_signal_connect(G_OBJECT(lib->live_view_zoom), "clicked", G_CALLBACK(_zoom_live_view_clicked), lib);
332   g_signal_connect(G_OBJECT(lib->rotate_ccw), "clicked", G_CALLBACK(_rotate_ccw), lib);
333   g_signal_connect(G_OBJECT(lib->rotate_cw), "clicked", G_CALLBACK(_rotate_cw), lib);
334   g_signal_connect(G_OBJECT(lib->flip), "clicked", G_CALLBACK(_toggle_flip_clicked), lib);
335 
336   // focus buttons
337   box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
338   gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0);
339   lib->focus_in_big = dtgtk_button_new(dtgtk_cairo_paint_solid_triangle,
340                                        CPF_STYLE_FLAT | CPF_DIRECTION_LEFT, NULL);
341   lib->focus_in_small
342       = dtgtk_button_new(dtgtk_cairo_paint_arrow, CPF_STYLE_FLAT
343                                                   | CPF_DIRECTION_LEFT, NULL); // TODO icon not centered
344   lib->auto_focus = dtgtk_button_new(dtgtk_cairo_paint_lock, CPF_STYLE_FLAT, NULL);
345   lib->focus_out_small = dtgtk_button_new(dtgtk_cairo_paint_arrow, CPF_STYLE_FLAT
346                                                                    | CPF_DIRECTION_RIGHT, NULL); // TODO same here
347   lib->focus_out_big = dtgtk_button_new(dtgtk_cairo_paint_solid_triangle,
348                                         CPF_STYLE_FLAT | CPF_DIRECTION_RIGHT, NULL);
349 
350   gtk_box_pack_start(GTK_BOX(box), lib->focus_in_big, TRUE, TRUE, 0);
351   gtk_box_pack_start(GTK_BOX(box), lib->focus_in_small, TRUE, TRUE, 0);
352   gtk_box_pack_start(GTK_BOX(box), lib->auto_focus, TRUE, TRUE, 0);
353   gtk_box_pack_start(GTK_BOX(box), lib->focus_out_small, TRUE, TRUE, 0);
354   gtk_box_pack_start(GTK_BOX(box), lib->focus_out_big, TRUE, TRUE, 0);
355 
356   gtk_widget_set_tooltip_text(lib->focus_in_big, _("move focus point in (big steps)"));
357   gtk_widget_set_tooltip_text(lib->focus_in_small, _("move focus point in (small steps)"));
358   gtk_widget_set_tooltip_text(lib->auto_focus, _("run autofocus"));
359   gtk_widget_set_tooltip_text(lib->focus_out_small, _("move focus point out (small steps)"));
360   gtk_widget_set_tooltip_text(lib->focus_out_big, _("move focus point out (big steps)"));
361 
362 
363   g_signal_connect(G_OBJECT(lib->focus_in_big), "clicked",
364                    G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(DT_FOCUS_NEARER));
365   g_signal_connect(G_OBJECT(lib->focus_in_small), "clicked",
366                    G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(DT_FOCUS_NEAR));
367   g_signal_connect(G_OBJECT(lib->auto_focus), "clicked",
368                    G_CALLBACK(_auto_focus_button_clicked), GINT_TO_POINTER(1));
369   g_signal_connect(G_OBJECT(lib->focus_out_small), "clicked",
370                    G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(DT_FOCUS_FAR));
371   g_signal_connect(G_OBJECT(lib->focus_out_big), "clicked",
372                    G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(DT_FOCUS_FARTHER));
373 
374   lib->overlay = dt_bauhaus_combobox_new_action(DT_ACTION(self));
375 
376   dt_bauhaus_widget_set_label(lib->overlay, NULL, N_("overlay"));
377   dt_bauhaus_combobox_add(lib->overlay, _("none"));
378   dt_bauhaus_combobox_add(lib->overlay, _("selected image"));
379   dt_bauhaus_combobox_add(lib->overlay, _("id"));
380   gtk_widget_set_tooltip_text(lib->overlay, _("overlay another image over the live view"));
381   g_signal_connect(G_OBJECT(lib->overlay), "value-changed", G_CALLBACK(overlay_changed), lib);
382   gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay, TRUE, TRUE, 0);
383 
384   lib->overlay_id_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
385   GtkWidget *label = gtk_label_new(_("image id"));
386   gtk_widget_set_halign(label, GTK_ALIGN_START);
387   lib->overlay_id = gtk_spin_button_new_with_range(0, 1000000000, 1);
388   gtk_spin_button_set_digits(GTK_SPIN_BUTTON(lib->overlay_id), 0);
389   gtk_widget_set_tooltip_text(lib->overlay_id, _("enter image id of the overlay manually"));
390   g_signal_connect(G_OBJECT(lib->overlay_id), "value-changed", G_CALLBACK(_overlay_id_changed), lib);
391   gtk_spin_button_set_value(GTK_SPIN_BUTTON(lib->overlay_id),
392                             dt_conf_get_int("plugins/lighttable/live_view/overlay_imgid"));
393   gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), label, TRUE, TRUE, 0);
394   gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), lib->overlay_id, TRUE, TRUE, 0);
395   gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_id_box, TRUE, TRUE, 0);
396   gtk_widget_show(lib->overlay_id);
397   gtk_widget_show(label);
398 
399   lib->overlay_mode = dt_bauhaus_combobox_new_action(DT_ACTION(self));
400   dt_bauhaus_widget_set_label(lib->overlay_mode, NULL, N_("overlay mode"));
401   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "normal"));
402   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "xor"));
403   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "add"));
404   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "saturate"));
405   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "multiply"));
406   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "screen"));
407   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "overlay"));
408   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "darken"));
409   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "lighten"));
410   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color dodge"));
411   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color burn"));
412   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "hard light"));
413   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "soft light"));
414   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "difference"));
415   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "exclusion"));
416   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL hue"));
417   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL saturation"));
418   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL color"));
419   dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL luminosity"));
420   gtk_widget_set_tooltip_text(lib->overlay_mode, _("mode of the overlay"));
421   dt_bauhaus_combobox_set(lib->overlay_mode, dt_conf_get_int("plugins/lighttable/live_view/overlay_mode"));
422   g_signal_connect(G_OBJECT(lib->overlay_mode), "value-changed", G_CALLBACK(_overlay_mode_changed), lib);
423   gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_mode, TRUE, TRUE, 0);
424 
425   lib->overlay_splitline = dt_bauhaus_combobox_new_action(DT_ACTION(self));
426   dt_bauhaus_widget_set_label(lib->overlay_splitline, NULL, N_("split line"));
427   dt_bauhaus_combobox_add(lib->overlay_splitline, _("off"));
428   dt_bauhaus_combobox_add(lib->overlay_splitline, _("on"));
429   gtk_widget_set_tooltip_text(lib->overlay_splitline, _("only draw part of the overlay"));
430   dt_bauhaus_combobox_set(lib->overlay_splitline, dt_conf_get_int("plugins/lighttable/live_view/splitline"));
431   g_signal_connect(G_OBJECT(lib->overlay_splitline), "value-changed", G_CALLBACK(_overlay_splitline_changed),
432                    lib);
433   gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_splitline, TRUE, TRUE, 0);
434 
435   gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE);
436   gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE);
437   gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE);
438 
439   gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_mode), TRUE);
440   gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_id_box), TRUE);
441   gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_splitline), TRUE);
442 }
443 
gui_cleanup(dt_lib_module_t * self)444 void gui_cleanup(dt_lib_module_t *self)
445 {
446   // dt_lib_live_view_t *lib = self->data;
447 
448   // g_list_free(lib->guides_widgets_list);
449   // INTENTIONAL. it's supposed to be leaky until lua is fixed.
450 
451   free(self->data);
452   self->data = NULL;
453 }
454 
view_enter(struct dt_lib_module_t * self,struct dt_view_t * old_view,struct dt_view_t * new_view)455 void view_enter(struct dt_lib_module_t *self,struct dt_view_t *old_view,struct dt_view_t *new_view)
456 {
457   // disable buttons that won't work with this camera
458   // TODO: initialize tethering mode outside of libs/camera.s so we can use darktable.camctl->active_camera
459   // here
460   const dt_lib_live_view_t *lib = self->data;
461   const dt_camera_t *cam = darktable.camctl->active_camera;
462   if(cam == NULL) cam = darktable.camctl->wanted_camera;
463 
464   const gboolean sensitive = cam && cam->can_live_view_advanced;
465 
466   gtk_widget_set_sensitive(lib->live_view_zoom, sensitive);
467   gtk_widget_set_sensitive(lib->focus_in_big, sensitive);
468   gtk_widget_set_sensitive(lib->focus_in_small, sensitive);
469   gtk_widget_set_sensitive(lib->focus_out_big, sensitive);
470   gtk_widget_set_sensitive(lib->focus_out_small, sensitive);
471 }
472 
view_leave(struct dt_lib_module_t * self,struct dt_view_t * old_view,struct dt_view_t * new_view)473 void view_leave(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view)
474 {
475   const dt_lib_live_view_t *lib = self->data;
476 
477   // there's no code to automatically restart live view when entering
478   // the view, and besides the user may not want to jump right back
479   // into live view if they've been out of tethering view doing other
480   // things
481   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(lib->live_view)) == TRUE)
482   {
483     dt_camctl_camera_stop_live_view(darktable.camctl);
484     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lib->live_view), FALSE);
485   }
486 }
487 
488 // TODO: find out where the zoom window is and draw overlay + grid accordingly
489 #define MARGIN 20
490 #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)491 void gui_post_expose(dt_lib_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx,
492                      int32_t pointery)
493 {
494   dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
495   dt_lib_live_view_t *lib = self->data;
496 
497   if(cam->is_live_viewing == FALSE || cam->live_view_zoom == TRUE) return;
498 
499   dt_pthread_mutex_lock(&cam->live_view_buffer_mutex);
500   if(!cam->live_view_buffer)
501   {
502     dt_pthread_mutex_unlock(&cam->live_view_buffer_mutex);
503     return;
504   }
505   const double w = width - (MARGIN * 2.0f);
506   const double h = height - (MARGIN * 2.0f) - BAR_HEIGHT;
507   gint pw = cam->live_view_width;
508   gint ph = cam->live_view_height;
509   lib->overlay_x0 = lib->overlay_x1 = lib->overlay_y0 = lib->overlay_y1 = 0.0;
510 
511   const gboolean use_splitline = (dt_bauhaus_combobox_get(lib->overlay_splitline) == 1);
512 
513   // OVERLAY
514   int imgid = 0;
515   switch(dt_bauhaus_combobox_get(lib->overlay))
516   {
517     case OVERLAY_SELECTED:
518       imgid = dt_view_tethering_get_selected_imgid(darktable.view_manager);
519       break;
520     case OVERLAY_ID:
521       imgid = lib->imgid;
522       break;
523   }
524   if(imgid > 0)
525   {
526     cairo_save(cr);
527     const dt_image_t *img = dt_image_cache_testget(darktable.image_cache, imgid, 'r');
528     // if the user points at this image, we really want it:
529     if(!img) img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
530 
531     const float imgwd = 0.97f;
532     dt_mipmap_buffer_t buf;
533     dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, imgwd * w, imgwd * h);
534     dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, mip, 0, 'r');
535 
536     float scale = 1.0;
537     cairo_surface_t *surface = NULL;
538     if(buf.buf)
539     {
540       const int32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, buf.width);
541       surface = cairo_image_surface_create_for_data(buf.buf, CAIRO_FORMAT_RGB24, buf.width,
542                                                     buf.height, stride);
543       scale = fminf(fminf(w, pw) / (float)buf.width, fminf(h, ph) / (float)buf.height);
544     }
545 
546     // draw centered and fitted:
547     cairo_translate(cr, width / 2.0, (height + BAR_HEIGHT) / 2.0f);
548     cairo_scale(cr, scale, scale);
549 
550     if(buf.buf)
551     {
552       cairo_translate(cr, -.5f * buf.width, -.5f * buf.height);
553 
554       if(use_splitline)
555       {
556         double x0, y0, x1, y1;
557         switch(lib->splitline_rotation)
558         {
559           case 0:
560             x0 = 0.0;
561             y0 = 0.0;
562             x1 = buf.width * lib->splitline_x;
563             y1 = buf.height;
564             break;
565           case 1:
566             x0 = 0.0;
567             y0 = 0.0;
568             x1 = buf.width;
569             y1 = buf.height * lib->splitline_y;
570             break;
571           case 2:
572             x0 = buf.width * lib->splitline_x;
573             y0 = 0.0;
574             x1 = buf.width;
575             y1 = buf.height;
576             break;
577           case 3:
578             x0 = 0.0;
579             y0 = buf.height * lib->splitline_y;
580             x1 = buf.width;
581             y1 = buf.height;
582             break;
583           default:
584             fprintf(stderr, "OMFG, the world will collapse, this shouldn't be reachable!\n");
585             dt_pthread_mutex_unlock(&cam->live_view_buffer_mutex);
586             return;
587         }
588 
589         cairo_rectangle(cr, x0, y0, x1, y1);
590         cairo_clip(cr);
591       }
592 
593       cairo_set_source_surface(cr, surface, 0, 0);
594       // set filter no nearest:
595       // in skull mode, we want to see big pixels.
596       // in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel.
597       // in between, filtering just makes stuff go unsharp.
598       if((buf.width <= 8 && buf.height <= 8) || fabsf(scale - 1.0f) < 0.01f)
599         cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
600       cairo_rectangle(cr, 0, 0, buf.width, buf.height);
601       const int overlay_modes_index = dt_bauhaus_combobox_get(lib->overlay_mode);
602       if(overlay_modes_index >= 0)
603       {
604         const cairo_operator_t mode = _overlay_modes[overlay_modes_index];
605         cairo_set_operator(cr, mode);
606       }
607       cairo_fill(cr);
608       cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
609       cairo_surface_destroy(surface);
610     }
611     cairo_restore(cr);
612     if(buf.buf) dt_mipmap_cache_release(darktable.mipmap_cache, &buf);
613     if(img) dt_image_cache_read_release(darktable.image_cache, img);
614 
615     // ON CANVAS CONTROLS
616     if(use_splitline)
617     {
618       scale = fminf(1.0, fminf(w / pw, h / ph));
619 
620       // image coordinates
621       lib->overlay_x0 = 0.5 * (width - pw * scale);
622       lib->overlay_y0 = 0.5 * (height - ph * scale + BAR_HEIGHT);
623       lib->overlay_x1 = lib->overlay_x0 + pw * scale;
624       lib->overlay_y1 = lib->overlay_y0 + ph * scale;
625 
626       // splitline position to absolute coords:
627       const double sl_x = lib->overlay_x0 + lib->splitline_x * pw * scale;
628       const double sl_y = lib->overlay_y0 + lib->splitline_y * ph * scale;
629 
630       int x0 = sl_x, y0 = 0.0, x1 = x0, y1 = height;
631       if(lib->splitline_rotation % 2 != 0)
632       {
633         x0 = 0.0;
634         y0 = sl_y;
635         x1 = width;
636         y1 = y0;
637       }
638       const gboolean mouse_over_control = (lib->splitline_rotation % 2 == 0)
639         ? (fabs(sl_x - pointerx) < 5)
640         : (fabs(sl_y - pointery) < 5);
641 
642       cairo_save(cr);
643       cairo_set_source_rgb(cr, .7, .7, .7);
644       cairo_set_line_width(cr, (mouse_over_control ? 2.0 : 0.5));
645 
646       cairo_move_to(cr, x0, y0);
647       cairo_line_to(cr, x1, y1);
648       cairo_stroke(cr);
649 
650       /* if mouse over control lets draw center rotate control, hide if split is dragged */
651       if(!lib->splitline_dragging && mouse_over_control)
652       {
653         cairo_set_line_width(cr, 0.5);
654         const double s = width * HANDLE_SIZE;
655         dtgtk_cairo_paint_refresh(cr, sl_x - (s * 0.5), sl_y - (s * 0.5), s, s, 1, NULL);
656       }
657 
658       cairo_restore(cr);
659     }
660   }
661 
662   // GUIDES
663   float scale;
664   if(cam->live_view_rotation % 2 == 0)
665     scale = fminf(w / pw, h / ph);
666   else
667   {
668     const gint tmp = pw;
669     pw = ph;
670     ph = tmp;
671 
672     scale = fminf(w / ph, h / pw);
673   }
674 
675   // ensure some sanity on the scale factor
676   scale = fminf(10.0, scale);
677 
678   const double sw = scale * pw;
679   const double sh = scale * ph;
680 
681   // draw guides
682   const double left = (width - sw) * 0.5;
683   const double top = (height + BAR_HEIGHT - sh) * 0.5;
684 
685   dt_guides_draw(cr, left, top, sw, sh, 1.0);
686 
687   dt_pthread_mutex_unlock(&cam->live_view_buffer_mutex);
688 }
689 
button_released(struct dt_lib_module_t * self,double x,double y,int which,uint32_t state)690 int button_released(struct dt_lib_module_t *self, double x, double y, int which, uint32_t state)
691 {
692   dt_lib_live_view_t *d = (dt_lib_live_view_t *)self->data;
693   if(d->splitline_dragging == TRUE)
694   {
695     d->splitline_dragging = FALSE;
696     return 1;
697   }
698   return 0;
699 }
700 
button_pressed(struct dt_lib_module_t * self,double x,double y,double pressure,int which,int type,uint32_t state)701 int button_pressed(struct dt_lib_module_t *self, double x, double y, double pressure, int which, int type,
702                    uint32_t state)
703 {
704   dt_lib_live_view_t *lib = (dt_lib_live_view_t *)self->data;
705   int result = 0;
706 
707   int imgid = 0;
708   switch(dt_bauhaus_combobox_get(lib->overlay))
709   {
710     case OVERLAY_SELECTED:
711       imgid = dt_view_tethering_get_selected_imgid(darktable.view_manager);
712       break;
713     case OVERLAY_ID:
714       imgid = lib->imgid;
715       break;
716   }
717 
718   if(imgid > 0 && dt_bauhaus_combobox_get(lib->overlay_splitline))
719   {
720     const double width = lib->overlay_x1 - lib->overlay_x0;
721     const double height = lib->overlay_y1 - lib->overlay_y0;
722 
723     // splitline position to absolute coords:
724     const double sl_x = lib->overlay_x0 + lib->splitline_x * width;
725     const double sl_y = lib->overlay_y0 + lib->splitline_y * height;
726 
727     const gboolean mouse_over_control = (lib->splitline_rotation % 2 == 0)
728       ? (fabs(sl_x - x) < 5)
729       : (fabs(sl_y - y) < 5);
730 
731     /* do the split rotating */
732     if(which == 1 && fabs(sl_x - x) < 7 && fabs(sl_y - y) < 7)
733     {
734       /* let's rotate */
735       lib->splitline_rotation = (lib->splitline_rotation + 1) % 4;
736 
737       dt_control_queue_redraw_center();
738       result = 1;
739     }
740     /* do the dragging !? */
741     else if(which == 1 && mouse_over_control)
742     {
743       lib->splitline_dragging = TRUE;
744       dt_control_queue_redraw_center();
745       result = 1;
746     }
747   }
748   return result;
749 }
750 
mouse_moved(dt_lib_module_t * self,double x,double y,double pressure,int which)751 int mouse_moved(dt_lib_module_t *self, double x, double y, double pressure, int which)
752 {
753   dt_lib_live_view_t *lib = (dt_lib_live_view_t *)self->data;
754   int result = 0;
755 
756   if(lib->splitline_dragging)
757   {
758     const double width = lib->overlay_x1 - lib->overlay_x0;
759     const double height = lib->overlay_y1 - lib->overlay_y0;
760 
761     // absolute coords to splitline position:
762     lib->splitline_x = CLAMPS((x - lib->overlay_x0) / width, 0.0, 1.0);
763     lib->splitline_y = CLAMPS((y - lib->overlay_y0) / height, 0.0, 1.0);
764 
765     result = 1;
766   }
767 
768   return result;
769 }
770 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
771 // vim: shiftwidth=2 expandtab tabstop=2 cindent
772 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
773