/* * Copyright © 2019 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: Matthias Clasen */ #include "config.h" #include "constraint-editor.h" #include "constraint-view.h" struct _ConstraintEditor { GtkWidget parent_instance; GtkWidget *grid; GtkWidget *target; GtkWidget *target_attr; GtkWidget *relation; GtkWidget *source; GtkWidget *source_attr; GtkWidget *multiplier; GtkWidget *constant; GtkWidget *strength; GtkWidget *preview; GtkWidget *button; GtkConstraint *constraint; GListModel *model; gboolean constructed; }; enum { PROP_MODEL = 1, PROP_CONSTRAINT, LAST_PROP }; static GParamSpec *pspecs[LAST_PROP]; enum { DONE, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; G_DEFINE_TYPE(ConstraintEditor, constraint_editor, GTK_TYPE_WIDGET); static const char * get_target_name (GtkConstraintTarget *target) { if (target == NULL) return "Super"; else if (GTK_IS_WIDGET (target)) return gtk_widget_get_name (GTK_WIDGET (target)); else if (GTK_IS_CONSTRAINT_GUIDE (target)) return gtk_constraint_guide_get_name (GTK_CONSTRAINT_GUIDE (target)); else return ""; } static void constraint_target_combo (GListModel *model, GtkWidget *combo, gboolean is_source) { GtkStringList *targets; int i; targets = gtk_string_list_new (NULL); gtk_string_list_append (targets, "Super"); if (model) { for (i = 0; i < g_list_model_get_n_items (model); i++) { GObject *item = g_list_model_get_object (model, i); if (GTK_IS_CONSTRAINT (item)) continue; gtk_string_list_append (targets, get_target_name (GTK_CONSTRAINT_TARGET (item))); g_object_unref (item); } } gtk_drop_down_set_model (GTK_DROP_DOWN (combo), G_LIST_MODEL (targets)); g_object_unref (targets); } static gpointer get_target (GListModel *model, const char *id) { int i; if (id == NULL) return NULL; if (strcmp ("Super", id) == 0) return NULL; for (i = 0; i < g_list_model_get_n_items (model); i++) { GObject *item = g_list_model_get_object (model, i); g_object_unref (item); if (GTK_IS_CONSTRAINT (item)) continue; else if (GTK_IS_WIDGET (item)) { if (strcmp (id, gtk_widget_get_name (GTK_WIDGET (item))) == 0) return item; } else if (GTK_IS_CONSTRAINT_GUIDE (item)) { if (strcmp (id, gtk_constraint_guide_get_name (GTK_CONSTRAINT_GUIDE (item))) == 0) return item; } } return NULL; } static void select_target (GtkDropDown *combo, const char *target_name) { GListModel *model = gtk_drop_down_get_model (combo); for (unsigned int i = 0; i < g_list_model_get_n_items (model); i++) { GtkStringObject *s = g_list_model_get_item (model, i); g_object_unref (s); if (strcmp (target_name, gtk_string_object_get_string (s)) == 0) { gtk_drop_down_set_selected (GTK_DROP_DOWN (combo), i); return; } } } static GtkConstraintAttribute get_attr (unsigned int id) { switch (id) { case 0: return GTK_CONSTRAINT_ATTRIBUTE_NONE; case 1: return GTK_CONSTRAINT_ATTRIBUTE_LEFT; case 2: return GTK_CONSTRAINT_ATTRIBUTE_RIGHT; case 3: return GTK_CONSTRAINT_ATTRIBUTE_TOP; case 4: return GTK_CONSTRAINT_ATTRIBUTE_BOTTOM; case 5: return GTK_CONSTRAINT_ATTRIBUTE_START; case 6: return GTK_CONSTRAINT_ATTRIBUTE_END; case 7: return GTK_CONSTRAINT_ATTRIBUTE_WIDTH; case 8: return GTK_CONSTRAINT_ATTRIBUTE_HEIGHT; case 9: return GTK_CONSTRAINT_ATTRIBUTE_CENTER_X; case 10: return GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y; case 11: return GTK_CONSTRAINT_ATTRIBUTE_BASELINE; default: g_assert_not_reached (); } } static unsigned int get_attr_id (GtkConstraintAttribute attr) { switch (attr) { case GTK_CONSTRAINT_ATTRIBUTE_NONE: return 0; case GTK_CONSTRAINT_ATTRIBUTE_LEFT: return 1; case GTK_CONSTRAINT_ATTRIBUTE_RIGHT: return 2; case GTK_CONSTRAINT_ATTRIBUTE_TOP: return 3; case GTK_CONSTRAINT_ATTRIBUTE_BOTTOM: return 4; case GTK_CONSTRAINT_ATTRIBUTE_START: return 5; case GTK_CONSTRAINT_ATTRIBUTE_END: return 6; case GTK_CONSTRAINT_ATTRIBUTE_WIDTH: return 7; case GTK_CONSTRAINT_ATTRIBUTE_HEIGHT: return 8; case GTK_CONSTRAINT_ATTRIBUTE_CENTER_X: return 9; case GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y: return 10; case GTK_CONSTRAINT_ATTRIBUTE_BASELINE: return 11; default: g_assert_not_reached (); } } static const char * get_attr_nick (GtkConstraintAttribute attr) { GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_ATTRIBUTE); GEnumValue *value = g_enum_get_value (class, attr); const char *nick = value->value_nick; g_type_class_unref (class); return nick; } static GtkConstraintRelation get_relation (unsigned int id) { switch (id) { case 0: return GTK_CONSTRAINT_RELATION_LE; case 1: return GTK_CONSTRAINT_RELATION_EQ; case 2: return GTK_CONSTRAINT_RELATION_GE; default: g_assert_not_reached (); } } static unsigned int get_relation_id (GtkConstraintRelation relation) { switch (relation) { case GTK_CONSTRAINT_RELATION_LE: return 0; case GTK_CONSTRAINT_RELATION_EQ: return 1; case GTK_CONSTRAINT_RELATION_GE: return 2; default: g_assert_not_reached (); } } static const char * get_relation_nick (GtkConstraintRelation relation) { GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_RELATION); GEnumValue *value = g_enum_get_value (class, relation); const char *nick = value->value_nick; g_type_class_unref (class); return nick; } static const char * get_relation_display_name (GtkConstraintRelation relation) { switch (relation) { case GTK_CONSTRAINT_RELATION_LE: return "≤"; case GTK_CONSTRAINT_RELATION_EQ: return "="; case GTK_CONSTRAINT_RELATION_GE: return "≥"; default: return "?"; } } static GtkConstraintStrength get_strength (unsigned int id) { switch (id) { case 0: return GTK_CONSTRAINT_STRENGTH_WEAK; case 1: return GTK_CONSTRAINT_STRENGTH_MEDIUM; case 2: return GTK_CONSTRAINT_STRENGTH_STRONG; case 3: return GTK_CONSTRAINT_STRENGTH_REQUIRED; default: g_assert_not_reached (); } } static unsigned int get_strength_id (GtkConstraintStrength strength) { switch (strength) { case GTK_CONSTRAINT_STRENGTH_WEAK: return 0; case GTK_CONSTRAINT_STRENGTH_MEDIUM: return 1; case GTK_CONSTRAINT_STRENGTH_STRONG: return 2; case GTK_CONSTRAINT_STRENGTH_REQUIRED: return 3; default: g_assert_not_reached (); } } static const char * get_strength_nick (GtkConstraintStrength strength) { GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_STRENGTH); GEnumValue *value = g_enum_get_value (class, strength); const char *nick = value->value_nick; g_type_class_unref (class); return nick; } void constraint_editor_serialize_constraint (GString *str, int indent, GtkConstraint *constraint) { const char *target; const char *target_attr; const char *relation; const char *source; const char *source_attr; double multiplier; double constant; const char *strength; target = get_target_name (gtk_constraint_get_target (constraint)); target_attr = get_attr_nick (gtk_constraint_get_target_attribute (constraint)); relation = get_relation_nick (gtk_constraint_get_relation (constraint)); source = get_target_name (gtk_constraint_get_source (constraint)); source_attr = get_attr_nick (gtk_constraint_get_source_attribute (constraint)); multiplier = gtk_constraint_get_multiplier (constraint); constant = gtk_constraint_get_constant (constraint); strength = get_strength_nick (gtk_constraint_get_strength (constraint)); g_string_append_printf (str, "%*s\n", indent, "", strength); } static void create_constraint (GtkButton *button, ConstraintEditor *editor) { gpointer obj; gpointer target; GtkConstraintAttribute target_attr; gpointer source; GtkConstraintAttribute source_attr; GtkConstraintRelation relation; double multiplier; double constant; int strength; GtkConstraint *constraint; obj = gtk_drop_down_get_selected_item (GTK_DROP_DOWN (editor->target)); if (obj) target = get_target (editor->model, gtk_string_object_get_string (GTK_STRING_OBJECT (obj))); else target = NULL; target_attr = get_attr (gtk_drop_down_get_selected (GTK_DROP_DOWN (editor->target_attr))); obj = gtk_drop_down_get_selected_item (GTK_DROP_DOWN (editor->source)); if (obj) source = get_target (editor->model, gtk_string_object_get_string (GTK_STRING_OBJECT (obj))); else source = NULL; source_attr = get_attr (gtk_drop_down_get_selected (GTK_DROP_DOWN(editor->source_attr))); relation = get_relation (gtk_drop_down_get_selected (GTK_DROP_DOWN (editor->relation))); multiplier = g_ascii_strtod (gtk_editable_get_text (GTK_EDITABLE (editor->multiplier)), NULL); constant = g_ascii_strtod (gtk_editable_get_text (GTK_EDITABLE (editor->constant)), NULL); strength = get_strength (gtk_drop_down_get_selected (GTK_DROP_DOWN (editor->strength))); constraint = gtk_constraint_new (target, target_attr, relation, source, source_attr, multiplier, constant, strength); g_signal_emit (editor, signals[DONE], 0, constraint); g_object_unref (constraint); } static void source_attr_changed (ConstraintEditor *editor) { if (get_attr (gtk_drop_down_get_selected (GTK_DROP_DOWN (editor->source_attr))) == GTK_CONSTRAINT_ATTRIBUTE_NONE) { gtk_drop_down_set_selected (GTK_DROP_DOWN (editor->source), GTK_INVALID_LIST_POSITION); gtk_editable_set_text (GTK_EDITABLE (editor->multiplier), ""); gtk_widget_set_sensitive (editor->source, FALSE); gtk_widget_set_sensitive (editor->multiplier, FALSE); } else { gtk_widget_set_sensitive (editor->source, TRUE); gtk_widget_set_sensitive (editor->multiplier, TRUE); gtk_editable_set_text (GTK_EDITABLE (editor->multiplier), "1"); } } char * constraint_editor_constraint_to_string (GtkConstraint *constraint) { GString *str; const char *name; const char *attr; const char *relation; double c, m; str = g_string_new (""); name = get_target_name (gtk_constraint_get_target (constraint)); attr = get_attr_nick (gtk_constraint_get_target_attribute (constraint)); relation = get_relation_display_name (gtk_constraint_get_relation (constraint)); if (name == NULL) name = "[ ]"; g_string_append_printf (str, "%s.%s %s ", name, attr, relation); c = gtk_constraint_get_constant (constraint); attr = get_attr_nick (gtk_constraint_get_source_attribute (constraint)); if (strcmp (attr, "none") != 0) { name = get_target_name (gtk_constraint_get_source (constraint)); m = gtk_constraint_get_multiplier (constraint); if (name == NULL) name = "[ ]"; g_string_append_printf (str, "%s.%s", name, attr); if (m != 1.0) g_string_append_printf (str, " × %g", m); if (c > 0.0) g_string_append_printf (str, " + %g", c); else if (c < 0.0) g_string_append_printf (str, " - %g", -c); } else g_string_append_printf (str, "%g", c); return g_string_free (str, FALSE); } static void update_preview (ConstraintEditor *editor) { GString *str; const char *name; const char *attr; const char *relation; const char *multiplier; const char *constant; double c, m; if (!editor->constructed) return; str = g_string_new (""); name = gtk_string_object_get_string (GTK_STRING_OBJECT (gtk_drop_down_get_selected_item (GTK_DROP_DOWN (editor->target)))); attr = get_attr_nick (get_attr (gtk_drop_down_get_selected ((GTK_DROP_DOWN (editor->target_attr))))); relation = get_relation_nick (get_relation (gtk_drop_down_get_selected (GTK_DROP_DOWN (editor->relation)))); if (name == NULL) name = "[ ]"; g_string_append_printf (str, "%s.%s %s ", name, attr, relation); constant = gtk_editable_get_text (GTK_EDITABLE (editor->constant)); c = g_ascii_strtod (constant, NULL); attr = get_attr_nick (get_attr (gtk_drop_down_get_selected (GTK_DROP_DOWN (editor->source_attr)))); if (strcmp (attr, "none") != 0) { name = gtk_string_object_get_string (GTK_STRING_OBJECT (gtk_drop_down_get_selected_item (GTK_DROP_DOWN (editor->source)))); multiplier = gtk_editable_get_text (GTK_EDITABLE (editor->multiplier)); m = g_ascii_strtod (multiplier, NULL); if (name == NULL) name = "[ ]"; g_string_append_printf (str, "%s.%s", name, attr); if (m != 1.0) g_string_append_printf (str, " × %g", m); if (c > 0.0) g_string_append_printf (str, " + %g", c); else if (c < 0.0) g_string_append_printf (str, " - %g", -c); } else g_string_append_printf (str, "%g", c); gtk_label_set_label (GTK_LABEL (editor->preview), str->str); g_string_free (str, TRUE); } static void update_button (ConstraintEditor *editor) { gpointer obj; const char *target; const char *source; GtkConstraintAttribute source_attr = get_attr (gtk_drop_down_get_selected (GTK_DROP_DOWN (editor->source_attr))); obj = gtk_drop_down_get_selected_item (GTK_DROP_DOWN (editor->target)); target = obj ? gtk_string_object_get_string (GTK_STRING_OBJECT (obj)) : NULL; obj = gtk_drop_down_get_selected_item (GTK_DROP_DOWN (editor->source)); source = obj ? gtk_string_object_get_string (GTK_STRING_OBJECT (obj)) : NULL; if (target && (source || (source_attr == GTK_CONSTRAINT_ATTRIBUTE_NONE))) gtk_widget_set_sensitive (editor->button, TRUE); else gtk_widget_set_sensitive (editor->button, FALSE); } static void constraint_editor_init (ConstraintEditor *editor) { gtk_widget_init_template (GTK_WIDGET (editor)); } static void constraint_editor_constructed (GObject *object) { ConstraintEditor *editor = CONSTRAINT_EDITOR (object); constraint_target_combo (editor->model, editor->target, FALSE); constraint_target_combo (editor->model, editor->source, TRUE); if (editor->constraint) { GtkConstraintTarget *target; GtkConstraintAttribute attr; GtkConstraintRelation relation; GtkConstraintStrength strength; char *val; double multiplier; double constant; target = gtk_constraint_get_target (editor->constraint); select_target (GTK_DROP_DOWN (editor->target), get_target_name (target)); attr = gtk_constraint_get_target_attribute (editor->constraint); gtk_drop_down_set_selected (GTK_DROP_DOWN (editor->target_attr), get_attr_id (attr)); target = gtk_constraint_get_source (editor->constraint); select_target (GTK_DROP_DOWN (editor->source), get_target_name (target)); attr = gtk_constraint_get_source_attribute (editor->constraint); gtk_drop_down_set_selected (GTK_DROP_DOWN (editor->source_attr), get_attr_id (attr)); relation = gtk_constraint_get_relation (editor->constraint); gtk_drop_down_set_selected (GTK_DROP_DOWN (editor->relation), get_relation_id (relation)); multiplier = gtk_constraint_get_multiplier (editor->constraint); val = g_strdup_printf ("%g", multiplier); gtk_editable_set_text (GTK_EDITABLE (editor->multiplier), val); g_free (val); constant = gtk_constraint_get_constant (editor->constraint); val = g_strdup_printf ("%g", constant); gtk_editable_set_text (GTK_EDITABLE (editor->constant), val); g_free (val); strength = gtk_constraint_get_strength (editor->constraint); gtk_drop_down_set_selected (GTK_DROP_DOWN (editor->strength), get_strength_id (strength)); gtk_button_set_label (GTK_BUTTON (editor->button), "Apply"); } else { gtk_drop_down_set_selected (GTK_DROP_DOWN (editor->target_attr), get_attr_id (GTK_CONSTRAINT_ATTRIBUTE_LEFT)); gtk_drop_down_set_selected (GTK_DROP_DOWN (editor->source_attr), get_attr_id (GTK_CONSTRAINT_ATTRIBUTE_LEFT)); gtk_drop_down_set_selected (GTK_DROP_DOWN (editor->relation), get_relation_id (GTK_CONSTRAINT_RELATION_EQ)); gtk_drop_down_set_selected (GTK_DROP_DOWN (editor->strength), get_strength_id (GTK_CONSTRAINT_STRENGTH_REQUIRED)); gtk_editable_set_text (GTK_EDITABLE (editor->multiplier), "1.0"); gtk_editable_set_text (GTK_EDITABLE (editor->constant), "0.0"); gtk_button_set_label (GTK_BUTTON (editor->button), "Create"); } editor->constructed = TRUE; update_preview (editor); update_button (editor); } static void constraint_editor_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { ConstraintEditor *self = CONSTRAINT_EDITOR (object); switch (property_id) { case PROP_MODEL: self->model = g_value_dup_object (value); break; case PROP_CONSTRAINT: self->constraint = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void constraint_editor_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { ConstraintEditor *self = CONSTRAINT_EDITOR (object); switch (property_id) { case PROP_MODEL: g_value_set_object (value, self->model); break; case PROP_CONSTRAINT: g_value_set_object (value, self->constraint); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void constraint_editor_dispose (GObject *object) { ConstraintEditor *self = (ConstraintEditor *)object; g_clear_object (&self->model); g_clear_object (&self->constraint); gtk_widget_dispose_template (GTK_WIDGET (object), CONSTRAINT_EDITOR_TYPE); G_OBJECT_CLASS (constraint_editor_parent_class)->dispose (object); } static void constraint_editor_class_init (ConstraintEditorClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); object_class->constructed = constraint_editor_constructed; object_class->dispose = constraint_editor_dispose; object_class->set_property = constraint_editor_set_property; object_class->get_property = constraint_editor_get_property; pspecs[PROP_CONSTRAINT] = g_param_spec_object ("constraint", "constraint", "constraint", GTK_TYPE_CONSTRAINT, G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY); pspecs[PROP_MODEL] = g_param_spec_object ("model", "model", "model", G_TYPE_LIST_MODEL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, LAST_PROP, pspecs); signals[DONE] = g_signal_new ("done", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GTK_TYPE_CONSTRAINT); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/gtk4/constraint-editor/constraint-editor.ui"); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, grid); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, target); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, target_attr); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, relation); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, source); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, source_attr); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, multiplier); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, constant); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, strength); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, preview); gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, button); gtk_widget_class_bind_template_callback (widget_class, update_preview); gtk_widget_class_bind_template_callback (widget_class, update_button); gtk_widget_class_bind_template_callback (widget_class, create_constraint); gtk_widget_class_bind_template_callback (widget_class, source_attr_changed); } ConstraintEditor * constraint_editor_new (GListModel *model, GtkConstraint *constraint) { return g_object_new (CONSTRAINT_EDITOR_TYPE, "model", model, "constraint", constraint, NULL); }