1 /*
2  *  Entangle: Tethered Camera Control & Capture
3  *
4  *  Copyright (C) 2009-2017 Daniel P. Berrange
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include <string.h>
22 #include <glib/gi18n.h>
23 #include <math.h>
24 
25 #include "entangle-debug.h"
26 #include "entangle-control-panel.h"
27 #include "entangle-control-button.h"
28 #include "entangle-control-choice.h"
29 #include "entangle-control-date.h"
30 #include "entangle-control-group.h"
31 #include "entangle-control-range.h"
32 #include "entangle-control-text.h"
33 #include "entangle-control-toggle.h"
34 
35 #define ENTANGLE_CONTROL_PANEL_GET_PRIVATE(obj)                         \
36     (G_TYPE_INSTANCE_GET_PRIVATE((obj), ENTANGLE_TYPE_CONTROL_PANEL, EntangleControlPanelPrivate))
37 
38 struct _EntangleControlPanelPrivate {
39     EntangleCameraPreferences *cameraPrefs;
40     EntangleCamera *camera;
41 
42     gulong sigCamera;
43     gboolean hasControls;
44     gboolean inUpdate;
45 
46     GtkWidget *grid;
47     gsize rows;
48 };
49 
50 G_DEFINE_TYPE(EntangleControlPanel, entangle_control_panel, GTK_TYPE_EXPANDER);
51 
52 enum {
53     PROP_O,
54     PROP_CAMERA,
55     PROP_CAMERA_PREFS,
56     PROP_HAS_CONTROLS,
57 };
58 
do_control_remove(GtkWidget * widget,gpointer data)59 static void do_control_remove(GtkWidget *widget,
60                               gpointer data)
61 {
62     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(data));
63 
64     EntangleControlPanel *panel = data;
65     EntangleControlPanelPrivate *priv = panel->priv;
66 
67     gtk_container_remove(GTK_CONTAINER(priv->grid), widget);
68 }
69 
70 
do_update_control_finish(GObject * src,GAsyncResult * res,gpointer data)71 static void do_update_control_finish(GObject *src,
72                                      GAsyncResult *res,
73                                      gpointer data)
74 {
75     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(data));
76 
77     GError *error = NULL;
78 
79     if (!entangle_camera_save_controls_finish(ENTANGLE_CAMERA(src), res, &error)) {
80         GtkWidget *msg = gtk_message_dialog_new(NULL,
81                                                 0,
82                                                 GTK_MESSAGE_ERROR,
83                                                 GTK_BUTTONS_OK,
84                                                 _("Camera control update failed"));
85         gtk_window_set_title(GTK_WINDOW(msg),
86                              _("Entangle: Camera control update failed"));
87         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(msg),
88                                                  "%s",
89                                                  error->message);
90         g_signal_connect_swapped(msg,
91                                  "response",
92                                  G_CALLBACK(gtk_widget_destroy),
93                                  msg);
94         gtk_widget_show_all(msg);
95         g_error_free(error);
96     }
97 }
98 
99 
do_refresh_control_entry_idle(gpointer data)100 static gboolean do_refresh_control_entry_idle(gpointer data)
101 {
102     GtkWidget *widget = GTK_WIDGET(data);
103     EntangleControlPanel *panel = g_object_get_data(G_OBJECT(widget), "panel");
104     GObject *control = g_object_get_data(G_OBJECT(widget), "control");
105     gchar *text;
106 
107     panel->priv->inUpdate = TRUE;
108     g_object_get(control, "value", &text, NULL);
109     ENTANGLE_DEBUG("Notified control entry '%s' ('%s') with '%s'",
110                    entangle_control_get_path(ENTANGLE_CONTROL(control)),
111                    entangle_control_get_label(ENTANGLE_CONTROL(control)),
112                    text);
113 
114     if (GTK_IS_LABEL(widget))
115         gtk_label_set_text(GTK_LABEL(widget), text);
116     else
117         gtk_entry_set_text(GTK_ENTRY(widget), text);
118     g_free(text);
119     panel->priv->inUpdate = FALSE;
120     return FALSE;
121 }
122 
do_refresh_control_entry(GObject * object G_GNUC_UNUSED,GParamSpec * pspec G_GNUC_UNUSED,gpointer data)123 static void do_refresh_control_entry(GObject *object G_GNUC_UNUSED,
124                                      GParamSpec *pspec G_GNUC_UNUSED,
125                                      gpointer data)
126 {
127     g_idle_add(do_refresh_control_entry_idle, data);
128 }
129 
do_update_control_entry(GtkWidget * widget,GdkEventFocus * ev G_GNUC_UNUSED,gpointer data)130 static void do_update_control_entry(GtkWidget *widget,
131                                     GdkEventFocus *ev G_GNUC_UNUSED,
132                                     gpointer data)
133 {
134     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(data));
135 
136     EntangleControlText *control = g_object_get_data(G_OBJECT(widget), "control");
137     EntangleControlPanel *panel = ENTANGLE_CONTROL_PANEL(data);
138     EntangleControlPanelPrivate *priv = panel->priv;
139     const char *text;
140 
141     if (panel->priv->inUpdate)
142         return;
143 
144     text = gtk_entry_get_text(GTK_ENTRY(widget));
145 
146     ENTANGLE_DEBUG("Updated control entry '%s' ('%s') with '%s'",
147                    entangle_control_get_path(ENTANGLE_CONTROL(control)),
148                    entangle_control_get_label(ENTANGLE_CONTROL(control)),
149                    text);
150     g_object_set(control, "value", text, NULL);
151 
152     entangle_camera_save_controls_async(priv->camera,
153                                         NULL,
154                                         do_update_control_finish,
155                                         panel);
156 }
157 
158 
do_refresh_control_range_idle(gpointer data)159 static gboolean do_refresh_control_range_idle(gpointer data)
160 {
161     GtkWidget *widget = GTK_WIDGET(data);
162     EntangleControlPanel *panel = g_object_get_data(G_OBJECT(widget), "panel");
163     GObject *control = g_object_get_data(G_OBJECT(widget), "control");
164     gfloat val;
165 
166     panel->priv->inUpdate = TRUE;
167     g_object_get(control, "value", &val, NULL);
168     ENTANGLE_DEBUG("Notified control range '%s' ('%s') with '%lf'",
169                    entangle_control_get_path(ENTANGLE_CONTROL(control)),
170                    entangle_control_get_label(ENTANGLE_CONTROL(control)),
171                    (double)val);
172 
173     if (GTK_IS_LABEL(widget)) {
174         gchar *text = g_strdup_printf("%0.02f", (double)val);
175         gtk_label_set_text(GTK_LABEL(widget), text);
176         g_free(text);
177     } else {
178         gtk_range_set_value(GTK_RANGE(widget), val);
179     }
180     panel->priv->inUpdate = FALSE;
181     return FALSE;
182 }
183 
184 
do_refresh_control_range(GObject * object G_GNUC_UNUSED,GParamSpec * pspec G_GNUC_UNUSED,gpointer data)185 static void do_refresh_control_range(GObject *object G_GNUC_UNUSED,
186                                      GParamSpec *pspec G_GNUC_UNUSED,
187                                      gpointer data)
188 {
189     g_idle_add(do_refresh_control_range_idle, data);
190 }
191 
192 
do_update_control_range(GtkRange * widget G_GNUC_UNUSED,GtkScrollType scroll G_GNUC_UNUSED,gdouble value,gpointer data)193 static void do_update_control_range(GtkRange *widget G_GNUC_UNUSED,
194                                     GtkScrollType scroll G_GNUC_UNUSED,
195                                     gdouble value,
196                                     gpointer data)
197 {
198     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(data));
199 
200     EntangleControlRange *control = g_object_get_data(G_OBJECT(widget), "control");
201     EntangleControlPanel *panel = ENTANGLE_CONTROL_PANEL(data);
202     EntangleControlPanelPrivate *priv = panel->priv;
203 
204     if (panel->priv->inUpdate)
205         return;
206 
207     ENTANGLE_DEBUG("Updated control range '%s' ('%s') with '%lf'",
208                    entangle_control_get_path(ENTANGLE_CONTROL(control)),
209                    entangle_control_get_label(ENTANGLE_CONTROL(control)),
210                    value);
211     g_object_set(control, "value", (double)value, NULL);
212 
213     entangle_camera_save_controls_async(priv->camera,
214                                         NULL,
215                                         do_update_control_finish,
216                                         panel);
217 }
218 
219 
do_refresh_control_combo_idle(gpointer data)220 static gboolean do_refresh_control_combo_idle(gpointer data)
221 {
222     GtkWidget *widget = GTK_WIDGET(data);
223     EntangleControlPanel *panel = g_object_get_data(G_OBJECT(widget), "panel");
224     GObject *control = g_object_get_data(G_OBJECT(widget), "control");
225     gchar *text;
226 
227     panel->priv->inUpdate = TRUE;
228     g_object_get(control, "value", &text, NULL);
229     ENTANGLE_DEBUG("Notified control combo '%s' ('%s') with '%s'",
230                    entangle_control_get_path(ENTANGLE_CONTROL(control)),
231                    entangle_control_get_label(ENTANGLE_CONTROL(control)),
232                    text);
233 
234     if (GTK_IS_LABEL(widget)) {
235         gtk_label_set_text(GTK_LABEL(widget), text);
236     } else {
237         GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)));
238         int active = 0;
239         gtk_list_store_clear(store);
240         for (int n = 0; n < entangle_control_choice_entry_count(ENTANGLE_CONTROL_CHOICE(control)); n++) {
241             GtkTreeIter iter;
242             if (g_strcmp0(text, entangle_control_choice_entry_get(ENTANGLE_CONTROL_CHOICE(control), n)) == 0)
243                 active = n;
244             gtk_list_store_append(store, &iter);
245             gtk_list_store_set(store, &iter, 0,
246                                entangle_control_choice_entry_get(ENTANGLE_CONTROL_CHOICE(control), n),
247                                -1);
248         }
249         gtk_combo_box_set_active(GTK_COMBO_BOX(widget), active);
250     }
251     g_free(text);
252     panel->priv->inUpdate = FALSE;
253 
254     return FALSE;
255 }
256 
257 
do_refresh_control_combo(GObject * object G_GNUC_UNUSED,GParamSpec * pspec G_GNUC_UNUSED,gpointer data)258 static void do_refresh_control_combo(GObject *object G_GNUC_UNUSED,
259                                      GParamSpec *pspec G_GNUC_UNUSED,
260                                      gpointer data)
261 {
262     g_idle_add(do_refresh_control_combo_idle, data);
263 }
264 
265 
do_update_control_combo(GtkComboBox * widget,gpointer data)266 static void do_update_control_combo(GtkComboBox *widget,
267                                     gpointer data)
268 {
269     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(data));
270 
271     EntangleControlChoice *control = g_object_get_data(G_OBJECT(widget), "control");
272     EntangleControlPanel *panel = ENTANGLE_CONTROL_PANEL(data);
273     EntangleControlPanelPrivate *priv = panel->priv;
274     GtkTreeIter iter;
275     char *text = NULL;
276     GtkTreeModel *model = gtk_combo_box_get_model(widget);
277 
278     if (panel->priv->inUpdate)
279         return;
280 
281     if (gtk_combo_box_get_active_iter(widget, &iter))
282         gtk_tree_model_get(model, &iter, 0, &text, -1);
283 
284     ENTANGLE_DEBUG("Updated control combo '%s' ('%s') with '%s'",
285                    entangle_control_get_path(ENTANGLE_CONTROL(control)),
286                    entangle_control_get_label(ENTANGLE_CONTROL(control)),
287                    text);
288     g_object_set(control, "value", text, NULL);
289 
290     g_free(text);
291 
292     entangle_camera_save_controls_async(priv->camera,
293                                         NULL,
294                                         do_update_control_finish,
295                                         panel);
296 }
297 
298 
do_refresh_control_toggle_idle(gpointer data)299 static gboolean do_refresh_control_toggle_idle(gpointer data)
300 {
301     GtkWidget *widget = GTK_WIDGET(data);
302     EntangleControlPanel *panel = g_object_get_data(G_OBJECT(widget), "panel");
303     GObject *control = g_object_get_data(G_OBJECT(widget), "control");
304     gboolean state;
305 
306     panel->priv->inUpdate = TRUE;
307     g_object_get(control, "value", &state, NULL);
308     ENTANGLE_DEBUG("Notified control toggle '%s' ('%s') with '%d'",
309                    entangle_control_get_path(ENTANGLE_CONTROL(control)),
310                    entangle_control_get_label(ENTANGLE_CONTROL(control)),
311                    state);
312 
313     if (GTK_IS_LABEL(widget))
314         gtk_label_set_text(GTK_LABEL(widget), state ? _("On") : _("Off"));
315     else
316         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
317                                      state);
318     panel->priv->inUpdate = FALSE;
319     return FALSE;
320 }
321 
322 
do_refresh_control_toggle(GObject * object G_GNUC_UNUSED,GParamSpec * pspec G_GNUC_UNUSED,gpointer data)323 static void do_refresh_control_toggle(GObject *object G_GNUC_UNUSED,
324                                       GParamSpec *pspec G_GNUC_UNUSED,
325                                       gpointer data)
326 {
327     g_idle_add(do_refresh_control_toggle_idle, data);
328 }
329 
330 
do_update_control_toggle(GtkToggleButton * widget,gpointer data)331 static void do_update_control_toggle(GtkToggleButton *widget,
332                                      gpointer data)
333 {
334     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(data));
335 
336     EntangleControlChoice *control = g_object_get_data(G_OBJECT(widget), "control");
337     EntangleControlPanel *panel = ENTANGLE_CONTROL_PANEL(data);
338     EntangleControlPanelPrivate *priv = panel->priv;
339     gboolean active;
340 
341     if (panel->priv->inUpdate)
342         return;
343 
344     active = gtk_toggle_button_get_active(widget);
345     ENTANGLE_DEBUG("Updated control toggle '%s' ('%s') with '%d'",
346                    entangle_control_get_path(ENTANGLE_CONTROL(control)),
347                    entangle_control_get_label(ENTANGLE_CONTROL(control)),
348                    active);
349     g_object_set(control, "value", active, NULL);
350 
351     entangle_camera_save_controls_async(priv->camera,
352                                         NULL,
353                                         do_update_control_finish,
354                                         panel);
355 }
356 
do_update_control_readonly_idle(gpointer data)357 static gboolean do_update_control_readonly_idle(gpointer data)
358 {
359     GtkWidget *widget = GTK_WIDGET(data);
360     GObject *control = g_object_get_data(G_OBJECT(widget), "control");
361     gboolean state;
362 
363     g_object_get(control, "readonly", &state, NULL);
364     gtk_widget_set_sensitive(widget, !state);
365 
366     return FALSE;
367 }
368 
369 
do_update_control_readonly(GObject * object G_GNUC_UNUSED,GParamSpec * pspec G_GNUC_UNUSED,gpointer data)370 static void do_update_control_readonly(GObject *object G_GNUC_UNUSED,
371                                        GParamSpec *pspec G_GNUC_UNUSED,
372                                        gpointer data)
373 {
374     g_idle_add(do_update_control_readonly_idle, data);
375 }
376 
377 
do_setup_control(EntangleControlPanel * panel,EntangleControl * control,GtkContainer * box,gint row)378 static void do_setup_control(EntangleControlPanel *panel,
379                              EntangleControl *control,
380                              GtkContainer *box,
381                              gint row)
382 {
383     GtkWidget *label = NULL;
384     GtkWidget *value = NULL;
385     gboolean needLabel = TRUE;
386 
387     ENTANGLE_DEBUG("Build control %d %s",
388                    entangle_control_get_id(control),
389                    entangle_control_get_label(control));
390 
391     if (ENTANGLE_IS_CONTROL_BUTTON(control)) {
392         needLabel = FALSE;
393         value = gtk_button_new_with_label(entangle_control_get_label(control));
394         if (entangle_control_get_readonly(control))
395             gtk_widget_set_sensitive(value, FALSE);
396         g_signal_connect(control, "notify::readonly",
397                          G_CALLBACK(do_update_control_readonly), value);
398     } else if (ENTANGLE_IS_CONTROL_CHOICE(control)) {
399         GtkCellRenderer *cell;
400         GtkListStore *store;
401         char *text;
402         int active = -1;
403 
404         /*
405          * Need todo better here
406          *
407          *  If there's only two entries 0/1, turn into toggle
408          *  If there's a continuous sequence of numbers turn
409          *      into a spinbutton
410          *
411          *  Some sequences of numbers are nonsene, and need to
412          *  be turned in to real labels.
413          *
414          *   eg Shutter speed 0.00025 should be presented 1/4000
415          */
416 
417         store = gtk_list_store_new(1, G_TYPE_STRING);
418         value = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
419         g_object_unref(store);
420 
421         cell = gtk_cell_renderer_text_new();
422         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(value), cell, TRUE);
423         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(value), cell,
424                                        "text", 0,
425                                        NULL);
426 
427         g_object_get(control, "value", &text, NULL);
428         for (int n = 0; n < entangle_control_choice_entry_count(ENTANGLE_CONTROL_CHOICE(control)); n++) {
429             GtkTreeIter iter;
430             if (g_strcmp0(text, entangle_control_choice_entry_get(ENTANGLE_CONTROL_CHOICE(control), n)) == 0)
431                 active = n;
432             gtk_list_store_append(store, &iter);
433             gtk_list_store_set(store, &iter, 0,
434                                entangle_control_choice_entry_get(ENTANGLE_CONTROL_CHOICE(control), n),
435                                -1);
436         }
437 
438         if (entangle_control_get_readonly(control))
439             gtk_widget_set_sensitive(value, FALSE);
440         gtk_combo_box_set_active(GTK_COMBO_BOX(value), active);
441 
442         g_signal_connect(value, "changed",
443                          G_CALLBACK(do_update_control_combo), panel);
444         g_signal_connect(control, "notify::value",
445                          G_CALLBACK(do_refresh_control_combo), value);
446         g_signal_connect(control, "notify::readonly",
447                          G_CALLBACK(do_update_control_readonly), value);
448     } else if (ENTANGLE_IS_CONTROL_DATE(control)) {
449         int date;
450 
451         value = gtk_entry_new();
452         g_object_get(control, "value", &date, NULL);
453         if (entangle_control_get_readonly(control))
454             gtk_widget_set_sensitive(value, FALSE);
455         //gtk_entry_set_text(GTK_ENTRY(value), text);
456     } else if (ENTANGLE_IS_CONTROL_RANGE(control)) {
457         gfloat offset;
458         gdouble min = entangle_control_range_get_min(ENTANGLE_CONTROL_RANGE(control));
459         gdouble max = entangle_control_range_get_max(ENTANGLE_CONTROL_RANGE(control));
460         gboolean forceReadonly = FALSE;
461 
462         if (fabs(min-max) < 0.005) {
463             forceReadonly = TRUE;
464             max += 1;
465         }
466 
467         value = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL,
468                                          min, max,
469                                          entangle_control_range_get_step(ENTANGLE_CONTROL_RANGE(control)));
470         g_object_get(control, "value", &offset, NULL);
471         gtk_range_set_value(GTK_RANGE(value), offset);
472         if (entangle_control_get_readonly(control) || forceReadonly)
473             gtk_widget_set_sensitive(value, FALSE);
474         g_signal_connect(value, "change-value",
475                          G_CALLBACK(do_update_control_range), panel);
476         g_signal_connect(control, "notify::value",
477                          G_CALLBACK(do_refresh_control_range), value);
478         g_signal_connect(control, "notify::readonly",
479                          G_CALLBACK(do_update_control_readonly), value);
480     } else if (ENTANGLE_IS_CONTROL_TEXT(control)) {
481         const char *text;
482 
483         value = gtk_entry_new();
484         g_object_get(control, "value", &text, NULL);
485         gtk_entry_set_text(GTK_ENTRY(value), text);
486         if (entangle_control_get_readonly(control))
487             gtk_widget_set_sensitive(value, FALSE);
488         g_signal_connect(value, "focus-out-event",
489                          G_CALLBACK(do_update_control_entry), panel);
490         g_signal_connect(control, "notify::value",
491                          G_CALLBACK(do_refresh_control_entry), value);
492         g_signal_connect(control, "notify::readonly",
493                          G_CALLBACK(do_update_control_readonly), value);
494     } else if (ENTANGLE_IS_CONTROL_TOGGLE(control)) {
495         gboolean active;
496         needLabel = FALSE;
497         value = gtk_check_button_new_with_label(entangle_control_get_label(control));
498         g_object_get(control, "value", &active, NULL);
499         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(value), active);
500         if (entangle_control_get_readonly(control))
501             gtk_widget_set_sensitive(value, FALSE);
502         g_signal_connect(value, "toggled",
503                          G_CALLBACK(do_update_control_toggle), panel);
504         g_signal_connect(control, "notify::value",
505                          G_CALLBACK(do_refresh_control_toggle), value);
506         g_signal_connect(control, "notify::readonly",
507                          G_CALLBACK(do_update_control_readonly), value);
508     }
509 
510     if (needLabel) {
511         label = gtk_label_new(entangle_control_get_label(control));
512         gtk_widget_set_tooltip_text(label, entangle_control_get_info(control));
513         gtk_widget_set_halign(label, GTK_ALIGN_FILL);
514         gtk_grid_attach(GTK_GRID(box), label, 0, row, 1, 1);
515 
516         g_object_set_data(G_OBJECT(label), "panel", panel);
517         g_object_set_data(G_OBJECT(label), "control", control);
518         gtk_widget_show(label);
519 
520         gtk_widget_set_hexpand(value, TRUE);
521         gtk_widget_set_halign(value, GTK_ALIGN_FILL);
522         gtk_grid_attach(GTK_GRID(box), value, 1, row, 1, 1);
523 
524         g_object_set_data(G_OBJECT(value), "panel", panel);
525         g_object_set_data(G_OBJECT(value), "control", control);
526         gtk_widget_show(value);
527     } else {
528         gtk_widget_set_hexpand(value, TRUE);
529         gtk_widget_set_halign(value, GTK_ALIGN_FILL);
530         gtk_grid_attach(GTK_GRID(box), value, 0, row, 2, 1);
531 
532         g_object_set_data(G_OBJECT(value), "panel", panel);
533         g_object_set_data(G_OBJECT(value), "control", control);
534         gtk_widget_show(value);
535     }
536 }
537 
538 
entangle_control_panel_get_default_controls(EntangleControlGroup * root)539 static gchar **entangle_control_panel_get_default_controls(EntangleControlGroup *root)
540 {
541     gchar **controls = NULL;
542     gsize ncontrols = 0;
543 
544     if (entangle_control_group_get_by_path(root,
545                                            "/main/capturesettings/f-number")) {
546         controls = g_renew(gchar *, controls, ncontrols + 1);
547         controls[ncontrols++] = g_strdup("/main/capturesettings/f-number");
548     } else if (entangle_control_group_get_by_path(root,
549                                            "/main/capturesettings/aperture")) {
550         controls = g_renew(gchar *, controls, ncontrols + 1);
551         controls[ncontrols++] = g_strdup("/main/capturesettings/aperture");
552     }
553 
554     if (entangle_control_group_get_by_path(root,
555                                            "/main/capturesettings/shutterspeed2")) {
556         controls = g_renew(gchar *, controls, ncontrols + 1);
557         controls[ncontrols++] = g_strdup("/main/capturesettings/shutterspeed2");
558     } else if (entangle_control_group_get_by_path(root,
559                                                   "/main/capturesettings/shutterspeed")) {
560         controls = g_renew(gchar *, controls, ncontrols + 1);
561         controls[ncontrols++] = g_strdup("/main/capturesettings/shutterspeed");
562     }
563 
564     if (entangle_control_group_get_by_path(root,
565                                            "/main/imgsettings/iso")) {
566         controls = g_renew(gchar *, controls, ncontrols + 1);
567         controls[ncontrols++] = g_strdup("/main/imgsettings/iso");
568     }
569 
570     if (entangle_control_group_get_by_path(root,
571                                            "/main/imgsettings/whitebalance")) {
572         controls = g_renew(gchar *, controls, ncontrols + 1);
573         controls[ncontrols++] = g_strdup("/main/imgsettings/whitebalance");
574     }
575 
576     if (entangle_control_group_get_by_path(root,
577                                            "/main/capturesettings/imagequality")) {
578         controls = g_renew(gchar *, controls, ncontrols + 1);
579         controls[ncontrols++] = g_strdup("/main/capturesettings/imagequality");
580     } else if (entangle_control_group_get_by_path(root,
581                                                   "/main/imgsettings/imageformat")) {
582         controls = g_renew(gchar *, controls, ncontrols + 1);
583         controls[ncontrols++] = g_strdup("/main/imgsettings/imageformat");
584     }
585 
586     if (entangle_control_group_get_by_path(root,
587                                            "/main/imgsettings/imagesize")) {
588         controls = g_renew(gchar *, controls, ncontrols + 1);
589         controls[ncontrols++] = g_strdup("/main/imgsettings/imagesize");
590     }
591 
592     controls = g_renew(gchar *, controls, ncontrols + 1);
593     controls[ncontrols++] = NULL;
594 
595     return controls;
596 }
597 
do_get_control_list(EntangleControlGroup * group)598 static GList *do_get_control_list(EntangleControlGroup *group)
599 {
600     gsize i;
601     GList *controls = NULL;
602 
603     for (i = 0; i < entangle_control_group_count(group); i++) {
604         EntangleControl *control = entangle_control_group_get(group, i);
605 
606         if (ENTANGLE_IS_CONTROL_GROUP(control)) {
607             GList *children = do_get_control_list(ENTANGLE_CONTROL_GROUP(control));
608 
609             controls = g_list_concat(controls, children);
610         } else {
611             controls = g_list_append(controls, control);
612         }
613     }
614 
615     return controls;
616 }
617 
compare_control(gconstpointer a,gconstpointer b)618 static int compare_control(gconstpointer a, gconstpointer b)
619 {
620     EntangleControl *ac = (EntangleControl *)a;
621     EntangleControl *bc = (EntangleControl *)b;
622 
623     return strcmp(entangle_control_get_label(ac),
624                   entangle_control_get_label(bc));
625 }
626 
627 
is_control_enabled(gchar ** controls,const gchar * check)628 static gboolean is_control_enabled(gchar **controls,
629                                    const gchar *check)
630 {
631     gsize i;
632 
633     if (!controls)
634         return FALSE;
635 
636     for (i = 0; controls[i] != NULL; i++)
637         if (g_str_equal(controls[i], check))
638             return TRUE;
639     return FALSE;
640 }
641 
642 static void do_setup_controls(EntangleControlPanel *panel);
643 
do_reset_controls(GtkWidget * src G_GNUC_UNUSED,EntangleControlPanel * panel)644 static void do_reset_controls(GtkWidget *src G_GNUC_UNUSED,
645                               EntangleControlPanel *panel)
646 {
647     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(panel));
648 
649     EntangleControlPanelPrivate *priv = panel->priv;
650 
651     gtk_container_foreach(GTK_CONTAINER(priv->grid), do_control_remove, panel);
652     priv->rows = 0;
653     entangle_camera_preferences_set_controls(priv->cameraPrefs,
654                                              NULL);
655     do_setup_controls(panel);
656 }
657 
658 
do_update_control_prefs(EntangleControlPanel * panel)659 static void do_update_control_prefs(EntangleControlPanel *panel)
660 {
661     EntangleControlPanelPrivate *priv = panel->priv;
662     const gchar **controlnames = g_new0(const gchar *, priv->rows + 1);
663     gsize i;
664 
665     for (i = 0; i < priv->rows; i++) {
666         GtkWidget *widget = gtk_grid_get_child_at(GTK_GRID(priv->grid), 0, i);
667         EntangleControl *control = g_object_get_data(G_OBJECT(widget), "control");
668         controlnames[i] = entangle_control_get_path(control);
669     }
670     controlnames[priv->rows] = NULL;
671     entangle_camera_preferences_set_controls(priv->cameraPrefs,
672                                              (const gchar *const *)controlnames);
673     g_free(controlnames);
674 }
675 
do_add_control(EntangleControlPanel * panel,EntangleControl * control)676 static void do_add_control(EntangleControlPanel *panel,
677                            EntangleControl *control)
678 {
679     EntangleControlPanelPrivate *priv = panel->priv;
680     gsize i;
681 
682     for (i = 0; i < priv->rows; i++) {
683         GtkWidget *widget = gtk_grid_get_child_at(GTK_GRID(priv->grid),
684                                                   0, i);
685         EntangleControl *that = g_object_get_data(G_OBJECT(widget), "control");
686 
687         if (that == control)
688             return;
689     }
690 
691     gtk_grid_insert_row(GTK_GRID(priv->grid), priv->rows);
692     do_setup_control(panel, control, GTK_CONTAINER(priv->grid), priv->rows++);
693     do_update_control_prefs(panel);
694 }
695 
do_remove_control(EntangleControlPanel * panel,EntangleControl * control)696 static void do_remove_control(EntangleControlPanel *panel,
697                               EntangleControl *control)
698 {
699     EntangleControlPanelPrivate *priv = panel->priv;
700     gsize i;
701 
702     for (i = 0; i < priv->rows; i++) {
703         GtkWidget *widget = gtk_grid_get_child_at(GTK_GRID(priv->grid),
704                                                   0, i);
705         EntangleControl *that = g_object_get_data(G_OBJECT(widget), "control");
706 
707         if (that == control) {
708             gtk_grid_remove_row(GTK_GRID(priv->grid), i);
709             priv->rows--;
710             break;
711         }
712     }
713     do_update_control_prefs(panel);
714 }
715 
716 
do_addremove_control(GtkWidget * src,EntangleControlPanel * panel)717 static void do_addremove_control(GtkWidget *src,
718                                  EntangleControlPanel *panel)
719 {
720     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(panel));
721 
722     EntangleControl *control;
723 
724     control = g_object_get_data(G_OBJECT(src), "control");
725     g_return_if_fail(ENTANGLE_IS_CONTROL(control));
726 
727     if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(src)))
728         do_add_control(panel, control);
729     else
730         do_remove_control(panel, control);
731 }
732 
733 
do_create_control_menu(EntangleControlPanel * panel,EntangleControlGroup * group,gchar ** controlnames)734 static GtkWidget *do_create_control_menu(EntangleControlPanel *panel,
735                                          EntangleControlGroup *group,
736                                          gchar **controlnames)
737 {
738     GList *controls = do_get_control_list(group);
739     GList *tmp;
740     GtkWidget *menu = gtk_menu_new();
741     GtkWidget *item;
742 
743     tmp = controls = g_list_sort(controls, compare_control);
744 
745     while (tmp) {
746         EntangleControl *control = tmp->data;
747         item = gtk_check_menu_item_new_with_label(entangle_control_get_label(control));
748 
749         g_object_set_data(G_OBJECT(item), "control", control);
750         g_signal_connect(item, "toggled",
751                          G_CALLBACK(do_addremove_control), panel);
752 
753         gtk_container_add(GTK_CONTAINER(menu),
754                           item);
755 
756         if (is_control_enabled(controlnames,
757                                entangle_control_get_path(control)))
758             gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
759                                            TRUE);
760 
761         tmp = tmp->next;
762     }
763 
764     g_list_free(controls);
765 
766     item = gtk_separator_menu_item_new();
767     gtk_container_add(GTK_CONTAINER(menu), item);
768 
769     item = gtk_menu_item_new_with_label(_("Reset controls"));
770     gtk_container_add(GTK_CONTAINER(menu), item);
771     g_signal_connect(item, "activate", G_CALLBACK(do_reset_controls), panel);
772 
773     gtk_widget_show_all(menu);
774 
775     return menu;
776 }
777 
778 
do_setup_controls(EntangleControlPanel * panel)779 static void do_setup_controls(EntangleControlPanel *panel)
780 {
781     EntangleControlPanelPrivate *priv = panel->priv;
782     EntangleControlGroup *root;
783     GtkWidget *settingsButton;
784     gchar **controls;
785     GtkWidget *menu;
786     gsize i;
787 
788     root = entangle_camera_get_controls(priv->camera, NULL);
789 
790     controls = entangle_camera_preferences_get_controls(priv->cameraPrefs);
791     if (!controls || !controls[0]) {
792         controls = entangle_control_panel_get_default_controls(root);
793         entangle_camera_preferences_set_controls(priv->cameraPrefs,
794                                                  (const char *const *)controls);
795     }
796 
797 
798     for (i = 0; controls[i] != NULL; i++) {
799         EntangleControl *control = entangle_control_group_get_by_path(root,
800                                                                       controls[i]);
801         if (control)
802             do_setup_control(panel, control, GTK_CONTAINER(priv->grid), priv->rows++);
803     }
804 
805     menu = do_create_control_menu(panel,
806                                   root,
807                                   controls);
808 
809     settingsButton = gtk_menu_button_new();
810     gtk_container_add(GTK_CONTAINER(settingsButton),
811                       gtk_image_new_from_icon_name("emblem-system-symbolic",
812                                                    GTK_ICON_SIZE_SMALL_TOOLBAR));
813     gtk_menu_button_set_popup(GTK_MENU_BUTTON(settingsButton),
814                               menu);
815     gtk_widget_set_hexpand(settingsButton, TRUE);
816     gtk_widget_set_halign(settingsButton, GTK_ALIGN_END);
817 #if GTK_CHECK_VERSION(3, 12, 0)
818     gtk_widget_set_margin_end(settingsButton, 6);
819 #else
820     gtk_widget_set_margin_right(settingsButton, 6);
821 #endif
822 
823 
824     gtk_grid_attach(GTK_GRID(priv->grid), settingsButton, 1, priv->rows, 2, 1);
825 
826     gtk_widget_show_all(GTK_WIDGET(panel));
827     g_object_unref(root);
828     g_strfreev(controls);
829 }
830 
do_setup_camera(EntangleControlPanel * panel)831 static void do_setup_camera(EntangleControlPanel *panel)
832 {
833     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(panel));
834 
835     EntangleControlPanelPrivate *priv = panel->priv;
836 
837     gtk_container_foreach(GTK_CONTAINER(priv->grid), do_control_remove, panel);
838     priv->rows = 0;
839 
840     if (!priv->camera) {
841         GtkWidget *label = gtk_label_new(_("No camera connected"));
842 
843         gtk_widget_set_hexpand(label, TRUE);
844         gtk_widget_set_halign(label, GTK_ALIGN_FILL);
845         gtk_grid_attach(GTK_GRID(priv->grid), label, 0, 0, 2, 1);
846         gtk_widget_show_all(GTK_WIDGET(panel));
847     } else if (entangle_camera_get_controls(priv->camera, NULL) == NULL) {
848         GtkWidget *label = gtk_label_new(_("No controls available"));
849         gtk_widget_set_hexpand(label, TRUE);
850         gtk_widget_set_halign(label, GTK_ALIGN_FILL);
851         gtk_grid_attach(GTK_GRID(priv->grid), label, 0, 0, 2, 1);
852         gtk_widget_show_all(GTK_WIDGET(panel));
853     } else {
854         do_setup_controls(panel);
855     }
856 }
857 
858 
do_update_camera(GObject * object G_GNUC_UNUSED,GParamSpec * pspec G_GNUC_UNUSED,gpointer data)859 static void do_update_camera(GObject *object G_GNUC_UNUSED,
860                              GParamSpec *pspec G_GNUC_UNUSED,
861                              gpointer data)
862 {
863     g_return_if_fail(ENTANGLE_IS_CONTROL_PANEL(data));
864 
865     EntangleControlPanel *panel = ENTANGLE_CONTROL_PANEL(data);
866     EntangleControlPanelPrivate *priv = panel->priv;
867 
868     if (priv->camera) {
869         g_object_unref(priv->camera);
870         priv->camera = NULL;
871     }
872     priv->camera = entangle_camera_preferences_get_camera(priv->cameraPrefs);
873     if (priv->camera)
874         g_object_ref(priv->camera);
875 
876     do_setup_camera(panel);
877 }
878 
879 
entangle_control_panel_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)880 static void entangle_control_panel_get_property(GObject *object,
881                                                 guint prop_id,
882                                                 GValue *value,
883                                                 GParamSpec *pspec)
884 {
885     EntangleControlPanel *panel = ENTANGLE_CONTROL_PANEL(object);
886     EntangleControlPanelPrivate *priv = panel->priv;
887 
888     switch (prop_id)
889         {
890         case PROP_CAMERA_PREFS:
891             g_value_set_object(value, priv->cameraPrefs);
892             break;
893 
894         case PROP_CAMERA:
895             g_value_set_object(value, priv->camera);
896             break;
897 
898         case PROP_HAS_CONTROLS:
899             g_value_set_boolean(value, priv->hasControls);
900             break;
901 
902         default:
903             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
904         }
905 }
906 
907 
entangle_control_panel_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)908 static void entangle_control_panel_set_property(GObject *object,
909                                                 guint prop_id,
910                                                 const GValue *value,
911                                                 GParamSpec *pspec)
912 {
913     EntangleControlPanel *panel = ENTANGLE_CONTROL_PANEL(object);
914     EntangleControlPanelPrivate *priv = panel->priv;
915 
916     ENTANGLE_DEBUG("Set prop on control panel %d", prop_id);
917 
918     switch (prop_id)
919         {
920         case PROP_CAMERA_PREFS:
921             priv->cameraPrefs = g_value_get_object(value);
922             priv->sigCamera = g_signal_connect(priv->cameraPrefs, "notify::camera",
923                                                G_CALLBACK(do_update_camera), panel);
924             break;
925 
926         default:
927             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
928         }
929 }
930 
931 
entangle_control_panel_finalize(GObject * object)932 static void entangle_control_panel_finalize(GObject *object)
933 {
934     EntangleControlPanel *panel = ENTANGLE_CONTROL_PANEL(object);
935     EntangleControlPanelPrivate *priv = panel->priv;
936 
937     if (priv->camera)
938         g_object_unref(priv->camera);
939 
940     if (priv->cameraPrefs) {
941         g_signal_handler_disconnect(priv->cameraPrefs, priv->sigCamera);
942         g_object_unref(priv->cameraPrefs);
943     }
944 
945     G_OBJECT_CLASS(entangle_control_panel_parent_class)->finalize(object);
946 }
947 
948 
entangle_control_panel_class_init(EntangleControlPanelClass * klass)949 static void entangle_control_panel_class_init(EntangleControlPanelClass *klass)
950 {
951     GObjectClass *object_class = G_OBJECT_CLASS(klass);
952 
953     object_class->finalize = entangle_control_panel_finalize;
954     object_class->get_property = entangle_control_panel_get_property;
955     object_class->set_property = entangle_control_panel_set_property;
956 
957     g_object_class_install_property(object_class,
958                                     PROP_CAMERA_PREFS,
959                                     g_param_spec_object("camera-prefs",
960                                                         "Camera prefs",
961                                                         "Camera preferences to manage",
962                                                         ENTANGLE_TYPE_CAMERA_PREFERENCES,
963                                                         G_PARAM_READWRITE |
964                                                         G_PARAM_CONSTRUCT_ONLY |
965                                                         G_PARAM_STATIC_NAME |
966                                                         G_PARAM_STATIC_NICK |
967                                                         G_PARAM_STATIC_BLURB));
968 
969     g_object_class_install_property(object_class,
970                                     PROP_CAMERA,
971                                     g_param_spec_object("camera",
972                                                         "Camera",
973                                                         "Camera to manage",
974                                                         ENTANGLE_TYPE_CAMERA,
975                                                         G_PARAM_READABLE |
976                                                         G_PARAM_STATIC_NAME |
977                                                         G_PARAM_STATIC_NICK |
978                                                         G_PARAM_STATIC_BLURB));
979 
980     g_object_class_install_property(object_class,
981                                     PROP_HAS_CONTROLS,
982                                     g_param_spec_boolean("has-controls",
983                                                          "Has Controls",
984                                                          "Has Controls",
985                                                          FALSE,
986                                                          G_PARAM_READABLE |
987                                                          G_PARAM_STATIC_NAME |
988                                                          G_PARAM_STATIC_NICK |
989                                                          G_PARAM_STATIC_BLURB));
990 
991     g_type_class_add_private(klass, sizeof(EntangleControlPanelPrivate));
992 }
993 
994 
entangle_control_panel_new(EntangleCameraPreferences * prefs)995 EntangleControlPanel *entangle_control_panel_new(EntangleCameraPreferences *prefs)
996 {
997     return ENTANGLE_CONTROL_PANEL(g_object_new(ENTANGLE_TYPE_CONTROL_PANEL,
998                                                "camera-prefs", prefs,
999                                                "label", "Camera settings",
1000                                                "expanded", TRUE,
1001                                                NULL));
1002 }
1003 
1004 
entangle_control_panel_init(EntangleControlPanel * panel)1005 static void entangle_control_panel_init(EntangleControlPanel *panel)
1006 {
1007     EntangleControlPanelPrivate *priv = panel->priv;
1008 
1009     priv = panel->priv = ENTANGLE_CONTROL_PANEL_GET_PRIVATE(panel);
1010 
1011     gtk_container_set_border_width(GTK_CONTAINER(panel), 0);
1012 
1013     priv->grid = gtk_grid_new();
1014     gtk_grid_set_row_spacing(GTK_GRID(priv->grid), 6);
1015     gtk_grid_set_column_spacing(GTK_GRID(priv->grid), 6);
1016     gtk_container_set_border_width(GTK_CONTAINER(priv->grid), 6);
1017     gtk_widget_set_hexpand(priv->grid, TRUE);
1018     gtk_widget_set_halign(priv->grid, GTK_ALIGN_FILL);
1019 
1020     gtk_container_add(GTK_CONTAINER(panel), priv->grid);
1021 
1022     do_setup_camera(panel);
1023 }
1024 
1025 
1026 /**
1027  * entangle_control_panel_get_camera_preferences:
1028  * @panel: the control widget
1029  *
1030  * Get the camera preferences whose controls are displayed
1031  *
1032  * Returns: (transfer none): the camera preferences or NULL
1033  */
entangle_control_panel_get_camera_preferences(EntangleControlPanel * panel)1034 EntangleCameraPreferences *entangle_control_panel_get_camera_preferences(EntangleControlPanel *panel)
1035 {
1036     g_return_val_if_fail(ENTANGLE_IS_CONTROL_PANEL(panel), NULL);
1037 
1038     EntangleControlPanelPrivate *priv = panel->priv;
1039 
1040     return priv->cameraPrefs;
1041 }
1042 
1043 
entangle_control_panel_get_has_controls(EntangleControlPanel * panel)1044 gboolean entangle_control_panel_get_has_controls(EntangleControlPanel *panel)
1045 {
1046     g_return_val_if_fail(ENTANGLE_IS_CONTROL_PANEL(panel), FALSE);
1047 
1048     EntangleControlPanelPrivate *priv = panel->priv;
1049 
1050     return priv->hasControls;
1051 }
1052 
1053 
1054 /*
1055  * Local variables:
1056  *  c-indent-level: 4
1057  *  c-basic-offset: 4
1058  *  indent-tabs-mode: nil
1059  *  tab-width: 8
1060  * End:
1061  */
1062