diff --git a/data/themes/darktable.css b/data/themes/darktable.css index 34b27036d931..64c76d533153 100644 --- a/data/themes/darktable.css +++ b/data/themes/darktable.css @@ -916,6 +916,16 @@ menuitem > arrow margin: 0.2em 0; } +/* the "show guides" checkbox, which is the one that carries an icon of its + own and is appended to modules that know nothing about it. Left moves its + tick box and label in from the start of the row, right pulls its icon in + from the end, and neither touches any other checkbox */ +.dt_guides_toggle +{ + padding-left: 0.65em; + padding-right: 0.65em; +} + /* and set menuitem related to sort of combobox like metadata presets in import module */ menuitem label, #view-dropdown button, diff --git a/src/bauhaus/bauhaus.c b/src/bauhaus/bauhaus.c index a718508aae81..368ad5ae2ad3 100644 --- a/src/bauhaus/bauhaus.c +++ b/src/bauhaus/bauhaus.c @@ -919,6 +919,7 @@ void dt_bauhaus_load_theme() // around it gint min_width = 0, min_height = 0; GtkBorder check_padding = { 0 }, check_border = { 0 }; + bh->check_margin = (GtkBorder){ 0 }; if(bh->check_context) { const GtkStateFlags check_state = gtk_style_context_get_state(bh->check_context); @@ -928,7 +929,14 @@ void dt_bauhaus_load_theme() NULL); gtk_style_context_get_padding(bh->check_context, check_state, &check_padding); gtk_style_context_get_border(bh->check_context, check_state, &check_border); + // gtk lays a check button out with these around the indicator, and + // gtk_render_check() does not apply them, so the space in front of the + // box and between the box and the label are ours to add + gtk_style_context_get_margin(bh->check_context, check_state, &bh->check_margin); } + // only the gap between the box and the label is used, and never less than + // the spacing the other widget types keep between their own parts + bh->check_margin.right = MAX(bh->check_margin.right, INNER_PADDING); const gint css_width = min_width + check_padding.left + check_padding.right + check_border.left + check_border.right; const gint css_height = min_height + check_padding.top + check_padding.bottom @@ -1168,6 +1176,25 @@ void dt_bauhaus_widget_set_label_ellipsize(GtkWidget *widget, gtk_widget_queue_draw(GTK_WIDGET(w)); } +void dt_bauhaus_widget_get_quad_rect(GtkWidget *widget, + GdkRectangle *rect) +{ + dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget); + + GtkAllocation allocation; + gtk_widget_get_allocation(widget, &allocation); + + // the quad is the last thing on the row, drawn INNER_PADDING * 4 into the + // width _widget_get_quad_width() reserves for it at the end + const int right = allocation.width - w->margin.right - w->padding.right; + const int top = w->margin.top + w->padding.top; + + rect->width = darktable.bauhaus->quad_width; + rect->height = allocation.height - top - w->margin.bottom - w->padding.bottom; + rect->x = right - _widget_get_quad_width(w) + INNER_PADDING * 4; + rect->y = top; +} + const char* dt_bauhaus_widget_get_label(GtkWidget *widget) { const dt_bauhaus_widget_t *w = DT_BAUHAUS_WIDGET(widget); @@ -1685,10 +1712,6 @@ GtkWidget *dt_bauhaus_toggle_from_widget(dt_bauhaus_widget_t *w, d->active = FALSE; d->defpos = FALSE; - // the tick box is drawn in front of the label, so the column that the - // other types keep free on the right would only be wasted width here - w->show_quad = FALSE; - gtk_widget_set_name(GTK_WIDGET(w), "bauhaus-toggle"); return GTK_WIDGET(w); @@ -2972,10 +2995,12 @@ static gboolean _widget_draw(GtkWidget *widget, break; case DT_BAUHAUS_TOGGLE: { - // box first, then the label beside it, so the pair reads as one - // control the way a check button does + // the box starts where the other types start their label, so that a + // checkbox lines up with them down the left of a module, and the + // label follows it after the gap the css keeps between a check + // button's indicator and its own label const float size = _toggle_box_size(); - const float indent = size + INNER_PADDING * 2; + const float indent = size + darktable.bauhaus->check_margin.right; _draw_toggle_box(w, cr, 0, h3, size); gchar *label_text = _build_label(w); @@ -3058,7 +3083,7 @@ static gint _natural_width(GtkWidget *widget, else if(w->type == DT_BAUHAUS_TOGGLE) { // the box sits in front of the label rather than in the quad column - natural_size += _toggle_box_size() + INNER_PADDING * 2; + natural_size += _toggle_box_size() + darktable.bauhaus->check_margin.right; } else { diff --git a/src/bauhaus/bauhaus.h b/src/bauhaus/bauhaus.h index 9b63ca1ab69d..6ff0485d5015 100644 --- a/src/bauhaus/bauhaus.h +++ b/src/bauhaus/bauhaus.h @@ -133,6 +133,7 @@ typedef struct dt_bauhaus_t // that toggles are drawn by the theme rather than from hardcoded colours GtkStyleContext *check_context; float check_size; // side of a toggle's tick box + GtkBorder check_margin; // space the css keeps around it // initialise or connect accelerators in set_label int skip_accel; @@ -186,6 +187,10 @@ void dt_bauhaus_widget_set_label_text(GtkWidget *widget, // instead, so that the value survives void dt_bauhaus_widget_set_label_ellipsize(GtkWidget *widget, const PangoEllipsizeMode ellipsize); +// where the quad is drawn, in widget coordinates, for anchoring a popup to +// the icon rather than to the whole widget +void dt_bauhaus_widget_get_quad_rect(GtkWidget *widget, + GdkRectangle *rect); const char* dt_bauhaus_widget_get_label(GtkWidget *widget); void dt_bauhaus_widget_hide_label(GtkWidget *widget); void dt_bauhaus_widget_set_show_extended_label(GtkWidget *widget, diff --git a/src/gui/guides.c b/src/gui/guides.c index 6a9320c9200e..1e73d096ac5f 100644 --- a/src/gui/guides.c +++ b/src/gui/guides.c @@ -22,6 +22,7 @@ #include "common/darktable.h" #include "common/math.h" #include "dtgtk/button.h" +#include "gui/gtk.h" #include "gui/guides.h" #define DEFAULT_GUIDE_NAME "rules of thirds" @@ -1006,7 +1007,7 @@ static void _settings_autoshow_change2(GtkWidget *combo, { DT_GUARD_GUI_UPDATE(); gchar *key = _conf_get_path(module->op, "autoshow", NULL); - dt_conf_set_bool(key, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(combo))); + dt_conf_set_bool(key, dt_bauhaus_toggle_get(combo)); g_free(key); dt_control_queue_redraw_center(); } @@ -1017,6 +1018,12 @@ static void _settings_autoshow_menu(GtkWidget *button, GtkWidget *popover = darktable.view_manager->guides_popover; gtk_popover_set_relative_to(GTK_POPOVER(popover), button); + // the icon is this widget's quad, so point at that rather than letting the + // popup aim at the middle of the whole checkbox + GdkRectangle quad; + dt_bauhaus_widget_get_quad_rect(button, &quad); + gtk_popover_set_pointing_to(GTK_POPOVER(popover), &quad); + g_object_set(G_OBJECT(popover), "transitions-enabled", FALSE, NULL); dt_guides_update_popover_values(); @@ -1029,38 +1036,48 @@ void dt_guides_init_module_widget(GtkWidget *iopw, { if(!(module->flags() & IOP_FLAGS_GUIDES_WIDGET)) return; - GtkWidget *cb = module->guides_combo = gtk_check_button_new_with_label(_("show guides")); - gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(cb))), PANGO_ELLIPSIZE_START); + // no field: whether the overlay is shown is a per module preference kept + // in conf, not one of the module's parameters + GtkWidget *cb = module->guides_combo = dt_bauhaus_toggle_new(module); + dt_bauhaus_widget_set_label(cb, NULL, N_("show guides")); + // this row is the only checkbox that carries an icon of its own, and it is + // added to the bottom of modules whose other widgets it knows nothing + // about, so give it a class of its own to be spaced by rather than + // borrowing the one every checkbox in every module is drawn with + dt_gui_add_class(cb, "dt_guides_toggle"); + dt_bauhaus_widget_set_label_ellipsize(cb, PANGO_ELLIPSIZE_START); gchar *key = _conf_get_path(module->op, "autoshow", NULL); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb), dt_conf_get_bool(key)); + dt_bauhaus_toggle_set(cb, dt_conf_get_bool(key)); g_free(key); - g_signal_connect(G_OBJECT(cb), "toggled", + g_signal_connect(G_OBJECT(cb), "value-changed", G_CALLBACK(_settings_autoshow_change2), module); gtk_widget_set_tooltip_text(cb, _("show guide overlay when this module has focus")); - GtkWidget *ic = dtgtk_button_new(dtgtk_cairo_paint_grid, 0, NULL); - gtk_widget_set_tooltip_text - (ic, _("change global guide settings\nnote that these settings are applied globally " - "and will impact any module that shows guide overlays")); - g_signal_connect(G_OBJECT(ic), "clicked", - G_CALLBACK(_settings_autoshow_menu), module); - GtkWidget *box = dt_gui_hbox(dt_gui_expand(cb), ic); + // the settings icon goes in the widget's own quad, which is the column the + // sliders and comboboxes above draw theirs in, rather than in a box beside + // the widget where it would sit further out than all of them + dt_bauhaus_widget_set_quad + (cb, module, dtgtk_cairo_paint_grid, FALSE, _settings_autoshow_menu, + _("change global guide settings\nnote that these settings are applied globally " + "and will impact any module that shows guide overlays")); + // we hide it if the preference is set to "off" - gtk_widget_set_no_show_all(box, TRUE); + gtk_widget_set_no_show_all(cb, TRUE); gtk_widget_show(cb); - gtk_widget_show(ic); - dt_gui_box_add(iopw, box); + dt_gui_box_add(iopw, cb); } void dt_guides_update_module_widget(const dt_iop_module_t *module) { if(!module->guides_combo) return; - GtkWidget *box = gtk_widget_get_parent(module->guides_combo); - gtk_widget_set_visible(box, dt_conf_get_bool("plugins/darkroom/show_guides_in_ui")); + // the checkbox is added to the module box directly, there is no wrapper + // to hide any more + gtk_widget_set_visible(module->guides_combo, + dt_conf_get_bool("plugins/darkroom/show_guides_in_ui")); } void dt_guides_update_popover_values() diff --git a/src/iop/channelmixerrgb.c b/src/iop/channelmixerrgb.c index 609fd69600a4..0dc94dce451c 100644 --- a/src/iop/channelmixerrgb.c +++ b/src/iop/channelmixerrgb.c @@ -3784,7 +3784,7 @@ void gui_update(dt_iop_module_t *self) gboolean use_mixing = TRUE; if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/use_mixing")) use_mixing = dt_conf_get_bool("darkroom/modules/channelmixerrgb/use_mixing"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->use_mixing), use_mixing); + dt_bauhaus_toggle_set(g->use_mixing, use_mixing); float lightness = 50.f; if(dt_conf_key_exists("darkroom/modules/channelmixerrgb/lightness")) @@ -3955,8 +3955,7 @@ static void _spot_settings_changed_callback(GtkWidget *slider, Lch_target[0] = dt_bauhaus_slider_get(g->lightness_spot); Lch_target[1] = dt_bauhaus_slider_get(g->chroma_spot); Lch_target[2] = dt_bauhaus_slider_get(g->hue_spot) / 360.f; - const gboolean use_mixing = - gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->use_mixing)); + const gboolean use_mixing = dt_bauhaus_toggle_get(g->use_mixing); dt_iop_gui_leave_critical_section(self); // Save the color on change @@ -4191,8 +4190,7 @@ static void _auto_set_illuminant(dt_iop_module_t *self, g_free(str); const dt_spot_mode_t mode = dt_bauhaus_combobox_get(g->spot_mode); - const gboolean use_mixing = - gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->use_mixing)); + const gboolean use_mixing = dt_bauhaus_toggle_get(g->use_mixing); // build the channel mixing matrix - keep in synch with commit_params() dt_colormatrix_t MIX = { { 0.f } }; @@ -4553,15 +4551,15 @@ void gui_init(dt_iop_module_t *self) G_CALLBACK(_spot_settings_changed_callback), self); const gchar *label = N_("take channel mixing into account"); - g->use_mixing = gtk_check_button_new_with_label(_(label)); - dt_action_define_iop(self, N_("mapping"), label, g->use_mixing, &dt_action_def_toggle); - gtk_label_set_ellipsize - (GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->use_mixing))), PANGO_ELLIPSIZE_END); + // no field: this setting belongs to the spot mapping workflow and is kept + // in conf across images, rather than being part of the module's params + g->use_mixing = dt_bauhaus_toggle_new(self); + dt_bauhaus_widget_set_label(g->use_mixing, N_("mapping"), label); gtk_widget_set_tooltip_text (g->use_mixing, _("compute the target by taking the channel mixing into account.\n" "if disabled, only the CAT is considered.")); - g_signal_connect(G_OBJECT(g->use_mixing), "toggled", + g_signal_connect(G_OBJECT(g->use_mixing), "value-changed", G_CALLBACK(_spot_settings_changed_callback), self); g->origin_spot = gtk_drawing_area_new(); diff --git a/src/iop/colorharmonizer.c b/src/iop/colorharmonizer.c index 9d14e133b877..a644f9926ac1 100644 --- a/src/iop/colorharmonizer.c +++ b/src/iop/colorharmonizer.c @@ -909,10 +909,10 @@ static void _on_vectorscope_harmony_changed(const dt_color_harmony_guide_t *guid _apply_harmony_guide(self, self->params, self->gui_data, guide); } -static void _sync_to_vectorscope_toggled(GtkToggleButton *button, dt_iop_module_t *self) +static void _sync_to_vectorscope_toggled(GtkWidget *button, dt_iop_module_t *self) { dt_iop_colorharmonizer_gui_data_t *g = self->gui_data; - const gboolean active = gtk_toggle_button_get_active(button); + const gboolean active = dt_bauhaus_toggle_get(button); dt_conf_set_bool("plugins/darkroom/colorharmonizer/sync_to_vectorscope", active); if(active) @@ -999,7 +999,7 @@ static void _custom_hue_changed(GtkWidget *widget, dt_iop_module_t *self) dt_dev_add_history_item(self->dev, self, TRUE); if(g->sync_to_vectorscope - && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->sync_to_vectorscope))) + && dt_bauhaus_toggle_get(g->sync_to_vectorscope)) _push_to_vectorscope(self); } @@ -1094,7 +1094,7 @@ static void _sync_vectorscope_if_enabled(dt_iop_module_t *self, GtkWidget *widget) { if(!g->sync_to_vectorscope - || !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->sync_to_vectorscope))) + || !dt_bauhaus_toggle_get(g->sync_to_vectorscope)) return; if(self->enabled) @@ -1373,7 +1373,7 @@ static void _auto_detect_callback(GtkButton *button, dt_iop_module_t *self) dt_dev_add_history_item(self->dev, self, TRUE); if(g->sync_to_vectorscope - && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->sync_to_vectorscope))) + && dt_bauhaus_toggle_get(g->sync_to_vectorscope)) _push_to_vectorscope(self); } @@ -1510,9 +1510,11 @@ void gui_init(dt_iop_module_t *self) GtkWidget *sync_row = dt_gui_hbox(); - g->sync_to_vectorscope = gtk_check_button_new_with_label(_("vectorscope two-way sync")); - gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->sync_to_vectorscope))), - PANGO_ELLIPSIZE_END); + // no field: this only says whether the vectorscope overlay follows the + // module, it is not one of the module's parameters + g->sync_to_vectorscope = dt_bauhaus_toggle_new(self); + dt_bauhaus_widget_set_label(g->sync_to_vectorscope, NULL, + N_("vectorscope two-way sync")); gtk_widget_set_tooltip_text(g->sync_to_vectorscope, _("when enabled, the vectorscope harmony overlay is kept in sync with\n" "the harmony rule and anchor hue controls in the module.\n" @@ -1521,9 +1523,9 @@ void gui_init(dt_iop_module_t *self) const char *conf_key = "plugins/darkroom/colorharmonizer/sync_to_vectorscope"; if(!dt_conf_key_exists(conf_key)) dt_conf_set_bool(conf_key, TRUE); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->sync_to_vectorscope), dt_conf_get_bool(conf_key)); + dt_bauhaus_toggle_set(g->sync_to_vectorscope, dt_conf_get_bool(conf_key)); - g_signal_connect(G_OBJECT(g->sync_to_vectorscope), "toggled", + g_signal_connect(G_OBJECT(g->sync_to_vectorscope), "value-changed", G_CALLBACK(_sync_to_vectorscope_toggled), self); g->set_from_vectorscope = dtgtk_button_new(dtgtk_cairo_paint_refresh, CPF_NONE, NULL); @@ -1542,7 +1544,7 @@ void gui_init(dt_iop_module_t *self) // "set from vectorscope" is redundant when sync is active; disable it initially // (sync checkbox starts checked, so the button starts insensitive) gtk_widget_set_sensitive(g->set_from_vectorscope, - !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->sync_to_vectorscope))); + !dt_bauhaus_toggle_get(g->sync_to_vectorscope)); g->histogram_valid = FALSE; g_mutex_init(&g->histogram_lock); @@ -1636,7 +1638,7 @@ void gui_focus(dt_iop_module_t *self, gboolean in) dt_iop_colorharmonizer_gui_data_t *g = self->gui_data; if(!g) return; const gboolean sync = g->sync_to_vectorscope - && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->sync_to_vectorscope)); + && dt_bauhaus_toggle_get(g->sync_to_vectorscope); if(in) { if(sync && self->enabled) diff --git a/src/iop/colorzones.c b/src/iop/colorzones.c index 503e502e1360..e3ab99c47a8f 100644 --- a/src/iop/colorzones.c +++ b/src/iop/colorzones.c @@ -2364,7 +2364,7 @@ static void _edit_by_area_callback(GtkWidget *widget, DT_GUARD_GUI_UPDATE(); dt_iop_colorzones_gui_data_t *g = self->gui_data; - g->edit_by_area = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + g->edit_by_area = dt_bauhaus_toggle_get(widget); gtk_widget_queue_draw(GTK_WIDGET(g->area)); } @@ -2669,13 +2669,14 @@ void gui_init(dt_iop_module_t *self) // edit by area gchar *label = N_("edit by area"); - g->chk_edit_by_area = gtk_check_button_new_with_label(_(label)); - dt_action_define_iop(self, NULL, label, g->chk_edit_by_area, &dt_action_def_toggle); - gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->chk_edit_by_area))), - PANGO_ELLIPSIZE_START); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->chk_edit_by_area), g->edit_by_area); + // no field: this selects an editing mode for the curve widget below, it + // is not one of the module's parameters + g->chk_edit_by_area = dt_bauhaus_toggle_new(self); + dt_bauhaus_widget_set_label(g->chk_edit_by_area, NULL, label); + dt_bauhaus_widget_set_label_ellipsize(g->chk_edit_by_area, PANGO_ELLIPSIZE_START); + dt_bauhaus_toggle_set(g->chk_edit_by_area, g->edit_by_area); gtk_widget_set_tooltip_text(g->chk_edit_by_area, _("edit the curve nodes by area")); - g_signal_connect(G_OBJECT(g->chk_edit_by_area), "toggled", + g_signal_connect(G_OBJECT(g->chk_edit_by_area), "value-changed", G_CALLBACK(_edit_by_area_callback), self); // display selection diff --git a/src/iop/lens.cc b/src/iop/lens.cc index e326aef53669..389580b59cac 100644 --- a/src/iop/lens.cc +++ b/src/iop/lens.cc @@ -4328,7 +4328,7 @@ void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous) img->exif_correction_type != CORRECTION_TYPE_DNG && p->md_version >= DT_IOP_LENS_EMBEDDED_METADATA_VERSION_2; - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->use_latest_md_algo), + dt_bauhaus_toggle_set(g->use_latest_md_algo, FALSE); gtk_widget_set_visible (g->use_latest_md_algo, @@ -4515,15 +4515,18 @@ void gui_init(dt_iop_module_t *self) GtkWidget *only_vig = dt_gui_vbox(); /* embedded metadata widgets */ - g->use_latest_md_algo = - gtk_check_button_new_with_label(_("use latest algorithm")); + // no field: this only switches the module over to the newer correction + // algorithm, it is not one of the module's parameters + g->use_latest_md_algo = dt_bauhaus_toggle_new(self); + dt_bauhaus_widget_set_label(g->use_latest_md_algo, NULL, + N_("use latest algorithm")); gtk_widget_set_tooltip_text (g->use_latest_md_algo, _("you're using an old version of the algorithm.\n" "once enabled, you won't be able to\n" "return back to old algorithm.")); GtkWidget *box_md = dt_gui_vbox(g->use_latest_md_algo); - g_signal_connect(G_OBJECT(g->use_latest_md_algo), "toggled", + g_signal_connect(G_OBJECT(g->use_latest_md_algo), "value-changed", G_CALLBACK(_use_latest_md_algo_callback), self); // we put fine-tuning values under an expander