summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2019-06-30 13:25:30 +0000
committerEmmanuele Bassi <ebassi@gnome.org>2019-07-01 00:10:11 +0100
commit514de0b91a34e90166a2f4a35ff29a0558590f1d (patch)
tree583b8430d0974308f8c91bf645a1b4e000f566a0
parent9a463056d02226a3232dbdceda281a0f4c02a743 (diff)
downloadgtk+-514de0b91a34e90166a2f4a35ff29a0558590f1d.tar.gz
Add a constraint editor demo
This is an initial cut at providing a tool for interactive exploration of constraints.
-rw-r--r--demos/constraint-editor/constraint-editor-application.c94
-rw-r--r--demos/constraint-editor/constraint-editor-application.h28
-rw-r--r--demos/constraint-editor/constraint-editor-window.c354
-rw-r--r--demos/constraint-editor/constraint-editor-window.h34
-rw-r--r--demos/constraint-editor/constraint-editor-window.ui68
-rw-r--r--demos/constraint-editor/constraint-editor.c583
-rw-r--r--demos/constraint-editor/constraint-editor.css12
-rw-r--r--demos/constraint-editor/constraint-editor.gresource.xml9
-rw-r--r--demos/constraint-editor/constraint-editor.h29
-rw-r--r--demos/constraint-editor/constraint-editor.ui180
-rw-r--r--demos/constraint-editor/constraint-view-child.c93
-rw-r--r--demos/constraint-editor/constraint-view-child.h44
-rw-r--r--demos/constraint-editor/constraint-view.c277
-rw-r--r--demos/constraint-editor/constraint-view.h44
-rw-r--r--demos/constraint-editor/guide-editor.c318
-rw-r--r--demos/constraint-editor/guide-editor.h28
-rw-r--r--demos/constraint-editor/guide-editor.ui188
-rw-r--r--demos/constraint-editor/main.c28
-rw-r--r--demos/constraint-editor/meson.build19
-rw-r--r--demos/meson.build1
20 files changed, 2431 insertions, 0 deletions
diff --git a/demos/constraint-editor/constraint-editor-application.c b/demos/constraint-editor/constraint-editor-application.c
new file mode 100644
index 0000000000..09b5d64270
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-application.c
@@ -0,0 +1,94 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "constraint-editor-application.h"
+#include "constraint-editor-window.h"
+
+struct _ConstraintEditorApplication
+{
+ GtkApplication parent_instance;
+};
+
+G_DEFINE_TYPE(ConstraintEditorApplication, constraint_editor_application, GTK_TYPE_APPLICATION);
+
+static void
+constraint_editor_application_init (ConstraintEditorApplication *app)
+{
+}
+
+static void
+quit_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ g_application_quit (G_APPLICATION (data));
+}
+
+static GActionEntry app_entries[] =
+{
+ { "quit", quit_activated, NULL, NULL, NULL }
+};
+
+static void
+constraint_editor_application_startup (GApplication *app)
+{
+ const char *quit_accels[2] = { "<Ctrl>Q", NULL };
+ GtkCssProvider *provider;
+
+ G_APPLICATION_CLASS (constraint_editor_application_parent_class)->startup (app);
+
+ g_action_map_add_action_entries (G_ACTION_MAP (app),
+ app_entries, G_N_ELEMENTS (app_entries),
+ app);
+ gtk_application_set_accels_for_action (GTK_APPLICATION (app), "app.quit", quit_accels);
+
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_resource (provider, "/org/gtk/gtk4/constraint-editor/constraint-editor.css");
+ gtk_style_context_add_provider_for_display (gdk_display_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
+
+static void
+constraint_editor_application_activate (GApplication *app)
+{
+ ConstraintEditorWindow *win;
+
+ win = constraint_editor_window_new (CONSTRAINT_EDITOR_APPLICATION (app));
+ gtk_window_present (GTK_WINDOW (win));
+}
+
+static void
+constraint_editor_application_class_init (ConstraintEditorApplicationClass *class)
+{
+ GApplicationClass *application_class = G_APPLICATION_CLASS (class);
+
+ application_class->startup = constraint_editor_application_startup;
+ application_class->activate = constraint_editor_application_activate;
+}
+
+ConstraintEditorApplication *
+constraint_editor_application_new (void)
+{
+ return g_object_new (CONSTRAINT_EDITOR_APPLICATION_TYPE,
+ "application-id", "org.gtk.gtk4.ConstraintEditor",
+ NULL);
+}
diff --git a/demos/constraint-editor/constraint-editor-application.h b/demos/constraint-editor/constraint-editor-application.h
new file mode 100644
index 0000000000..c7d9fd3048
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-application.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define CONSTRAINT_EDITOR_APPLICATION_TYPE (constraint_editor_application_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintEditorApplication, constraint_editor_application, CONSTRAINT, EDITOR_APPLICATION, GtkApplication)
+
+ConstraintEditorApplication *constraint_editor_application_new (void);
diff --git a/demos/constraint-editor/constraint-editor-window.c b/demos/constraint-editor/constraint-editor-window.c
new file mode 100644
index 0000000000..a8fa33464d
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-window.c
@@ -0,0 +1,354 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "constraint-editor-window.h"
+#include "constraint-view.h"
+#include "constraint-editor.h"
+#include "guide-editor.h"
+
+struct _ConstraintEditorWindow
+{
+ GtkApplicationWindow parent_instance;
+
+ GtkWidget *paned;
+ GtkWidget *view;
+ GtkWidget *list;
+};
+
+G_DEFINE_TYPE(ConstraintEditorWindow, constraint_editor_window, GTK_TYPE_APPLICATION_WINDOW);
+
+gboolean
+constraint_editor_window_load (ConstraintEditorWindow *self,
+ GFile *file)
+{
+ GBytes *bytes;
+
+ bytes = g_file_load_bytes (file, NULL, NULL, NULL);
+ if (bytes == NULL)
+ return FALSE;
+
+ if (!g_utf8_validate (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), NULL))
+ {
+ g_bytes_unref (bytes);
+ return FALSE;
+ }
+
+#if 0
+
+ gtk_text_buffer_get_end_iter (self->text_buffer, &end);
+ gtk_text_buffer_insert (self->text_buffer,
+ &end,
+ g_bytes_get_data (bytes, NULL),
+ g_bytes_get_size (bytes));
+#endif
+
+ g_bytes_unref (bytes);
+
+ return TRUE;
+}
+
+static void
+constraint_editor_window_finalize (GObject *object)
+{
+ //ConstraintEditorWindow *self = (ConstraintEditorWindow *)object;
+
+ G_OBJECT_CLASS (constraint_editor_window_parent_class)->finalize (object);
+}
+
+static int child_counter;
+static int guide_counter;
+
+static void
+add_child (ConstraintEditorWindow *win)
+{
+ char *name;
+
+ child_counter++;
+ name = g_strdup_printf ("Child %d", child_counter);
+ constraint_view_add_child (CONSTRAINT_VIEW (win->view), name);
+ g_free (name);
+}
+
+static void
+add_guide (ConstraintEditorWindow *win)
+{
+ char *name;
+ GtkConstraintGuide *guide;
+
+ guide_counter++;
+ name = g_strdup_printf ("Guide %d", guide_counter);
+ guide = g_object_new (GTK_TYPE_CONSTRAINT_GUIDE, NULL);
+ g_object_set_data_full (G_OBJECT (guide), "name", name, g_free);
+
+ constraint_view_add_guide (CONSTRAINT_VIEW (win->view), guide);
+}
+
+static void
+constraint_editor_done (ConstraintEditor *editor,
+ GtkConstraint *constraint,
+ ConstraintEditorWindow *win)
+{
+ GtkConstraint *old_constraint;
+
+ g_object_get (editor, "constraint", &old_constraint, NULL);
+
+ if (old_constraint)
+ constraint_view_remove_constraint (CONSTRAINT_VIEW (win->view), old_constraint);
+
+ constraint_view_add_constraint (CONSTRAINT_VIEW (win->view), constraint);
+
+ g_clear_object (&old_constraint);
+
+ gtk_widget_destroy (gtk_widget_get_ancestor (GTK_WIDGET (editor), GTK_TYPE_WINDOW));
+}
+
+static void
+edit_constraint (ConstraintEditorWindow *win,
+ GtkConstraint *constraint)
+{
+ GtkWidget *window;
+ ConstraintEditor *editor;
+ GListModel *model;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (win));
+ gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+ if (constraint)
+ gtk_window_set_title (GTK_WINDOW (window), "Edit Constraint");
+ else
+ gtk_window_set_title (GTK_WINDOW (window), "Create Constraint");
+
+ model = constraint_view_get_model (CONSTRAINT_VIEW (win->view));
+
+ editor = constraint_editor_new (model, constraint);
+
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (editor));
+
+ g_signal_connect (editor, "done", G_CALLBACK (constraint_editor_done), win);
+
+ gtk_widget_show (window);
+}
+
+static void
+add_constraint (ConstraintEditorWindow *win)
+{
+ edit_constraint (win, NULL);
+}
+
+static void
+guide_editor_done (GuideEditor *editor,
+ GtkConstraintGuide *guide,
+ ConstraintEditorWindow *win)
+{
+ constraint_view_guide_changed (CONSTRAINT_VIEW (win->view), guide);
+ gtk_widget_destroy (gtk_widget_get_ancestor (GTK_WIDGET (editor), GTK_TYPE_WINDOW));
+}
+
+static void
+edit_guide (ConstraintEditorWindow *win,
+ GtkConstraintGuide *guide)
+{
+ GtkWidget *window;
+ GuideEditor *editor;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+ gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (win));
+ gtk_window_set_title (GTK_WINDOW (window), "Edit Guide");
+
+ editor = guide_editor_new (guide);
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (editor));
+
+ g_signal_connect (editor, "done", G_CALLBACK (guide_editor_done), win);
+ gtk_widget_show (window);
+}
+
+static void
+row_activated (GtkListBox *list,
+ GtkListBoxRow *row,
+ ConstraintEditorWindow *win)
+{
+ GObject *item;
+
+ item = G_OBJECT (g_object_get_data (G_OBJECT (row), "item"));
+
+ if (GTK_IS_CONSTRAINT (item))
+ edit_constraint (win, GTK_CONSTRAINT (item));
+ else if (GTK_IS_CONSTRAINT_GUIDE (item))
+ edit_guide (win, GTK_CONSTRAINT_GUIDE (item));
+}
+
+static void
+constraint_editor_window_class_init (ConstraintEditorWindowClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ g_type_ensure (CONSTRAINT_VIEW_TYPE);
+
+ object_class->finalize = constraint_editor_window_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gtk/gtk4/constraint-editor/constraint-editor-window.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditorWindow, paned);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditorWindow, view);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditorWindow, list);
+
+ gtk_widget_class_bind_template_callback (widget_class, add_child);
+ gtk_widget_class_bind_template_callback (widget_class, add_guide);
+ gtk_widget_class_bind_template_callback (widget_class, add_constraint);
+ gtk_widget_class_bind_template_callback (widget_class, row_activated);
+}
+
+static void
+row_edit (GtkButton *button,
+ ConstraintEditorWindow *win)
+{
+ GtkWidget *row;
+ GObject *item;
+
+ row = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_LIST_BOX_ROW);
+ item = (GObject *)g_object_get_data (G_OBJECT (row), "item");
+ if (GTK_IS_CONSTRAINT (item))
+ edit_constraint (win, GTK_CONSTRAINT (item));
+ else if (GTK_IS_CONSTRAINT_GUIDE (item))
+ edit_guide (win, GTK_CONSTRAINT_GUIDE (item));
+}
+
+static void
+mark_constraints_invalid (ConstraintEditorWindow *win,
+ gpointer removed)
+{
+ GtkWidget *child;
+ GObject *item;
+
+ for (child = gtk_widget_get_first_child (win->list);
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ item = (GObject *)g_object_get_data (G_OBJECT (child), "item");
+ if (GTK_IS_CONSTRAINT (item))
+ {
+ GtkConstraint *constraint = GTK_CONSTRAINT (item);
+
+ if (gtk_constraint_get_target (constraint) == (GtkConstraintTarget *)removed ||
+ gtk_constraint_get_source (constraint) == (GtkConstraintTarget *)removed)
+ {
+ GtkWidget *button;
+ button = (GtkWidget *)g_object_get_data (G_OBJECT (child), "edit");
+ gtk_button_set_icon_name (GTK_BUTTON (button), "dialog-warning-symbolic");
+ gtk_widget_set_tooltip_text (button, "Constraint is invalid");
+ }
+ }
+ }
+}
+
+static void
+row_delete (GtkButton *button,
+ ConstraintEditorWindow *win)
+{
+ GtkWidget *row;
+ GObject *item;
+
+ row = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_LIST_BOX_ROW);
+ item = (GObject *)g_object_get_data (G_OBJECT (row), "item");
+ if (GTK_IS_CONSTRAINT (item))
+ constraint_view_remove_constraint (CONSTRAINT_VIEW (win->view),
+ GTK_CONSTRAINT (item));
+ else if (GTK_IS_CONSTRAINT_GUIDE (item))
+ {
+ mark_constraints_invalid (win, item);
+ constraint_view_remove_guide (CONSTRAINT_VIEW (win->view),
+ GTK_CONSTRAINT_GUIDE (item));
+ }
+ else if (GTK_IS_WIDGET (item))
+ {
+ mark_constraints_invalid (win, item);
+ constraint_view_remove_child (CONSTRAINT_VIEW (win->view),
+ GTK_WIDGET (item));
+ }
+}
+
+static GtkWidget *
+create_widget_func (gpointer item,
+ gpointer user_data)
+{
+ ConstraintEditorWindow *win = user_data;
+ const char *name;
+ GtkWidget *row, *box, *label, *button;
+
+ name = (const char *)g_object_get_data (G_OBJECT (item), "name");
+
+ row = gtk_list_box_row_new ();
+ g_object_set_data (G_OBJECT (row), "item", item);
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ label = gtk_label_new (name);
+ g_object_set (label,
+ "margin", 10,
+ NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_container_add (GTK_CONTAINER (row), box);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ if (GTK_IS_CONSTRAINT (item) || GTK_IS_CONSTRAINT_GUIDE (item))
+ {
+ button = gtk_button_new_from_icon_name ("document-edit-symbolic");
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ g_signal_connect (button, "clicked", G_CALLBACK (row_edit), win);
+ g_object_set_data (G_OBJECT (row), "edit", button);
+ gtk_container_add (GTK_CONTAINER (box), button);
+ button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ g_signal_connect (button, "clicked", G_CALLBACK (row_delete), win);
+ gtk_container_add (GTK_CONTAINER (box), button);
+ }
+ else if (GTK_IS_WIDGET (item))
+ {
+ button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ g_signal_connect (button, "clicked", G_CALLBACK (row_delete), win);
+ gtk_container_add (GTK_CONTAINER (box), button);
+ }
+
+ return row;
+}
+
+static void
+constraint_editor_window_init (ConstraintEditorWindow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_list_box_bind_model (GTK_LIST_BOX (self->list),
+ constraint_view_get_model (CONSTRAINT_VIEW (self->view)),
+ create_widget_func,
+ self,
+ NULL);
+}
+
+ConstraintEditorWindow *
+constraint_editor_window_new (ConstraintEditorApplication *application)
+{
+ return g_object_new (CONSTRAINT_EDITOR_WINDOW_TYPE,
+ "application", application,
+ NULL);
+}
diff --git a/demos/constraint-editor/constraint-editor-window.h b/demos/constraint-editor/constraint-editor-window.h
new file mode 100644
index 0000000000..9096c6e71d
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-window.h
@@ -0,0 +1,34 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "constraint-editor-application.h"
+
+
+#define CONSTRAINT_EDITOR_WINDOW_TYPE (constraint_editor_window_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintEditorWindow, constraint_editor_window, CONSTRAINT, EDITOR_WINDOW, GtkApplicationWindow)
+
+ConstraintEditorWindow * constraint_editor_window_new (ConstraintEditorApplication *application);
+
+gboolean constraint_editor_window_load (ConstraintEditorWindow *self,
+ GFile *file);
diff --git a/demos/constraint-editor/constraint-editor-window.ui b/demos/constraint-editor/constraint-editor-window.ui
new file mode 100644
index 0000000000..c2c9a009ec
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-window.ui
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="ConstraintEditorWindow" parent="GtkApplicationWindow">
+ <style>
+ <class name="devel"/>
+ </style>
+ <property name="title" translatable="yes">GTK Constraint Editor</property>
+ <property name="default-width">1024</property>
+ <property name="default-height">768</property>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="header">
+ <property name="title" translatable="yes">GTK Constraint Editor</property>
+ <property name="show-title-buttons">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPaned" id="paned">
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkButton">
+ <property name="label">Add Child</property>
+ <signal name="clicked" handler="add_child" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label">Add Guide</property>
+ <signal name="clicked" handler="add_guide" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label">Add Constraint</property>
+ <signal name="clicked" handler="add_constraint" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="vscrollbar-policy">automatic</property>
+ <property name="vexpand">1</property>
+ <child>
+ <object class="GtkListBox" id="list">
+ <property name="show-separators">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="row_activated"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="ConstraintView" id="view">
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/demos/constraint-editor/constraint-editor.c b/demos/constraint-editor/constraint-editor.c
new file mode 100644
index 0000000000..5a85b35ecf
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.c
@@ -0,0 +1,583 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "constraint-editor.h"
+
+struct _ConstraintEditor
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *grid;
+ GtkWidget *name;
+ 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
+ return (const char *)g_object_get_data (G_OBJECT (target), "name");
+}
+
+static void
+constraint_target_combo (GListModel *model,
+ GtkWidget *combo,
+ gboolean is_source)
+{
+ int i;
+
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "super", "Super");
+
+ if (model)
+ {
+ for (i = 0; i < g_list_model_get_n_items (model); i++)
+ {
+ GObject *item = g_list_model_get_object (model, i);
+ const char *name;
+
+ if (GTK_IS_CONSTRAINT (item))
+ continue;
+
+ name = get_target_name (GTK_CONSTRAINT_TARGET (item));
+
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), name, name);
+ g_object_unref (item);
+ }
+ }
+}
+
+static void
+constraint_attribute_combo (GtkWidget *combo,
+ gboolean is_source)
+{
+ if (is_source)
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "none", "None");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "left", "Left");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "right", "Right");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "top", "Top");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "bottom", "Bottom");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "start", "Start");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "end", "End");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "width", "Width");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "height", "Height");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "center-x", "Center X");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "center-y", "Center Y");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "baseline", "Baseline");
+}
+
+static void
+constraint_relation_combo (GtkWidget *combo)
+{
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "le", "≤");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "eq", "=");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "ge", "≥");
+}
+
+static void
+constraint_strength_combo (GtkWidget *combo)
+{
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "weak", "Weak");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "medium", "Medium");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "strong", "Strong");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "required", "Required");
+}
+
+static gpointer
+get_target (GListModel *model,
+ const char *id)
+{
+ int i;
+
+ 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);
+ const char *name;
+ if (GTK_IS_CONSTRAINT (item))
+ continue;
+ name = (const char *)g_object_get_data (item, "name");
+ g_object_unref (item);
+ if (strcmp (name, id) == 0)
+ return item;
+ }
+
+ return NULL;
+}
+
+static GtkConstraintAttribute
+get_target_attr (const char *id)
+{
+ GtkConstraintAttribute attr;
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_ATTRIBUTE);
+ GEnumValue *value = g_enum_get_value_by_nick (class, id);
+ attr = value->value;
+ g_type_class_unref (class);
+
+ return attr;
+}
+
+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 (const char *id)
+{
+ GtkConstraintRelation relation;
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_RELATION);
+ GEnumValue *value = g_enum_get_value_by_nick (class, id);
+ relation = value->value;
+ g_type_class_unref (class);
+
+ return relation;
+}
+
+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 GtkConstraintStrength
+get_strength (const char *id)
+{
+ GtkConstraintStrength strength;
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_STRENGTH);
+ GEnumValue *value = g_enum_get_value_by_nick (class, id);
+ strength = value->value;
+ g_type_class_unref (class);
+
+ return strength;
+}
+
+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;
+}
+
+static void
+create_constraint (GtkButton *button,
+ ConstraintEditor *editor)
+{
+ const char *id;
+ gpointer target;
+ GtkConstraintAttribute target_attr;
+ gpointer source;
+ GtkConstraintAttribute source_attr;
+ GtkConstraintRelation relation;
+ double multiplier;
+ double constant;
+ int strength;
+ GtkConstraint *constraint;
+ const char *name;
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target));
+ target = get_target (editor->model, id);
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target_attr));
+ target_attr = get_target_attr (id);
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source));
+ source = get_target (editor->model, id);
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source_attr));
+ source_attr = get_target_attr (id);
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->relation));
+ relation = get_relation (id);
+
+ 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);
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->strength));
+ strength = get_strength (id);
+
+ name = gtk_editable_get_text (GTK_EDITABLE (editor->name));
+
+ constraint = gtk_constraint_new (target, target_attr,
+ relation,
+ source, source_attr,
+ multiplier,
+ constant,
+ strength);
+ g_object_set_data_full (G_OBJECT (constraint), "name", g_strdup (name), g_free);
+ g_signal_emit (editor, signals[DONE], 0, constraint);
+ g_object_unref (constraint);
+}
+
+static void
+source_attr_changed (ConstraintEditor *editor)
+{
+ const char *id;
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source_attr));
+ if (strcmp (id, "none") == 0)
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (editor->source), -1);
+ 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);
+ }
+}
+
+static void
+update_preview (ConstraintEditor *editor)
+{
+ GString *str;
+ const char *name;
+ const char *attr;
+ char *relation;
+ const char *multiplier;
+ const char *constant;
+ double c, m;
+
+ if (!editor->constructed)
+ return;
+
+ str = g_string_new ("");
+
+ name = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target));
+ attr = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target_attr));
+ relation = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (editor->relation));
+
+ if (name == NULL)
+ name = "[ ]";
+
+ g_string_append_printf (str, "%s.%s %s ", name, attr, relation);
+ g_free (relation);
+
+ constant = gtk_editable_get_text (GTK_EDITABLE (editor->constant));
+ c = g_ascii_strtod (constant, NULL);
+
+ attr = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source_attr));
+ if (strcmp (attr, "none") != 0)
+ {
+ name = gtk_combo_box_get_active_id (GTK_COMBO_BOX (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)
+{
+ if (gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target)) != NULL &&
+ gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source)) != NULL)
+ 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 int constraint_counter;
+
+static void
+constraint_editor_constructed (GObject *object)
+{
+ ConstraintEditor *editor = CONSTRAINT_EDITOR (object);
+
+ constraint_target_combo (editor->model, editor->target, FALSE);
+ constraint_attribute_combo (editor->target_attr, FALSE);
+ constraint_relation_combo (editor->relation);
+ constraint_target_combo (editor->model, editor->source, TRUE);
+ constraint_attribute_combo (editor->source_attr, TRUE);
+
+ constraint_strength_combo (editor->strength);
+
+ if (editor->constraint)
+ {
+ GtkConstraintTarget *target;
+ GtkConstraintAttribute attr;
+ GtkConstraintRelation relation;
+ GtkConstraintStrength strength;
+ const char *nick;
+ char *val;
+ double multiplier;
+ double constant;
+
+ nick = (char *)g_object_get_data (G_OBJECT (editor->constraint), "name");
+ gtk_editable_set_text (GTK_EDITABLE (editor->name), nick);
+
+ target = gtk_constraint_get_target (editor->constraint);
+ nick = get_target_name (target);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->target), nick);
+
+ attr = gtk_constraint_get_target_attribute (editor->constraint);
+ nick = get_attr_nick (attr);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->target_attr), nick);
+
+ target = gtk_constraint_get_source (editor->constraint);
+ nick = get_target_name (target);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->source), nick);
+
+ attr = gtk_constraint_get_source_attribute (editor->constraint);
+ nick = get_attr_nick (attr);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->source_attr), nick);
+
+ relation = gtk_constraint_get_relation (editor->constraint);
+ nick = get_relation_nick (relation);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->relation), nick);
+
+ 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);
+ nick = get_strength_nick (strength);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->strength), nick);
+
+ gtk_button_set_label (GTK_BUTTON (editor->button), "Apply");
+ }
+ else
+ {
+ char *name;
+
+ constraint_counter++;
+ name = g_strdup_printf ("Constraint %d", constraint_counter);
+ gtk_editable_set_text (GTK_EDITABLE (editor->name), name);
+ g_free (name);
+
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->target_attr), "left");
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->source_attr), "left");
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->relation), "eq");
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->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_pointer (&self->grid, gtk_widget_unparent);
+ g_clear_object (&self->model);
+ g_clear_object (&self->constraint);
+
+ 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, name);
+ 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);
+}
diff --git a/demos/constraint-editor/constraint-editor.css b/demos/constraint-editor/constraint-editor.css
new file mode 100644
index 0000000000..c9538bf268
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.css
@@ -0,0 +1,12 @@
+constraintview {
+ background: black;
+ color: white;
+}
+
+constraintview .child {
+ background: red;
+}
+
+constraintview .guide {
+ background: blue;
+}
diff --git a/demos/constraint-editor/constraint-editor.gresource.xml b/demos/constraint-editor/constraint-editor.gresource.xml
new file mode 100644
index 0000000000..e57964b1a7
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gtk/gtk4/constraint-editor">
+ <file preprocess="xml-stripblanks">constraint-editor-window.ui</file>
+ <file preprocess="xml-stripblanks">constraint-editor.ui</file>
+ <file preprocess="xml-stripblanks">guide-editor.ui</file>
+ <file>constraint-editor.css</file>
+ </gresource>
+</gresources>
diff --git a/demos/constraint-editor/constraint-editor.h b/demos/constraint-editor/constraint-editor.h
new file mode 100644
index 0000000000..c5940e254b
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.h
@@ -0,0 +1,29 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define CONSTRAINT_EDITOR_TYPE (constraint_editor_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintEditor, constraint_editor, CONSTRAINT, EDITOR, GtkWidget)
+
+ConstraintEditor * constraint_editor_new (GListModel *model,
+ GtkConstraint *constraint);
diff --git a/demos/constraint-editor/constraint-editor.ui b/demos/constraint-editor/constraint-editor.ui
new file mode 100644
index 0000000000..604757659a
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.ui
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="ConstraintEditor" parent="GtkWidget">
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="margin">20</property>
+ <property name="row-spacing">10</property>
+ <property name="column-spacing">10</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Name</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name">
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Target</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="target">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <signal name="changed" handler="update_button" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="target_attr">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Relation</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="relation">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Source</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="source">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <signal name="changed" handler="update_button" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="source_attr">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <signal name="changed" handler="source_attr_changed" swapped="yes"/>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Multiplier</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="multiplier">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Constant</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">5</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="constant">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">5</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Strength</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">6</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="strength">
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">6</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="preview">
+ <property name="xalign">0</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">7</property>
+ <property name="column-span">2</property>
+ </layout>
+ <attributes>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button">
+ <property name="label">Create</property>
+ <signal name="clicked" handler="create_constraint"/>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">8</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/demos/constraint-editor/constraint-view-child.c b/demos/constraint-editor/constraint-view-child.c
new file mode 100644
index 0000000000..272054a387
--- /dev/null
+++ b/demos/constraint-editor/constraint-view-child.c
@@ -0,0 +1,93 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "constraint-view-child.h"
+
+struct _ConstraintViewChild
+{
+ GObject parent_instance;
+
+ char *name;
+};
+
+enum {
+ PROP_NAME = 1,
+ LAST_PROP
+};
+
+static GParamSpec props[LAST_PROP];
+
+G_DEFINE_TYPE (ConstraintViewChild, constraint_view_child, G_TYPE_OBJECT)
+
+static void
+constraint_view_child_init (ConstraintViewChild *child)
+{
+}
+
+static void
+constraint_view_child_finalize (GObject *object)
+{
+ ConstraintViewChild *child = CONSTRAINT_VIEW_CHILD (object);
+
+ g_free (child->name);
+
+ G_OBJECT_CLASS (constraint_view_child_parent_class)->finalize (object);
+}
+
+static void
+constraint_view_child_set_property (GObject *object,
+
+static void
+constraint_view_child_class_init (ConstraintViewChildClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = constraint_view_child_finalize;
+ object_class->get_property = constraint_view_child_get_property;
+ object_class->set_property = constraint_view_child_set_property;
+
+ props[PROP_NAME] =
+ g_param_spec_string ("name", "name", "name",
+ NULL,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+}
+
+#define CONSTRAINT_VIEW_CHILD_TYPE (constraint_view_get_type ())
+
+G_DECLARE_TYPE (ConstraintViewChild, constraint_view_child, CONSTRAINT, VIEW_CHILD, GObject)
+
+#define CONSTRAINT_VIEW_WIDGET_TYPE (constraint_view_widget_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewWidget, constraint_view_widget, CONSTRAINT, VIEW_WIDGET, ConstraintViewChild)
+
+ConstraintViewWidget * constraint_view_widget_new (void);
+
+#define CONSTRAINT_VIEW_GUIDE_TYPE (constraint_view_guide_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewGuide, constraint_view_guide, CONSTRAINT, VIEW_GUIDE, ConstraintViewChild)
+
+ConstraintViewGuide * constraint_view_guide_new (void);
+
+#define CONSTRAINT_VIEW_CONSTRAINT_TYPE (constraint_view_constraint_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewConstraint, constraint_view_constraint, CONSTRAINT, VIEW_CONSTRAINT, ConstraintViewChild)
+
+ConstraintViewGuide * constraint_view_constraint_new (void);
diff --git a/demos/constraint-editor/constraint-view-child.h b/demos/constraint-editor/constraint-view-child.h
new file mode 100644
index 0000000000..6f2120ef0b
--- /dev/null
+++ b/demos/constraint-editor/constraint-view-child.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define CONSTRAINT_VIEW_CHILD_TYPE (constraint_view_get_type ())
+
+G_DECLARE_TYPE (ConstraintViewChild, constraint_view_child, CONSTRAINT, VIEW_CHILD, GObject)
+
+#define CONSTRAINT_VIEW_WIDGET_TYPE (constraint_view_widget_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewWidget, constraint_view_widget, CONSTRAINT, VIEW_WIDGET, ConstraintViewChild)
+
+ConstraintViewWidget * constraint_view_widget_new (void);
+
+#define CONSTRAINT_VIEW_GUIDE_TYPE (constraint_view_guide_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewGuide, constraint_view_guide, CONSTRAINT, VIEW_GUIDE, ConstraintViewChild)
+
+ConstraintViewGuide * constraint_view_guide_new (void);
+
+#define CONSTRAINT_VIEW_CONSTRAINT_TYPE (constraint_view_constraint_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewConstraint, constraint_view_constraint, CONSTRAINT, VIEW_CONSTRAINT, ConstraintViewChild)
+
+ConstraintViewGuide * constraint_view_constraint_new (void);
diff --git a/demos/constraint-editor/constraint-view.c b/demos/constraint-editor/constraint-view.c
new file mode 100644
index 0000000000..eb5852a94c
--- /dev/null
+++ b/demos/constraint-editor/constraint-view.c
@@ -0,0 +1,277 @@
+/* Copyright (C) 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 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+#include "constraint-view.h"
+
+struct _ConstraintView
+{
+ GtkWidget parent;
+
+ GListStore *store;
+};
+
+G_DEFINE_TYPE (ConstraintView, constraint_view, GTK_TYPE_WIDGET);
+
+static void
+constraint_view_dispose (GObject *object)
+{
+ ConstraintView *view = CONSTRAINT_VIEW (object);
+ GtkWidget *child;
+
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (view))) != NULL)
+ gtk_widget_unparent (child);
+
+ g_clear_object (&view->store);
+
+ G_OBJECT_CLASS (constraint_view_parent_class)->dispose (object);
+}
+
+static void
+constraint_view_class_init (ConstraintViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = constraint_view_dispose;
+
+ gtk_widget_class_set_css_name (widget_class, "constraintview");
+}
+
+static void
+constraint_view_init (ConstraintView *self)
+{
+ gtk_widget_set_layout_manager (GTK_WIDGET (self),
+ gtk_constraint_layout_new ());
+
+ self->store = g_list_store_new (G_TYPE_OBJECT);
+}
+
+ConstraintView *
+constraint_view_new (void)
+{
+ return g_object_new (CONSTRAINT_VIEW_TYPE, NULL);
+}
+
+void
+constraint_view_add_child (ConstraintView *view,
+ const char *name)
+{
+ GtkWidget *frame;
+ GtkWidget *label;
+
+ label = gtk_label_new (name);
+ frame = gtk_frame_new (NULL);
+ gtk_style_context_add_class (gtk_widget_get_style_context (frame), "child");
+ g_object_set_data_full (G_OBJECT (frame), "name", g_strdup (name), g_free);
+ gtk_container_add (GTK_CONTAINER (frame), label);
+ gtk_widget_set_parent (frame, GTK_WIDGET (view));
+
+ g_list_store_append (view->store, frame);
+}
+
+void
+constraint_view_remove_child (ConstraintView *view,
+ GtkWidget *child)
+{
+ int i;
+
+ gtk_widget_unparent (child);
+
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (view->store)); i++)
+ {
+ if (g_list_model_get_item (G_LIST_MODEL (view->store), i) == (GObject*)child)
+ {
+ g_list_store_remove (view->store, i);
+ break;
+ }
+ }
+}
+
+void
+constraint_view_add_guide (ConstraintView *view,
+ GtkConstraintGuide *guide)
+{
+ GtkLayoutManager *manager;
+ GtkWidget *frame;
+ GtkWidget *label;
+ const char *name;
+ GtkConstraint *constraint;
+
+ name = (const char *)g_object_get_data (G_OBJECT (guide), "name");
+
+ label = gtk_label_new (name);
+ frame = gtk_frame_new (NULL);
+ gtk_style_context_add_class (gtk_widget_get_style_context (frame), "guide");
+ g_object_set_data_full (G_OBJECT (frame), "name", g_strdup (name), g_free);
+ gtk_container_add (GTK_CONTAINER (frame), label);
+ gtk_widget_set_parent (frame, GTK_WIDGET (view));
+
+ g_object_set_data (G_OBJECT (guide), "frame", frame);
+ g_object_set_data (G_OBJECT (guide), "label", label);
+
+ manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
+ gtk_constraint_layout_add_guide (GTK_CONSTRAINT_LAYOUT (manager),
+ g_object_ref (guide));
+
+ constraint = gtk_constraint_new (frame,
+ GTK_CONSTRAINT_ATTRIBUTE_LEFT,
+ GTK_CONSTRAINT_RELATION_EQ,
+ guide,
+ GTK_CONSTRAINT_ATTRIBUTE_LEFT,
+ 1.0, 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (guide), "left-constraint", constraint);
+
+ constraint = gtk_constraint_new (frame,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ GTK_CONSTRAINT_RELATION_EQ,
+ guide,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ 1.0, 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (guide), "top-constraint", constraint);
+
+ constraint = gtk_constraint_new (frame,
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ GTK_CONSTRAINT_RELATION_EQ,
+ guide,
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ 1.0, 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (guide), "width-constraint", constraint);
+
+ constraint = gtk_constraint_new (frame,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
+ GTK_CONSTRAINT_RELATION_EQ,
+ guide,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
+ 1.0, 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+
+ g_object_set_data (G_OBJECT (guide), "height-constraint", constraint);
+
+ g_list_store_append (view->store, guide);
+}
+
+void
+constraint_view_guide_changed (ConstraintView *view,
+ GtkConstraintGuide *guide)
+{
+ GtkWidget *label;
+ const char *name;
+ int i;
+
+ name = (const char *)g_object_get_data (G_OBJECT (guide), "name");
+ label = (GtkWidget *)g_object_get_data (G_OBJECT (guide), "label");
+ gtk_label_set_label (GTK_LABEL (label), name);
+
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (view->store)); i++)
+ {
+ if (g_list_model_get_item (G_LIST_MODEL (view->store), i) == (GObject*)guide)
+ {
+ g_list_model_items_changed (G_LIST_MODEL (view->store), i, 1, 1);
+ break;
+ }
+ }
+}
+
+void
+constraint_view_remove_guide (ConstraintView *view,
+ GtkConstraintGuide *guide)
+{
+ GtkLayoutManager *manager;
+ GtkWidget *frame;
+ GtkConstraint *constraint;
+ int i;
+
+ manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
+
+ constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), "left-constraint");
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), "top-constraint");
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), "width-constraint");
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), "height-constraint");
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+
+ frame = (GtkWidget *)g_object_get_data (G_OBJECT (guide), "frame");
+ gtk_widget_unparent (frame);
+
+ gtk_constraint_layout_remove_guide (GTK_CONSTRAINT_LAYOUT (manager),
+ guide);
+
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (view->store)); i++)
+ {
+ if (g_list_model_get_item (G_LIST_MODEL (view->store), i) == (GObject*)guide)
+ {
+ g_list_store_remove (view->store, i);
+ break;
+ }
+ }
+}
+
+void
+constraint_view_add_constraint (ConstraintView *view,
+ GtkConstraint *constraint)
+{
+ GtkLayoutManager *manager;
+
+ manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ g_object_ref (constraint));
+
+ g_list_store_append (view->store, constraint);
+}
+
+void
+constraint_view_remove_constraint (ConstraintView *view,
+ GtkConstraint *constraint)
+{
+ GtkLayoutManager *manager;
+ int i;
+
+ manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (view->store)); i++)
+ {
+ if (g_list_model_get_item (G_LIST_MODEL (view->store), i) == (GObject*)constraint)
+ {
+ g_list_store_remove (view->store, i);
+ break;
+ }
+ }
+}
+
+GListModel *
+constraint_view_get_model (ConstraintView *view)
+{
+ return G_LIST_MODEL (view->store);
+}
diff --git a/demos/constraint-editor/constraint-view.h b/demos/constraint-editor/constraint-view.h
new file mode 100644
index 0000000000..40e80c8c43
--- /dev/null
+++ b/demos/constraint-editor/constraint-view.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define CONSTRAINT_VIEW_TYPE (constraint_view_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintView, constraint_view, CONSTRAINT, VIEW, GtkWidget)
+
+ConstraintView * constraint_view_new (void);
+
+void constraint_view_add_child (ConstraintView *view,
+ const char *name);
+void constraint_view_remove_child (ConstraintView *view,
+ GtkWidget *child);
+void constraint_view_add_guide (ConstraintView *view,
+ GtkConstraintGuide *guide);
+void constraint_view_remove_guide (ConstraintView *view,
+ GtkConstraintGuide *guide);
+void constraint_view_guide_changed (ConstraintView *view,
+ GtkConstraintGuide *guide);
+void constraint_view_add_constraint (ConstraintView *view,
+ GtkConstraint *constraint);
+void constraint_view_remove_constraint (ConstraintView *view,
+ GtkConstraint *constraint);
+GListModel * constraint_view_get_model (ConstraintView *view);
diff --git a/demos/constraint-editor/guide-editor.c b/demos/constraint-editor/guide-editor.c
new file mode 100644
index 0000000000..e4baf8ca32
--- /dev/null
+++ b/demos/constraint-editor/guide-editor.c
@@ -0,0 +1,318 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "guide-editor.h"
+
+struct _GuideEditor
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *grid;
+ GtkWidget *name;
+ GtkWidget *min_width;
+ GtkWidget *min_height;
+ GtkWidget *nat_width;
+ GtkWidget *nat_height;
+ GtkWidget *max_width;
+ GtkWidget *max_height;
+ GtkWidget *strength;
+ GtkWidget *button;
+
+ GtkConstraintGuide *guide;
+
+ gboolean constructed;
+};
+
+enum {
+ PROP_GUIDE = 1,
+ LAST_PROP
+};
+
+static GParamSpec *pspecs[LAST_PROP];
+
+enum {
+ DONE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE(GuideEditor, guide_editor, GTK_TYPE_WIDGET);
+
+static void
+guide_strength_combo (GtkWidget *combo)
+{
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "weak", "Weak");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "medium", "Medium");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "strong", "Strong");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "required", "Required");
+}
+
+#if 0
+static GtkConstraintStrength
+get_strength (const char *id)
+{
+ GtkConstraintStrength strength;
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_STRENGTH);
+ GEnumValue *value = g_enum_get_value_by_nick (class, id);
+ strength = value->value;
+ g_type_class_unref (class);
+
+ return strength;
+}
+#endif
+
+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;
+}
+
+static void
+create_guide (GtkButton *button,
+ GuideEditor *editor)
+{
+#if 0
+ const char *id;
+ int strength;
+#endif
+ const char *name;
+ int w, h;
+ GtkConstraintGuide *guide;
+
+ //guide = gtk_constraint_guide_new ();
+ if (editor->guide)
+ guide = g_object_ref (editor->guide);
+ else
+ guide = g_object_new (GTK_TYPE_CONSTRAINT_GUIDE, NULL);
+
+ name = gtk_editable_get_text (GTK_EDITABLE (editor->name));
+ g_object_set_data_full (G_OBJECT (guide), "name", g_strdup (name), g_free);
+
+ w = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->min_width));
+ h = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->min_height));
+ //gtk_constraint_guide_set_min_size (guide, w, h);
+ g_object_set (guide, "min-width", w, "min-height", h, NULL);
+
+ w = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->nat_width));
+ h = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->nat_height));
+ //gtk_constraint_guide_set_nat_size (guide, w, h);
+ g_object_set (guide, "nat-width", w, "nat-height", h, NULL);
+
+#if 0
+ w = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->max_width));
+ h = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->max_height));
+ gtk_constraint_guide_set_max_size (guide, w, h);
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->strength));
+ strength = get_strength (id);
+ gtk_constraint_guide_set_strength (guide, strength);
+#endif
+
+ g_signal_emit (editor, signals[DONE], 0, guide);
+ g_object_unref (guide);
+}
+
+static void
+guide_editor_init (GuideEditor *editor)
+{
+ gtk_widget_init_template (GTK_WIDGET (editor));
+}
+
+static int guide_counter;
+
+static void
+guide_editor_constructed (GObject *object)
+{
+ GuideEditor *editor = GUIDE_EDITOR (object);
+
+ guide_strength_combo (editor->strength);
+
+ gtk_widget_set_sensitive (editor->max_width, FALSE);
+ gtk_widget_set_sensitive (editor->max_height, FALSE);
+ gtk_widget_set_sensitive (editor->strength, FALSE);
+
+ if (editor->guide)
+ {
+#if 0
+ GtkConstaintStrength strength;
+#endif
+ const char *nick;
+ int w, h;
+
+ nick = (char *)g_object_get_data (G_OBJECT (editor->guide), "name");
+ gtk_editable_set_text (GTK_EDITABLE (editor->name), nick);
+
+ //gtk_constaint_guide_get_min_size (editor->guide, &w, &h);
+ g_object_get (editor->guide, "min-width", &w, "min-height", &h, NULL);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->min_width), w);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->min_height), h);
+
+ //gtk_constaint_guide_get_nat_size (editor->guide, &w, &h);
+ g_object_get (editor->guide, "nat-width", &w, "nat-height", &h, NULL);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_width), w);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_height), h);
+
+#if 0
+ gtk_constaint_guide_get_max_size (editor->guide, &w, &h);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->max_width), w);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->max_height), h);
+
+ strength = gtk_guide_get_strength (editor->guide);
+ nick = get_strength_nick (strength);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->strength), nick);
+#endif
+
+ gtk_button_set_label (GTK_BUTTON (editor->button), "Apply");
+ }
+ else
+ {
+ char *name;
+
+ guide_counter++;
+ name = g_strdup_printf ("Guide %d", guide_counter);
+ gtk_editable_set_text (GTK_EDITABLE (editor->name), name);
+ g_free (name);
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->min_width), 0.0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->min_height), 0.0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_width), 0.0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_height), 0.0);
+#if 0
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->max_width), G_MAXINT);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->max_height), G_MAXINT);
+
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->strength), "medium");
+#endif
+
+ gtk_button_set_label (GTK_BUTTON (editor->button), "Create");
+ }
+
+ editor->constructed = TRUE;
+}
+
+static void
+guide_editor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GuideEditor *self = GUIDE_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_GUIDE:
+ self->guide = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+guide_editor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GuideEditor *self = GUIDE_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_GUIDE:
+ g_value_set_object (value, self->guide);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+guide_editor_dispose (GObject *object)
+{
+ GuideEditor *self = (GuideEditor *)object;
+
+ g_clear_pointer (&self->grid, gtk_widget_unparent);
+ g_clear_object (&self->guide);
+
+ G_OBJECT_CLASS (guide_editor_parent_class)->dispose (object);
+}
+
+static void
+guide_editor_class_init (GuideEditorClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ object_class->constructed = guide_editor_constructed;
+ object_class->dispose = guide_editor_dispose;
+ object_class->set_property = guide_editor_set_property;
+ object_class->get_property = guide_editor_get_property;
+
+ pspecs[PROP_GUIDE] =
+ g_param_spec_object ("guide", "guide", "guide",
+ GTK_TYPE_CONSTRAINT_GUIDE,
+ 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_GUIDE);
+
+ 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/guide-editor.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, grid);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, name);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, min_width);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, min_height);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, nat_width);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, nat_height);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, max_width);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, max_height);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, strength);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, button);
+
+ gtk_widget_class_bind_template_callback (widget_class, create_guide);
+}
+
+GuideEditor *
+guide_editor_new (GtkConstraintGuide *guide)
+{
+ return g_object_new (GUIDE_EDITOR_TYPE,
+ "guide", guide,
+ NULL);
+}
diff --git a/demos/constraint-editor/guide-editor.h b/demos/constraint-editor/guide-editor.h
new file mode 100644
index 0000000000..d11cb4f3db
--- /dev/null
+++ b/demos/constraint-editor/guide-editor.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define GUIDE_EDITOR_TYPE (guide_editor_get_type ())
+
+G_DECLARE_FINAL_TYPE (GuideEditor, guide_editor, GUIDE, EDITOR, GtkWidget)
+
+GuideEditor * guide_editor_new (GtkConstraintGuide *guide);
diff --git a/demos/constraint-editor/guide-editor.ui b/demos/constraint-editor/guide-editor.ui
new file mode 100644
index 0000000000..a788de10bf
--- /dev/null
+++ b/demos/constraint-editor/guide-editor.ui
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <object class="GtkAdjustment" id="min_width_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="min_height_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="nat_width_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="nat_height_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="max_width_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="max_height_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <template class="GuideEditor" parent="GtkWidget">
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="margin">20</property>
+ <property name="row-spacing">10</property>
+ <property name="column-spacing">10</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Name</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name">
+ <property name="max-width-chars">20</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Min Size</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="min_width">
+ <property name="adjustment">min_width_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="min_height">
+ <property name="adjustment">min_height_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Nat Size</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="nat_width">
+ <property name="adjustment">nat_width_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="nat_height">
+ <property name="adjustment">nat_height_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Max Size</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max_width">
+ <property name="adjustment">max_width_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max_height">
+ <property name="adjustment">max_height_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Strength</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="strength">
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button">
+ <property name="label">Create</property>
+ <signal name="clicked" handler="create_guide"/>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">5</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/demos/constraint-editor/main.c b/demos/constraint-editor/main.c
new file mode 100644
index 0000000000..771774c08f
--- /dev/null
+++ b/demos/constraint-editor/main.c
@@ -0,0 +1,28 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#include "config.h"
+
+#include <constraint-editor-application.h>
+
+int
+main (int argc, char *argv[])
+{
+ return g_application_run (G_APPLICATION (constraint_editor_application_new ()), argc, argv);
+}
diff --git a/demos/constraint-editor/meson.build b/demos/constraint-editor/meson.build
new file mode 100644
index 0000000000..af66846437
--- /dev/null
+++ b/demos/constraint-editor/meson.build
@@ -0,0 +1,19 @@
+constraint_editor_sources = [
+ 'main.c',
+ 'constraint-editor-application.c',
+ 'constraint-editor-window.c',
+ 'constraint-view.c',
+ 'constraint-editor.c',
+ 'guide-editor.c',
+]
+
+constraint_editor_resources = gnome.compile_resources('constraint_editor_resources',
+ 'constraint-editor.gresource.xml',
+ source_dir: '.')
+
+executable('gtk4-constraint-editor',
+ constraint_editor_sources, constraint_editor_resources,
+ dependencies: libgtk_dep,
+ include_directories: confinc,
+ gui_app: true,
+ install: false)
diff --git a/demos/meson.build b/demos/meson.build
index 6f1905ddb6..c9d7081b40 100644
--- a/demos/meson.build
+++ b/demos/meson.build
@@ -1,3 +1,4 @@
+subdir('constraint-editor')
subdir('gtk-demo')
subdir('icon-browser')
subdir('node-editor')