/* This file is part of darktable, Copyright (C) 2010-2026 darktable developers. darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see . */ #include "bauhaus/bauhaus.h" #include "common/camera_control.h" #include "common/darktable.h" #include "control/conf.h" #include "control/control.h" #include "control/jobs.h" #include "dtgtk/button.h" #include "gui/accelerators.h" #include "gui/gtk.h" #include "libs/lib.h" #include "libs/lib_api.h" #include #include DT_MODULE(1) typedef struct dt_lib_camera_property_t { // Visual property name gchar *name; // Property name gchar *property_name; // Combobox of values available for the property GtkWidget *values; // Show property OSD GtkDarktableToggleButton *osd; GtkWidget *remove; GtkWidget *hbox; gboolean user_defined; struct dt_lib_camera_t *lib; guint remove_source; } dt_lib_camera_property_t; typedef struct dt_lib_camera_t { // GUI part of the module struct { GtkGrid *main_grid; GtkDarktableToggleButton *toggle_timer, *toggle_sequence, *toggle_bracket; GtkWidget *timer, *count, *brackets, *steps; GtkWidget *button1; int rows; // the number of rows in the grid int prop_start; // the row of the grid above the first property int prop_end; // the row of the grid where to insert new properties GtkWidget *plabel, *pname; // propertylabel, widget GList *properties; // a list of dt_lib_camera_property_t GMenu *properties_menu; // available properties } gui; // Data part of the module struct { const gchar *camera_model; dt_camctl_listener_t *listener; } data; } dt_lib_camera_t; const char *name(dt_lib_module_t *self) { return _("camera settings"); } dt_view_type_flags_t views(dt_lib_module_t *self) { return DT_VIEW_TETHERING; } uint32_t container(dt_lib_module_t *self) { return DT_UI_CONTAINER_PANEL_RIGHT_CENTER; } void gui_reset(dt_lib_module_t *self) { } int position(const dt_lib_module_t *self) { return 997; } // Property changed static void property_changed_callback(GtkComboBox *cb, gpointer data) { dt_lib_camera_property_t *prop = (dt_lib_camera_property_t *)data; const char *new_value = dt_bauhaus_combobox_get_text(prop->values); const char *current = dt_camctl_camera_get_property(darktable.camctl, NULL, prop->property_name); if(new_value && current && g_strcmp0(new_value, current) == 0) return; dt_camctl_camera_set_property_string(darktable.camctl, NULL, prop->property_name, new_value); } // Add a new property of camera to the GUI static dt_lib_camera_property_t *_lib_property_add_new(dt_lib_camera_t *lib, const gchar *label, const gchar *propertyname) { if(dt_camctl_camera_property_exists(darktable.camctl, NULL, propertyname)) { const char *value; if((value = dt_camctl_camera_property_get_first_choice(darktable.camctl, NULL, propertyname)) != NULL) { // We got a value for property, let's construct the GUI for the property and add values int i = 0; const char *current_value = dt_camctl_camera_get_property(darktable.camctl, NULL, propertyname); dt_lib_camera_property_t *prop = calloc(1, sizeof(dt_lib_camera_property_t)); prop->lib = lib; prop->name = strdup(label); prop->property_name = strdup(propertyname); prop->values = dt_bauhaus_combobox_new(NULL); dt_bauhaus_widget_set_label(prop->values, NULL, label); g_object_ref_sink(prop->values); prop->osd = DTGTK_TOGGLEBUTTON(dtgtk_togglebutton_new(dtgtk_cairo_paint_eye, 0, NULL)); dt_gui_add_class(GTK_WIDGET(prop->osd), "dt_transparent_background"); g_object_ref_sink(prop->osd); gtk_widget_set_tooltip_text(GTK_WIDGET(prop->osd), _("toggle view property in center view")); do { dt_bauhaus_combobox_add(prop->values, g_dgettext("libgphoto2-6", value)); if(current_value && !strcmp(current_value, g_dgettext("libgphoto2-6", value))) dt_bauhaus_combobox_set(prop->values, i); i++; } while((value = dt_camctl_camera_property_get_next_choice(darktable.camctl, NULL, propertyname)) != NULL); lib->gui.properties = g_list_append(lib->gui.properties, prop); // Does dead lock!!! g_signal_connect(G_OBJECT(prop->values), "value-changed", G_CALLBACK(property_changed_callback), (gpointer)prop); return prop; } } return NULL; } static dt_lib_camera_property_t *_lib_property_add_stub(dt_lib_camera_t *lib, const gchar *label, const gchar *propertyname) { dt_lib_camera_property_t *prop = calloc(1, sizeof(dt_lib_camera_property_t)); prop->lib = lib; prop->user_defined = TRUE; prop->name = strdup(label); prop->property_name = strdup(propertyname ? propertyname : ""); prop->values = dt_bauhaus_combobox_new(NULL); dt_bauhaus_widget_set_label(prop->values, NULL, label); g_object_ref_sink(prop->values); dt_bauhaus_combobox_add(prop->values, _("unavailable")); dt_bauhaus_combobox_set(prop->values, 0); gtk_widget_set_sensitive(prop->values, FALSE); /* no value-changed handler - must not touch the camera */ lib->gui.properties = g_list_append(lib->gui.properties, prop); return prop; } static void _lib_property_free(gpointer data) { dt_lib_camera_property_t *prop = (dt_lib_camera_property_t *)data; if(prop->remove_source) { g_source_remove(prop->remove_source); prop->remove_source = 0; } if(prop->osd) g_object_unref(prop->osd); if(prop->values) g_object_unref(prop->values); if(prop->remove) g_object_unref(prop->remove); free(prop->name); free(prop->property_name); free(prop); } static dt_lib_camera_property_t *_find_user_property_by_label(dt_lib_camera_t *lib, const char *label) { for(GList *l = lib->gui.properties; l; l = g_list_next(l)) { dt_lib_camera_property_t *prop = l->data; if(prop->user_defined && g_strcmp0(prop->name, label) == 0) return prop; } return NULL; } static gint _compare_property_by_name(gconstpointer a, gconstpointer b) { dt_lib_camera_property_t *ca = (dt_lib_camera_property_t *)a; return strcmp(ca->property_name, (char *)b); } // Called when the property value changes static void _camera_property_value_changed(const dt_camera_t *camera, const char *name, const char *value, void *data) { dt_lib_camera_t *lib = (dt_lib_camera_t *)data; GList *citem; if((citem = g_list_find_custom(lib->gui.properties, name, _compare_property_by_name)) != NULL) { dt_lib_camera_property_t *prop = citem->data; /* Camera-driven update must not re-queue a set to the camera. */ g_signal_handlers_block_matched(prop->values, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(property_changed_callback), NULL); dt_bauhaus_combobox_set_from_text(prop->values, value); g_signal_handlers_unblock_matched(prop->values, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(property_changed_callback), NULL); } } // Called when the accessibility of a property changes static void _camera_property_accessibility_changed(const dt_camera_t *camera, const char *name, gboolean read_only, void *data) { } static gboolean _bailout_of_tethering(gpointer user_data) { /* Idle callback from CAMERA_CONNECTION_BROKEN only. */ dt_lib_camera_t *lib = (dt_lib_camera_t *)user_data; dt_camctl_tether_mode(darktable.camctl, NULL, FALSE); dt_camctl_unregister_listener(darktable.camctl, lib->data.listener); // Switch back to library mode dt_ctl_switch_mode_to("lighttable"); return FALSE; } // Called when a camera error occurs static void _camera_error_callback(const dt_camera_t *camera, dt_camera_error_t error, void *user_data) { if(error != CAMERA_CONNECTION_BROKEN) return; dt_control_log(_("connection with camera lost, exiting tethering mode")); g_idle_add(_bailout_of_tethering, user_data); } static void _capture_button_clicked(GtkWidget *widget, gpointer user_data) { const dt_lib_camera_t *lib = (dt_lib_camera_t *)user_data; const uint32_t delay = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(lib->gui.toggle_timer)) ? (uint32_t)gtk_spin_button_get_value(GTK_SPIN_BUTTON(lib->gui.timer)) : 0; const uint32_t count = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(lib->gui.toggle_sequence)) ? (uint32_t)gtk_spin_button_get_value(GTK_SPIN_BUTTON(lib->gui.count)) : 1; const uint32_t brackets = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(lib->gui.toggle_bracket)) ? (uint32_t)gtk_spin_button_get_value(GTK_SPIN_BUTTON(lib->gui.brackets)) : 0; const uint32_t steps = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(lib->gui.toggle_bracket)) ? (uint32_t)gtk_spin_button_get_value(GTK_SPIN_BUTTON(lib->gui.steps)) : 0; dt_control_add_job(DT_JOB_QUEUE_USER_FG, dt_camera_capture_job_create(delay, count, brackets, steps)); } static void _osd_button_clicked(GtkWidget *widget, gpointer user_data) { dt_control_queue_redraw_center(); } static void _property_choice_callback(GSimpleAction *action, GVariant *parameter, gpointer user_data) { dt_lib_camera_t *lib = (dt_lib_camera_t *)user_data; const gchar *p_name = g_variant_get_string(parameter, NULL); gtk_entry_set_text(GTK_ENTRY(lib->gui.pname), p_name); } static void _show_property_popupmenu_clicked(GtkWidget *widget, gpointer user_data) { GActionGroup *action_group = gtk_widget_get_action_group(widget, "camera"); if(action_group == NULL) { GActionEntry action_entries[] = { { "activate", _property_choice_callback, "s", NULL } }; action_group = G_ACTION_GROUP(g_simple_action_group_new()); g_action_map_add_action_entries(G_ACTION_MAP(action_group), action_entries, G_N_ELEMENTS(action_entries), user_data); gtk_widget_insert_action_group(widget, "camera", G_ACTION_GROUP(action_group)); g_object_unref(action_group); } dt_lib_camera_t *lib = (dt_lib_camera_t *)user_data; // properties_menu is owned until view_leave GtkWidget *popover_menu = dt_gui_popover_menu_from_model(widget, lib->gui.properties_menu); gtk_popover_popup(GTK_POPOVER(popover_menu)); } static void _property_conf_key(char *key, size_t key_size, const char *label) { g_strlcpy(key, "plugins/capture/tethering/properties/", key_size); g_strlcat(key, label, key_size); for(char *p = key + strlen("plugins/capture/tethering/properties/"); *p; p++) if(*p == ' ') *p = '_'; } static void _lib_property_remove(dt_lib_camera_property_t *prop, const gboolean remove_conf) { dt_lib_camera_t *lib = prop->lib; if(!prop->user_defined) return; if(remove_conf) { char key[256]; _property_conf_key(key, sizeof(key), prop->name); dt_conf_remove_key(key); } int top = 0; gtk_container_child_get(GTK_CONTAINER(lib->gui.main_grid), prop->hbox, "top-attach", &top, NULL); gtk_grid_remove_row(lib->gui.main_grid, top); lib->gui.prop_end--; lib->gui.rows--; lib->gui.properties = g_list_remove(lib->gui.properties, prop); _lib_property_free(prop); } static gboolean _lib_property_remove_idle(gpointer user_data) { dt_lib_camera_property_t *prop = (dt_lib_camera_property_t *)user_data; prop->remove_source = 0; _lib_property_remove(prop, TRUE); return G_SOURCE_REMOVE; } static void _remove_property_clicked(GtkWidget *widget, gpointer user_data) { dt_lib_camera_property_t *prop = (dt_lib_camera_property_t *)user_data; if(prop->remove_source) return; prop->remove_source = g_idle_add(_lib_property_remove_idle, prop); } static void _lib_property_add_to_gui(dt_lib_camera_property_t *prop, dt_lib_camera_t *lib) { prop->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_PIXEL_APPLY_DPI(5)); gtk_box_pack_start(GTK_BOX(prop->hbox), GTK_WIDGET(prop->values), TRUE, TRUE, 0); if(prop->osd) { gtk_box_pack_start(GTK_BOX(prop->hbox), GTK_WIDGET(prop->osd), FALSE, FALSE, 0); g_signal_connect(G_OBJECT(prop->osd), "clicked", G_CALLBACK(_osd_button_clicked), prop); } if(prop->user_defined) { prop->remove = dtgtk_button_new(dtgtk_cairo_paint_remove, 0, NULL); dt_gui_add_class(prop->remove, "dt_transparent_background"); g_object_ref_sink(prop->remove); gtk_widget_set_tooltip_text(prop->remove, _("remove this property")); gtk_box_pack_start(GTK_BOX(prop->hbox), prop->remove, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(prop->remove), "clicked", G_CALLBACK(_remove_property_clicked), prop); } gtk_grid_insert_row(lib->gui.main_grid, lib->gui.prop_end); // make space for the new row gtk_grid_attach(lib->gui.main_grid, prop->hbox, 0, lib->gui.prop_end, 2, 1); gtk_widget_show_all(GTK_WIDGET(prop->hbox)); lib->gui.rows++; lib->gui.prop_end++; } static void _add_property_button_clicked(GtkWidget *widget, gpointer user_data) { dt_lib_camera_t *lib = (dt_lib_camera_t *)user_data; gchar *label = g_strstrip(g_strdup(gtk_entry_get_text(GTK_ENTRY(lib->gui.plabel)))); gchar *property = g_strstrip(g_strdup(gtk_entry_get_text(GTK_ENTRY(lib->gui.pname)))); if(label[0] == '\0' || property[0] == '\0') { g_free(label); g_free(property); return; } dt_lib_camera_property_t *prop = _lib_property_add_new(lib, label, property); if(prop == NULL) { dt_control_log(_("cannot add camera property '%s'"), property); g_free(label); g_free(property); return; } dt_lib_camera_property_t *existing = _find_user_property_by_label(lib, label); if(existing && existing != prop) _lib_property_remove(existing, FALSE); prop->user_defined = TRUE; _lib_property_add_to_gui(prop, lib); char key[256]; _property_conf_key(key, sizeof(key), label); dt_conf_set_string(key, property); gtk_entry_set_text(GTK_ENTRY(lib->gui.plabel), ""); gtk_entry_set_text(GTK_ENTRY(lib->gui.pname), ""); g_free(label); g_free(property); } static void _toggle_capture_mode_clicked(GtkWidget *widget, gpointer user_data) { dt_lib_camera_t *lib = (dt_lib_camera_t *)user_data; GtkWidget *w = NULL; if(widget == GTK_WIDGET(lib->gui.toggle_timer)) w = lib->gui.timer; else if(widget == GTK_WIDGET(lib->gui.toggle_sequence)) w = lib->gui.count; else if(widget == GTK_WIDGET(lib->gui.toggle_bracket)) { gtk_widget_set_sensitive(lib->gui.brackets, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))); gtk_widget_set_sensitive(lib->gui.steps, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))); } if(w) gtk_widget_set_sensitive(w, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))); } #define BAR_HEIGHT DT_PIXEL_APPLY_DPI(18) // also change in views/tethering.c static void _expose_info_bar(dt_lib_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery) { dt_lib_camera_t *lib = self->data; // Draw infobar background at top cairo_set_source_rgb(cr, .0, .0, .0); cairo_rectangle(cr, 0, 0, width, BAR_HEIGHT); cairo_fill(cr); cairo_set_source_rgb(cr, .8, .8, .8); // Draw left aligned value camera model value PangoLayout *layout; PangoRectangle ink; PangoFontDescription *desc = dt_gui_get_font(); pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD); layout = pango_cairo_create_layout(cr); const int fontsize = DT_PIXEL_APPLY_DPI(11.5); pango_font_description_set_absolute_size(desc, fontsize * PANGO_SCALE); pango_layout_set_font_description(layout, desc); char model[128] = { 0 }; if(lib->data.camera_model) g_strlcpy(model, lib->data.camera_model, sizeof(model)); pango_layout_set_text(layout, model, -1); pango_layout_get_pixel_extents(layout, &ink, NULL); cairo_move_to(cr, DT_PIXEL_APPLY_DPI(5), DT_PIXEL_APPLY_DPI(1) + BAR_HEIGHT - ink.height / 2 - fontsize); pango_cairo_show_layout(cr, layout); // Draw right aligned battery value const char *battery_value = dt_camctl_camera_get_property(darktable.camctl, NULL, "batterylevel"); char battery[64] = { 0 }; snprintf(battery, sizeof(battery), "%s: %s", _("battery"), battery_value ? battery_value : _("n/a")); pango_layout_set_text(layout, battery, -1); pango_layout_get_pixel_extents(layout, &ink, NULL); cairo_move_to(cr, width - ink.width - DT_PIXEL_APPLY_DPI(5), DT_PIXEL_APPLY_DPI(1) + BAR_HEIGHT - ink.height / 2 - fontsize); pango_cairo_show_layout(cr, layout); // Let's cook up the middle part of infobar gchar center[1024] = { 0 }; for(GList *l = lib->gui.properties; l; l = g_list_next(l)) { dt_lib_camera_property_t *prop = l->data; if(prop->osd && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(prop->osd))) { g_strlcat(center, " ", sizeof(center)); g_strlcat(center, prop->name, sizeof(center)); g_strlcat(center, ": ", sizeof(center)); g_strlcat(center, dt_bauhaus_combobox_get_text(prop->values), sizeof(center)); } } g_strlcat(center, " ", sizeof(center)); // Now lets put it in center view... pango_layout_set_text(layout, center, -1); pango_layout_get_pixel_extents(layout, &ink, NULL); cairo_move_to(cr, (width / 2) - (ink.width / 2), DT_PIXEL_APPLY_DPI(1) + BAR_HEIGHT - ink.height / 2 - fontsize); pango_cairo_show_layout(cr, layout); pango_font_description_free(desc); g_object_unref(layout); } static void _expose_settings_bar(dt_lib_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery) { /*// Draw control bar at bottom cairo_set_source_rgb (cr, .0,.0,.0); cairo_rectangle(cr, 0, height-BAR_HEIGHT, width, BAR_HEIGHT); cairo_fill (cr);*/ } void gui_post_expose(dt_lib_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery) { // Setup cairo font.. cairo_set_font_size(cr, 11.5); //cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); _expose_info_bar(self, cr, width, height, pointerx, pointery); _expose_settings_bar(self, cr, width, height, pointerx, pointery); } void gui_init(dt_lib_module_t *self) { self->data = calloc(1, sizeof(dt_lib_camera_t)); // Setup lib data dt_lib_camera_t *lib = self->data; lib->data.listener = calloc(1, sizeof(dt_camctl_listener_t)); lib->data.listener->data = lib; lib->data.listener->camera_error = _camera_error_callback; lib->data.listener->camera_property_value_changed = _camera_property_value_changed; lib->data.listener->camera_property_accessibility_changed = _camera_property_accessibility_changed; // Setup GUI lib->gui.rows = 0; lib->gui.prop_end = 0; self->widget = gtk_grid_new(); gtk_grid_set_column_spacing(GTK_GRID(self->widget), DT_PIXEL_APPLY_DPI(5)); lib->gui.main_grid = GTK_GRID(self->widget); gtk_grid_set_row_spacing(GTK_GRID(self->widget), DT_PIXEL_APPLY_DPI(5)); gtk_grid_set_column_homogeneous(GTK_GRID(self->widget), FALSE); GtkBox *hbox; // Camera control GtkWidget *label = dt_ui_section_label_new(C_("section", "camera control")); gtk_widget_set_hexpand(label, TRUE); gtk_grid_attach(GTK_GRID(self->widget), label, lib->gui.rows++, 0, 2, 1); GtkWidget *modes_label = gtk_label_new(_("modes")); GtkWidget *timer_label = gtk_label_new(_("timer (s)")); GtkWidget *count_label = gtk_label_new(_("count")); GtkWidget *brackets_label = gtk_label_new(_("brackets")); GtkWidget *steps_label = gtk_label_new(_("bkt. steps")); gtk_widget_set_halign(GTK_WIDGET(modes_label), GTK_ALIGN_START); gtk_widget_set_halign(GTK_WIDGET(timer_label), GTK_ALIGN_START); gtk_widget_set_halign(GTK_WIDGET(count_label), GTK_ALIGN_START); gtk_widget_set_halign(GTK_WIDGET(brackets_label), GTK_ALIGN_START); gtk_widget_set_halign(GTK_WIDGET(steps_label), GTK_ALIGN_START); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(modes_label), 0, lib->gui.rows++, 1, 1); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(timer_label), 0, lib->gui.rows++, 1, 1); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(count_label), 0, lib->gui.rows++, 1, 1); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(brackets_label), 0, lib->gui.rows++, 1, 1); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(steps_label), 0, lib->gui.rows++, 1, 1); // Capture modes buttons lib->gui.toggle_timer = DTGTK_TOGGLEBUTTON(dtgtk_togglebutton_new(dtgtk_cairo_paint_timer, 0, NULL)); lib->gui.toggle_sequence = DTGTK_TOGGLEBUTTON(dtgtk_togglebutton_new(dtgtk_cairo_paint_filmstrip, 0, NULL)); lib->gui.toggle_bracket = DTGTK_TOGGLEBUTTON(dtgtk_togglebutton_new(dtgtk_cairo_paint_bracket, 0, NULL)); hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); gtk_box_pack_start(hbox, GTK_WIDGET(lib->gui.toggle_timer), TRUE, TRUE, 0); gtk_box_pack_start(hbox, GTK_WIDGET(lib->gui.toggle_sequence), TRUE, TRUE, 0); gtk_box_pack_start(hbox, GTK_WIDGET(lib->gui.toggle_bracket), TRUE, TRUE, 0); gtk_grid_attach_next_to(GTK_GRID(self->widget), GTK_WIDGET(hbox), GTK_WIDGET(modes_label), GTK_POS_RIGHT, 1, 1); lib->gui.timer = gtk_spin_button_new_with_range(1, 60, 1); lib->gui.count = gtk_spin_button_new_with_range(1, 9999, 1); lib->gui.brackets = gtk_spin_button_new_with_range(1, 5, 1); lib->gui.steps = gtk_spin_button_new_with_range(1, 9, 1); gtk_grid_attach_next_to(GTK_GRID(self->widget), GTK_WIDGET(lib->gui.timer), GTK_WIDGET(timer_label), GTK_POS_RIGHT, 1, 1); gtk_grid_attach_next_to(GTK_GRID(self->widget), GTK_WIDGET(lib->gui.count), GTK_WIDGET(count_label), GTK_POS_RIGHT, 1, 1); gtk_grid_attach_next_to(GTK_GRID(self->widget), GTK_WIDGET(lib->gui.brackets), GTK_WIDGET(brackets_label), GTK_POS_RIGHT, 1, 1); gtk_grid_attach_next_to(GTK_GRID(self->widget), GTK_WIDGET(lib->gui.steps), GTK_WIDGET(steps_label), GTK_POS_RIGHT, 1, 1); lib->gui.button1 = dt_action_button_new(self, N_("capture image(s)"), _capture_button_clicked, lib, NULL, 0, 0); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(lib->gui.button1), 0, lib->gui.rows++, 2, 1); gtk_widget_set_tooltip_text(GTK_WIDGET(lib->gui.toggle_timer), _("toggle delayed capture mode")); gtk_widget_set_tooltip_text(GTK_WIDGET(lib->gui.toggle_sequence), _("toggle sequenced capture mode")); gtk_widget_set_tooltip_text(GTK_WIDGET(lib->gui.toggle_bracket), _("toggle bracketed capture mode")); gtk_widget_set_tooltip_text(GTK_WIDGET(lib->gui.timer), _("the count of seconds before actually doing a capture")); gtk_widget_set_tooltip_text(GTK_WIDGET(lib->gui.count), _("the amount of images to capture in a sequence,\nyou can use this in conjunction with " "delayed mode to create stop-motion sequences")); gtk_widget_set_tooltip_text(GTK_WIDGET(lib->gui.brackets), _("the amount of brackets on each side of centered shoot, amount of images = (brackets*2) + 1")); gtk_widget_set_tooltip_text(GTK_WIDGET(lib->gui.steps), _("the amount of steps per bracket, steps is camera configurable and usually 3 steps per " "stop\nwith other words, 3 steps is 1EV exposure step between brackets")); g_signal_connect(G_OBJECT(lib->gui.toggle_timer), "clicked", G_CALLBACK(_toggle_capture_mode_clicked), lib); g_signal_connect(G_OBJECT(lib->gui.toggle_sequence), "clicked", G_CALLBACK(_toggle_capture_mode_clicked), lib); g_signal_connect(G_OBJECT(lib->gui.toggle_bracket), "clicked", G_CALLBACK(_toggle_capture_mode_clicked), lib); gtk_widget_set_sensitive(GTK_WIDGET(lib->gui.timer), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(lib->gui.count), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(lib->gui.brackets), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(lib->gui.steps), FALSE); // Camera settings label = dt_ui_section_label_new(C_("section", "properties")); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(label), 0, lib->gui.rows++, 2, 1); lib->gui.prop_start = lib->gui.rows -1; lib->gui.prop_end = lib->gui.rows; // User specified properties label = dt_ui_section_label_new(C_("section", "additional properties")); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(label), 0, lib->gui.rows++, 2, 1); label = gtk_label_new(_("label")); gtk_widget_set_halign(label, GTK_ALIGN_START); lib->gui.plabel = dt_ui_entry_new(0); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(label), 0, lib->gui.rows++, 1, 1); gtk_grid_attach_next_to(GTK_GRID(self->widget), GTK_WIDGET(lib->gui.plabel), GTK_WIDGET(label), GTK_POS_RIGHT, 1, 1); hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); label = gtk_label_new(_("property")); gtk_widget_set_halign(label, GTK_ALIGN_START); GtkWidget *widget = gtk_button_new_with_label("O"); g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(_show_property_popupmenu_clicked), lib); lib->gui.pname = dt_ui_entry_new(0); gtk_box_pack_start(hbox, GTK_WIDGET(lib->gui.pname), TRUE, TRUE, 0); gtk_box_pack_start(hbox, GTK_WIDGET(widget), FALSE, FALSE, 0); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(label), 0, lib->gui.rows++, 1, 1); gtk_grid_attach_next_to(GTK_GRID(self->widget), GTK_WIDGET(hbox), GTK_WIDGET(label), GTK_POS_RIGHT, 1, 1); widget = gtk_button_new_with_label(_("add user property")); g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(_add_property_button_clicked), lib); gtk_widget_show(widget); gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(widget), 0, lib->gui.rows++, 2, 1); } void gui_cleanup(dt_lib_module_t *self) { dt_lib_camera_t *lib = self->data; free(lib->data.listener); lib->data.listener = NULL; free(self->data); self->data = NULL; } void view_enter(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view) { dt_lib_camera_t *lib = self->data; // Add all camera properties to the widget dt_lib_camera_property_t *prop; if((prop = _lib_property_add_new(lib, _("program"), "expprogram")) != NULL) _lib_property_add_to_gui(prop, lib); if((prop = _lib_property_add_new(lib, _("focus mode"), "focusmode")) != NULL) _lib_property_add_to_gui(prop, lib); else if((prop = _lib_property_add_new(lib, _("focus mode"), "drivemode")) != NULL) _lib_property_add_to_gui(prop, lib); if((prop = _lib_property_add_new(lib, _("aperture"), "f-number")) != NULL) _lib_property_add_to_gui(prop, lib); else if((prop = _lib_property_add_new(lib, _("aperture"), "aperture")) != NULL) // for Canon cameras _lib_property_add_to_gui(prop, lib); if((prop = _lib_property_add_new(lib, _("focal length"), "focallength")) != NULL) _lib_property_add_to_gui(prop, lib); if((prop = _lib_property_add_new(lib, _("shutterspeed2"), "shutterspeed2")) != NULL) _lib_property_add_to_gui(prop, lib); else if((prop = _lib_property_add_new(lib, _("shutterspeed"), "shutterspeed")) != NULL) // Canon, again _lib_property_add_to_gui(prop, lib); if((prop = _lib_property_add_new(lib, _("ISO"), "iso")) != NULL) _lib_property_add_to_gui(prop, lib); if((prop = _lib_property_add_new(lib, _("WB"), "whitebalance")) != NULL) _lib_property_add_to_gui(prop, lib); if((prop = _lib_property_add_new(lib, _("quality"), "imagequality")) != NULL) _lib_property_add_to_gui(prop, lib); if((prop = _lib_property_add_new(lib, _("size"), "imagesize")) != NULL) _lib_property_add_to_gui(prop, lib); // Add user widgets GSList *options = dt_conf_all_string_entries("plugins/capture/tethering/properties"); if(options) { for(GSList *item = options; item; item = g_slist_next(item)) { dt_conf_string_entry_t *entry = item->data; // Get the label from key char *p = entry->key; const char *end = entry->key + strlen(entry->key); while(p++ < end) if(*p == '_') *p = ' '; if((prop = _lib_property_add_new(lib, entry->key, entry->value)) != NULL) { prop->user_defined = TRUE; _lib_property_add_to_gui(prop, lib); } else { prop = _lib_property_add_stub(lib, entry->key, entry->value); _lib_property_add_to_gui(prop, lib); } } g_slist_free_full(options, dt_conf_string_entry_free); } // Build the property menu (we do it now because it needs an actual camera) dt_camctl_camera_build_property_menu(darktable.camctl, NULL, &lib->gui.properties_menu, G_CALLBACK(_property_choice_callback), lib); // Register listener dt_camctl_register_listener(darktable.camctl, lib->data.listener); dt_camctl_tether_mode(darktable.camctl, NULL, TRUE); // Get camera model name lib->data.camera_model = dt_camctl_camera_get_model(darktable.camctl, NULL); } void view_leave(struct dt_lib_module_t *self, struct dt_view_t *old_view, struct dt_view_t *new_view) { dt_lib_camera_t *lib = self->data; // Remove listener from camera control dt_camctl_tether_mode(darktable.camctl, NULL, FALSE); dt_camctl_unregister_listener(darktable.camctl, lib->data.listener); g_clear_object(&lib->gui.properties_menu); // Remove all properties while(lib->gui.prop_end > lib->gui.prop_start +1) { gtk_grid_remove_row(lib->gui.main_grid,lib->gui.prop_start +1); lib->gui.rows--; lib->gui.prop_end--; } // No need to free widgets, they are freed when the line of the grid is destroyed g_list_free_full(lib->gui.properties,_lib_property_free); lib->gui.properties = NULL; } // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; // clang-format on